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
(continued)
 |
Web Devs: Moonlight as a Game Developer and Win Cool Prizes by Accepting the RIA Run Challenge
Now, your mission--should you choose to accept: Take your shot at gaming stardom if you think you might have what it takes to build a cool RIA game and you could win an Xbox 360 or other fabulous prizes. Hurry! You only have until May 15, 2008 to enter.
»
Article: Leveraging Your Flash Development with Silverlight
You're not giving up Flash any time soon (and we don't blame you.) But if you could get your Flash application working in Silverlight, why wouldn't you? We show you the tools and techniques required to have your rockin' Flash application rolled for Silverlight. Learn more here.
»
Article: What Does it Take to Build the Best RIA?
With the proliferation of Rich Interactive Application (RIA) platform choices out there, you no longer have to take a one-size-fits-all approach to developing your next RIA application. Knowing the strengths (and weaknesses) of each platform can help you to decide the best RIA for your next application.
»
|
 |
This example includes modifications to support the following features:
- How to support an unlimited number of clients
- How to find which client sent a particular message
- How to reply or send messages to specific clients
- How to find when a particular client is disconnected
- How to get the list of all connected clients at any given time
- Are variables safe in AsyncCallback methods? What about thread synchronization? [Updated on 02/01/05]
Other Enhancements
- On the server and client code, the receive buffer size is increased to 1024 instead of a single byte for more efficiency.
- 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.
private System.Collections.ArrayList m_workerSocketList =
new System.Collections.ArrayList();
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
{
Socket workerSocket = m_mainSocket.EndAccept (asyn);
++m_clientCount;
m_workerSocketList.Add(workerSocket);
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 )
{
pfnWorkerCallBack = new AsyncCallback (OnDataReceived);
}
SocketPacket theSocPkt = new SocketPacket (soc, clientNumber);
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
{
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;
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)
{
string msg = "Client " + socketData.m_clientNumber +
" Disconnected" + "\n";
richTextBoxReceivedMsg.AppendText(msg);
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
- Shared variables