Asynchronous Socket Programming in C#: Part II

Motivation for This Article

After the original article on Asynchronous Socket Programming in C# was published by CodeGuru, I received numerous responses from interested readers. Most of them asked for additional features that were missing in the original example. The original article was intended to show a simplistic example for asynchronous socket programming. To keep the simplicity of the original example, instead of modifying it, I am providing a more comprehensive example by adding the features requested by the readers.

Requested Features Added

This example includes modifications to support the following features:


  1. How to support an unlimited number of clients

  2. How to find which client sent a particular message

  3. How to reply or send messages to specific clients

  4. How to find when a particular client is disconnected

  5. How to get the list of all connected clients at any given time

  6. Are variables safe in AsyncCallback methods? What about thread synchronization? [Updated on 02/01/05]

Other Enhancements


  1. On the server and client code, the receive buffer size is increased to 1024 instead of a single byte for more efficiency.

  2. Cleanup code is added after a client is disconnected.

Screen shot of Socket Server:

Screen shot of Socket Client:

How to Support an Unlimited Number of Clients

This was an easy feature to add. In the original article, an array of Socket objects was used to store the references to the worker sockets. This is now modified to use an ArrayList as shown in the following code. (A HashTable also would have worked if you wanted to use a string instead of an index to track the connected clients.)

Note: If you want to run your server for an infinite duration, there is the possibility of overflow of the integer value of the m_clientCount variable. In such scenarios, you may want to reconsider using this numbering scheme for clients. This example will still work on such scenarios, as long as you don’t number your clients. But, this issue goes beyond the scope of this article.

// An ArrayList is used to keep track of worker sockets that are
// designed to communicate with each connected client
private System.Collections.ArrayList m_workerSocketList =
   new System.Collections.ArrayList();
// The following variable will keep track of the cumulative
// total number of clients connected at any time
private int m_clientCount = 0;

How to Find Which Client Sent a Particular Message

When multiple clients are connected, you may need to differentiate between the messages received from different clients. Also, there may be a reason to send a message to a particular client.

You could solve this problem by keeping track of each client by assigning them a serially incremented number as soon as they are connected to the server. Here is the code that does that:

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
      Socket workerSocket = m_mainSocket.EndAccept (asyn);
      // Now, increment the client count for this client
      ++m_clientCount;
      // Add the workerSocket reference to the ArrayList
      // We will use (clientNumber - 1) as the index to access
      // this socket in the future
      m_workerSocketList.Add(workerSocket);
      //........
      // Let the worker Socket do the further processing for the
      // just-connected client
      WaitForData(workerSocket, m_clientCount);
      //........

Inside the WaitForData() function, you will make the actual asynchronous call to receive the data from the client as shown below:

public void WaitForData(System.Net.Sockets.Socket soc,
                        int clientNumber)
{
   try
   {
      if( pfnWorkerCallBack == null )
      {
         // Specify the callback function that is to be invoked when
         // there is any write activity by the connected client
         pfnWorkerCallBack = new AsyncCallback (OnDataReceived);
      }
      SocketPacket theSocPkt = new SocketPacket (soc, clientNumber);
      // Start receiving any data written by the connected client
      // asynchronously
      soc.BeginReceive (theSocPkt.dataBuffer, 0,
      theSocPkt.dataBuffer.Length,
      SocketFlags.None,
      pfnWorkerCallBack,
      theSocPkt);
      //........

In the above code, the user-defined class SocketPacket is the most critical item. As you can see, an object of this class is the last parameter passed to the asynchronous function call BeginReceive(). This object can contain any information that you find useful; it can be used later, when you actually receive the data from the client. You send (1) the worker socket object and (2) the index number of the client packaged inside this object. You will retrieve them back when you actually receive the data from a particular client.

Given below is the definition of the SocketPacket class.

public class SocketPacket
{
   // Constructor that takes a Socket and a client number
   public SocketPacket(System.Net.Sockets.Socket socket,
                       int clientNumber)
   {
      m_currentSocket = socket;
      m_clientNumber  = clientNumber;
   }
   public System.Net.Sockets.Socket m_currentSocket;
   public int m_clientNumber;
   // Buffer to store the data sent by the client
   public byte[] dataBuffer = new byte[1024];
}

In the above code, the SocketPacket class contains the reference to a socket, a data buffer of size 1024 bytes, and a client number. This client number will be available when you actually start receiving data from a particular client. By using this client number, you can identify which client actually sent the data.

To demonstrate this in the example code, the server will echo back to the client (after converting to upper case) the received message, using the correct socket object.

How to Reply or Send Messages to Specific Clients

You might have figured out this already. This is very simple to implement. Because the SocketPacket object contains the reference to a particular worker socket, you just use that object to reply to the client. Additonally, you also could send any message to any particular client by using the worker socket object stored in the ArrayList.

How to Find when a Particular Client is Disconnected

This is a bit harder to address. There may be other elegant ways to do this, but here is a simple way.

When a client is disconnected, there will be a final call to the OnDataReceived() function. If nothing in particular is done, this call will throw a SocketException. What you can do here is to look inside this exception and see whether this was triggered by the “disconnection” of a client. For this, you will look at the error code inside the exception object and see whether it corresponds to 10054. If so, you will do the required action corresponding to the client disconnection. Here again, the SocketPacket object will give you the index number of the client that was disconnected.

catch(SocketException se)
{
   if(se.ErrorCode == 10054)    // Error code for Connection reset
                                // by peer
   {
      string msg = "Client " + socketData.m_clientNumber +
                   " Disconnected" + "\n";
      richTextBoxReceivedMsg.AppendText(msg);

      // Remove the reference to the worker socket of the closed
      // client so that this object will get garbage collected
      m_workerSocketList[socketData.m_clientNumber - 1] = null;
      UpdateClientList();
   }
   else
   {
      MessageBox.Show (se.Message );
   }
}

How to Get the List of All Connected Clients at Any Given Time

To show this, a dynamic list is displayed on the server GUI that will be updated (see the UpdateClientList() function) whenever a client is connected or disconnected.

Are Variables Safe in AsyncCallback Methods? What About Thread Synchronization?

This is a very valid question. For simplicity, I ignored this aspect in the first part of this article. Asynchronous programming using asynchronous delegates is just a matter of convenience. When you use asynchronous calls, you should be aware that, behind the scenes, you are actually using threads to achieve the asynchronous nature of these calls.

The following picture shows a simple illustration of the interplay of threads involved in this example.

In the above picture, the item labeled (1) is the main GUI thread that starts when you start the Server application. The thread labeled (2) starts whenever any client tries to connect to the socket. The thread labeled (3) spawns when there is any write activity by any one of the connected clients.

In the example code, the asynchronous functions OnClientConnect() and OnDataReceived() are called by threads other than the main GUI thread. Any other functions called inside these two functions are also invoked by threads other than the main GUI thread.

Threading issues to consider


  1. Shared variables

    Any shared variables that you modify inside the shared code mentioned above must be protected by synchronization structures. In this example, the shared variables you modify within the shared code are m_clientCount and m_workerSocketList.

    You can use very simple strategies to protect these variables. The m_clientCount variable is an integer variable and hence can be incremented by using the static method within the Interlocked class as shown below:

    // Now increment the client count for this client
    // in a thread safe manner
    Interlocked.Increment(ref m_clientCount);
    

    Similarly, you can protect the m_workerSocketList member variable from modification by multiple threads at the same time, by creating a Synchronized ArrayList as shown below:

    private System.Collections.ArrayList m_workerSocketList =
       ArrayList.Synchronized(new System.Collections.ArrayList());
    

  2. Modifying the GUI
  3. The main GUI thread actually owns the GUI controls. Hence, in production code, it is not recommended or advisable to access or modify any of the GUI controls by threads other than the main thread. When you need to update the GUI, you should make the main thread do it for you as shown in the following code:

    // This method could be called by either the main thread or
    // any of the worker threads
    private void AppendToRichEditControl(string msg)
    {
       // Check to see if this method is called from a thread
       // other than the one created the control
       if (InvokeRequired)
       {
          // We cannot update the GUI on this thread.
          // All GUI controls are to be updated by the main (GUI)
          // thread.
          // Hence, we will use the invoke method on the control
          // that will be called when the Main thread is free
          // Do UI update on UI thread
          object[] pList = {msg};
          richTextBoxReceivedMsg.BeginInvoke(new
             UpdateRichEditCallback(OnUpdateRichEdit), pList);
       }
       else
       {
       // This is the main thread which created this control,
       // hence update it directly
          OnUpdateRichEdit(msg);
       }
    }
    
    // This UpdateRichEdit will be run back on the UI thread
    // (using System.EventHandler signature so we don't
    // need to define a new delegate type here)
    private void OnUpdateRichEdit(string msg)
    {
       richTextBoxReceivedMsg.AppendText(msg);
    }
    

Acknowledgement

Part II of this article was developed to address the questions and comments I received from readers after publishing Part I of this article. The example programs used in Part I which are further enhanced and extended for Part II, are influenced by the article on Socket Programming in C# by Ashish Dhar.

Final Comments

Network programming is a very interesting topic. The C# language provides you with all the tools necessary to quickly develop networked applications. Compared to C++, Java and C# have a richer set of programming APIs, which will eliminate most of the complexities previously associated with network programming. This example incorporates all the features that the readers requested. Even then, use this example only as a learning tool. As you learn more about socket programming, you will realize the necessity to add thread synchronization constructs as well see the opportunities for further optimization, to solve the problem at your hand. Good luck with your learning and thanks for reading.

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read