Sunday, November 15, 2009

Asynchronous Socket Programming in C#



Objective

The objective of this article is to demonstrate a socket-based client/server application that will allow two-way asynchronous communication between a server and multiple client applications. Because this example uses asynchronous methods, the server application does not use threads to communicate to multiple clients (although internally the asynchronous communication mechanism uses threads at the OS level).

The Difference Between Synchronous and Asynchronous Communication in Network Programming


The key difference between synchronous and asynchronous communication can be explained with an example.
Consider a server application that is listening on a specific port to get data from clients. In synchronous receiving, while the server is waiting to receive data from a client, if the stream is empty the main thread will block until the request for data is satisfied. Hence, the server cannot do anything else until it receives data from the client. If another client attempts to connect to the server at that time, the server cannot process that request because it is blocked on the first client. This behavior is not acceptable for a real-world application where we need to support multiple clients at the same time.
In asynchronous communication, while the server is listening or receiving data from a client, it can still process connection requests from other clients as well as receive data from those clients. When a server is receiving asynchronously, a separate thread (at the OS level) listens on the socket and will invoke a callback function (specified when the asynchronous listening was commenced) when a socket event occurs. This callback function in turn will respond and process that socket event. For example, if the remote program writes some data to the socket, a "read data event" (callback function you specify) is invoked; it knows how to read the data from the socket at that point.
Even though this could be achieved by running multiple threads, the C# and .NET frameworks provide a rich set of functionalities to do asynchronous communications without introducing the complexity of threading.

Socket Class

The Socket class (System.Net.Sockets.Socket) provides a set of synchronous and asynchronous methods for synchronous or asynchronous communication. As per the .NET naming convention, all the asynchronous method names are created by prefixing the words "Begin" or "End" to the name of the synchronous methods. The methods prefixed with "Begin" and "End" represent a pair of asynchronous methods corresponding to a single synchronous method, as shown in the following table.
Synchronous Methods Asynchronous Methods
Connect()
BeginConnect()
EndConnect()
Receive()
BeginReceive()
EndReceive()

Example Application

The example shown in this article has two classes, one implementing the Socket Server and the other implementing the Socket Client.

Socket Server Implementation


The Socket Server application is implemented in the SocketServer class (file name SocketServer.cs). This class has a main Socket object (m_mainSocket) and an array of worker Socket objects (m_workerSocket) as members. The main Socket object does the listening for the clients. Once a client is connected, the main Socket transfers the responsibility to process the transactions related to that particular client to a worker Socket. Then, the main Socket goes back and continues listening for other clients.
BeginAccept() and BeginReceive() are the two important methods in the Socket class used by the Socket Server application.
The BeginAccept() method has the following signature:
public IAsyncResult BeginAccept(
   AsyncCallback callback,    // (1) Function to call when a client
                              //     is connected
   object state               // (2) State object to preserve socket
                              //     info
);
Essentially, after calling the Listen() method of the main Socket object, you call this asynchronous method and specify a call back function (1), which you designated to do the further processing related to the client connection. The state object (2) can be null in this particular instance.
Because this is an asynchronous method, it will return immediately and the server main thread is free to process other events. Behind the scenes, a separate thread will start listening on that particular socket for client connections. When a client requests a connection, the callback function you specified will be invoked.
Inside the callback function (in the example, the function is named "OnClientConnect()"), you will do further processing related to the client connection.


public void OnClientConnect(IAsyncResult asyn)
{
   try
   {
      // Here we complete/end the BeginAccept() asynchronous call
      // by calling EndAccept() - which returns the reference to
      // a new Socket object
      m_workerSocket[m_clientCount] = m_mainSocket.EndAccept (asyn);
      // Let the worker Socket do the further processing for the
      // just connected client
      WaitForData(m_workerSocket[m_clientCount]);
      // Now increment the client count
      ++m_clientCount;
      // Display this client connection as a status message on the GUI
      String str = String.Format("Client # {0} connected",
                                 m_clientCount);
      textBoxMsg.Text = str;

      // Since the main Socket is now free, it can go back and wait
      // for other clients who are attempting to connect
      m_mainSocket.BeginAccept(new AsyncCallback
                              ( OnClientConnect ),null);
   }
   catch(ObjectDisposedException)
   {
      System.Diagnostics.Debugger.Log(0,"1","\n OnClientConnection:
                                      Socket has been closed\n");
   }
   catch(SocketException se)
   {
      MessageBox.Show ( se.Message );
   }

}

The first thing you do inside the "OnClientConnect()" function is to call the EndAccept() method on the m_mainSocket member object, which will return a reference to another socket object. You set this object reference to one of the members of the array of Socket object references you have (m_workerSocket) and also increment the client counter. Now, because you have a reference to a new socket object that now can do the further transaction with the client, the main Socket (m_mainSocket) is free; hence, you will call its BeginAccept() method again to start waiting for connection requests from other clients.
On the worker socket, you use a similar strategy to receive the data from the client. In place of calling BeginAccept() and EndAccept(), here you call BeginReceive() and EndReceive(). This, in a nutshell, is the Socket Server implementation. While you are sending out data to the clients, the server simply uses the specific worker socket objects to send data to each client.

Socket Client Implementation




See full details: http://www.codeguru.com/csharp/csharp/cs_network/sockets/article.php/c7695/

No comments: