GJM :异步Socket [转载]

原帖地址:http://blog.csdn.net/awinye/article/details/537264

原文作者:Awinye

目录(?)[-]

  1. 转载请原作者联系
  2. Overview of Socket in Net
  3. Consider and Discuss
  4. Implement Asynchronous Socket
    1. 1 AsyncCallback Method
    2. 2 Synchronous Method Thread
    3. 3 Synchronous Method Net ThreadPool
    4. 4 Synchronous Method Own ThreadPool
  5. My Asynchronous Socket Lib
    1. Motivation for This Article
    2. Requested Features Added
    3. Other Enhancements
    4. How to Support an Unlimited Number of Clients
    5. How to Find Which Client Sent a Particular Message
    6. How to Reply or Send Messages to Specific Clients
    7. How to Find when a Particular Client is Disconnected
    8. How to Get the List of All Connected Clients at Any Given Time
    9. Are Variables Safe in AsyncCallback Methods What About Thread Synchronization
      1. Threading issues to consider
    10. Acknowledgement
    11. Final Comments
    12. Objective
    13. The Difference Between Synchronous and Asynchronous Communication in Network Programming
    14. Socket Class
    15. Example Application
    16. Socket Server Implementation
    17. Socket Client Implementation
    18. LimitationsPossible Improvements
    19. Acknowledgement
      1. Update added on 03012005
    20. Objective
    21. The Difference Between Synchronous and Asynchronous Communication in Network Programming
    22. Socket Class
    23. Example Application
    24. Socket Server Implementation
    25. Socket Client Implementation
    26. LimitationsPossible Improvements
    27. Acknowledgement
      1. Update added on 03012005
    28. Motivation for This Article
    29. Requested Features Added
    30. Other Enhancements
    31. How to Support an Unlimited Number of Clients
    32. How to Find Which Client Sent a Particular Message
    33. How to Reply or Send Messages to Specific Clients
    34. How to Find when a Particular Client is Disconnected
    35. How to Get the List of All Connected Clients at Any Given Time
    36. Are Variables Safe in AsyncCallback Methods What About Thread Synchronization
      1. Threading issues to consider
    37. Acknowledgement
    38. Final Comments
    39. Objective
    40. The Difference Between Synchronous and Asynchronous Communication in Network Programming
    41. Socket Class
    42. Example Application
    43. Socket Server Implementation
    44. Socket Client Implementation
    45. LimitationsPossible Improvements
    46. Acknowledgement
      1. Update added on 03012005
    47. Objective
    48. The Difference Between Synchronous and Asynchronous Communication in Network Programming
    49. Socket Class
    50. Example Application
    51. Socket Server Implementation
    52. Socket Client Implementation
    53. LimitationsPossible Improvements
    54. Acknowledgement
      1. Update added on 03012005
    55. Motivation for This Article
    56. Requested Features Added
    57. Other Enhancements
    58. How to Support an Unlimited Number of Clients
    59. How to Find Which Client Sent a Particular Message
    60. How to Reply or Send Messages to Specific Clients
    61. How to Find when a Particular Client is Disconnected
    62. How to Get the List of All Connected Clients at Any Given Time
    63. Are Variables Safe in AsyncCallback Methods What About Thread Synchronization
      1. Threading issues to consider
    64. Acknowledgement
    65. Final Comments
    66. Objective
    67. The Difference Between Synchronous and Asynchronous Communication in Network Programming
    68. Socket Class
    69. Example Application
    70. Socket Server Implementation
    71. Socket Client Implementation
    72. LimitationsPossible Improvements
    73. Acknowledgement
      1. Update added on 03012005
    74. Objective
    75. The Difference Between Synchronous and Asynchronous Communication in Network Programming
    76. Socket Class
    77. Example Application
    78. Socket Server Implementation
    79. Socket Client Implementation
    80. LimitationsPossible Improvements
    81. Acknowledgement
      1. Update added on 03012005
    82. Motivation for This Article
    83. Requested Features Added
    84. Other Enhancements
    85. How to Support an Unlimited Number of Clients
    86. How to Find Which Client Sent a Particular Message
    87. How to Reply or Send Messages to Specific Clients
    88. How to Find when a Particular Client is Disconnected
    89. How to Get the List of All Connected Clients at Any Given Time
    90. Are Variables Safe in AsyncCallback Methods What About Thread Synchronization
      1. Threading issues to consider
    91. Acknowledgement
    92. Final Comments
    93. Objective
    94. The Difference Between Synchronous and Asynchronous Communication in Network Programming
    95. Socket Class
    96. Example Application
    97. Socket Server Implementation
    98. Socket Client Implementation
    99. LimitationsPossible Improvements
    100. Acknowledgement
      1. Update added on 03012005
    101. Objective
    102. The Difference Between Synchronous and Asynchronous Communication in Network Programming
    103. Socket Class
    104. Example Application
    105. Socket Server Implementation
    106. Socket Client Implementation
    107. LimitationsPossible Improvements
    108. Acknowledgement
      1. Update added on 03012005
 

转载请原作者联系

1.   Overview of Socket in .Net

如果做大型的分布式应用,且要求有很高的实时性,通常我们会使用TCP/IP协议来让client和server进行通信——传递命令和数据(比如XML Stream)。这个时候我们就需要使用异步socket了。.Net Framework提供了Socket类,此类对WinSock进行了比较好的包装,隐藏了很多细节,大大简化了我们需要做的工作;另外还提供了更高层次的抽象的类——TcpListener类和TcpClient类。而且,.Net Framework还提供了NetworkStream来简化读取和发送数据的工作。但是TcpListener、TcpClient和NetworkStream都是使用blocking call的方式,而这并不是我们想要的——我们需要异步的方式——高实时性要求我们必须使用异步Socket。再者,我们还要注意到TCP协议的特点——它不会帮我们区分消息的边界——比如我们分别发送了10K和20K字节的数据出去,到了接收端的缓冲区中,这两条消息将是30K字节的一大块数据——这就需要我们自己建立一个机制来区分不同的消息。相对的,UDP协议将会为我们做区分消息边界的工作,就省去了区分不同的消息的工作。所以我们在做使用TCP协议通信的程序的时候,我们通常需要先定一个自己的通信协议。另外,还有一些实现上的细节需要注意的:多线程、server如何支持连接无限数量(当然是有限资源范围内的无限)的client、如何确定client已经断开了socket连接、如何区别client socket连接和server如何给特定的client发送信息,是否需要Queue来暂存消息等等问题。

2.   Consider and Discuss

其实上面已经提到了一些需要考量的问题。我把我认为需要考虑的问题列举如下:

1、  需要适应的网络环境。比如局域网、广域网和Internet。Internet环境中通常需要考虑穿透防护墙问题,秘密通信问题(加密),而局域网环境中通常不需要考虑防护墙问题。

2、  Server支持连接无限量的client,且能区分开每个client和给特定的client发送消息。

3、  缓存消息。比如使用消息队列来存放消息。如果客户端数量非常大,那使用队列(能把消息存放到磁盘中)来保存消息(这些消息是需要业务模块去处理的)就有必要了,这样即使down机了也能恢复回去。

4、  Server和client的通信协议。这里的协议不是指TCP这样的协议,而是我们的应用中约定的协议,比如消息格式。

5、  如何使用线程,即如何管理线程来实现异步通信。

6、  如何确定client已经断开了socket连接。

3.   Implement Asynchronous Socket

就我自己的了解,实现异步Socket大致有四种方法:

1、  使用Socket类的异步方法。

2、  使用Socket类的同步方法,但自己创建Thread来实现异步通信。

3、  使用Socket类的同步方法,但使用.Net Framework的ThreadPool类来实现异步通信。

4、  使用Socket类的同步方法,但使用自己写的ThreadPool来实现异步通信。

下面就这几种方法分别说明之。

3.1. AsyncCallback Method

此种方法可能是大家用的比较多的一种方法,也是一种最简单的方法。MSDN中讲如何实现异步socket的时候就讲的是使用AsyncCallback Method——使用socket类提供的BeginXXX、EndXXX这样的方法。这样方法实际上都是使用了线程池——.net framework中的ThreadPool类。此类在一个Process中只能有一个实例,任何BeginXXX、EndXXX方法都使用了此类,默认最大线程数量是25个,当然也可以修改此限制,具体做法为请看这里。

下面是一些AsyncCallback Method的例子:

MSDN上的例子

CodeGuru上的例子

CodeProject上的例子

3.2. Synchronous Method & Thread

使用.net ThreadPool类的话,自己不能对Thread进行控制。而且此ThreadPool类有一些缺点(比如不适合于长时间执行某操作的场合)。这个时候我们可以直接创建Thread来实现异步,这个时候我们自己需要去做更多的工作——使用一个Thread去接收TCP连接,对于每个连接还需要开一个Thread去接收数据,另外还需要注意线程的同步、资源的释放和各种异常情况的处理;另外,性能也是一个必须重视的问题。

CodeProject上的例子

3.3. Synchronous Method & .Net ThreadPool

其实这么做和使用AsyncCallback Method实质上是一样的——都是使用了.Net ThreadPool。和Synchronous Method & Thread不同的就是不用自己去管理线程了,而把线程管理的复杂性交给ThreadPool类来做了,它为我们使用多线程提供了便利。而且,.Net ThreadPool充分利用CPU的时间片,提供了比较高的性能,也有人做了测试证明使用ThreadPool通常会获比直接创建和管理多个线程更为理想的性能。

3.4. Synchronous Method & Own ThreadPool

如果不想使用.Net ThreadPool类,那我们完全可以自己写一个ThreadPool。.Net ThreadPool有一些缺点,其中主要的几个为:

1、  一个Process中只能有一个实例,它在各个AppDomain是共享的。ThreadPool只提供了静态方法,不仅我们自己添加进去的WorkItem使用这个Pool,而且.net framework中那些BeginXXX、EndXXX之类的方法都会使用此Pool。

2、  所支持的Callback不能有返回值。WaitCallback只能带一个object类型的参数,没有任何返回值。

3、  不能方便地修改最小和最大线程数。ThreadPool最大线程数为25个,有的情况下可能不够用的。如果要修改限制还颇费周折——需要使用COM。

4、  不适合用在长期执行某任务的场合。我们常常需要做一个Service来提供不间断的服务(除非服务器down掉),但是使用ThreadPool并不合适。

我们自己写ThreadPool的话,可以使用Hashtable来存放Thread,再实现WorkItem和WorkItemQueue这样的类来排队Work Item,使用WaitHandle来做同步。早已经有人因不满.net ThreadPool而编写了自己的ThreadPool,个人认为比较好的有SmartThreadPool、XYThreadPool。

把对线程的管理交给ThreadPool后,我们就可以专注于通信方面的工作了。

4.   My Asynchronous Socket Lib

我实现的一个简单的TCP Asynchronous Socket Lib有如下特点:

1、  适合于局域网和Internet的网络环境。

2、  Server能接收无限量的client连接——当然受server资源的限制。

3、  Server会保持client的连接一段时间(timeout),以便于server向client发送消息。这样可以保证即使client端在防火墙内,server也能给client发送消息。

4、  使用Length-leading的消息格式。通常区分TCP消息边界有三种方式:(1)发送固定长度的消息(2)使用Length-leading的消息(3)使用消息分隔符。

5、  对于server,接收到的消息存放到queue中,并触发事件。即使server down掉,消息也不丢失。

6、  对于server,待发送的消息也存入queue中,并触发事件。

7、  使用一个Thread Pool(不是.net framework提供的ThreadPool)来管理线程。分别有线程去做接收连接请求、检测连接是否可用和有无消息、接收消息和检查超时的工作。

8、  提供很多事件,比如OnServerStart、OnNewClient、OnReceiveMessage、OnServerError和OnServerStop等等。

9、  检查client连接。Client自己断开了连接后,在server端从socket的Available属性并不能判断出来,可行的办法是使用Poll方法,然后看是否能peek到数据;或者接收数据的时候捕捉异常。就我自己实践的结果,使用Poll方法会有一些麻烦:我是将client连接进来的socket存放到Hashtable中的,但是调用了Poll方法后,会使此Hashtable被改变,从而导致迭代此Hashtable失败。CodeGuru上的例子介绍了使用异常的方法。

附codeguru的例子

Asynchronous Socket Programming in C#: Part II
An advanced C# socket program example with a single server and multiple simultanneous clients
Rating: 

  Jayan Nair (view profile)
October 6, 2005

Environment:  C#, Windows

 

 

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)

 

 

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 find which client sent a particular message
  4. How to reply or send messages to specific clients
  5. How to find when a particular client is disconnected
  6. How to get the list of all connected clients at any given time
  7. Are variables safe in AsyncCallback methods? What about thread synchronization? [Updated on 02/01/05]
How to support an unlimited number of clients
  • How to find which client sent a particular message
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

  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

    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.

About the Author
Jayan Nair is a Senior Software Engineer with 8+ years of experience working with cutting edge software technologies. Currently he is developing the next generation software applications for the telecommnunications testing industry. Jayan's passions: Object Oriented software design and developing reusable software components. His motto: "if the software you write is not reusable, you are not writing software, but hardware instead". Jayan finished his Masters degree in Computer Science from Virginia Tech, Blacksburg, VA. His expertise includes, C, C++, Java, J2EE, Visual Basic, C#, ASP.NET and distributed applications. He is also a Sun Certified Programmer for the Java Platform (SCPJ). You can contact him at [email protected].

Downloads

  • async_client_server_II_exe.zip - Windows executable files
  • async_client_server_II_src.zip - C# Source Code


Asynchronous Socket Programming in C#: Part I
Client-Server Example with Multiple Simultaneous Clients
Rating: 

  Jayan Nair (view profile)
October 6, 2005

 

Environment:  C#, Windows

 

 

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


(continued)

 

Featured Resources from the
Avaya IP Telephony and Mobility Center
WHITEPAPER : 
Driving Agility Through Business Communications Applications
Meet the growing demands for increased speed and precision in global business. 

CASE STUDY : 
B·A·R Honda Gains a Competitive Edge
Find out how an IP telephony solution has led to increased collaboration, reduced IT maintenance requirements, and a significant cost savings. 

WHITEPAPER : 
Migrating to Converged Networks and IP Telephony Applications 
Lower the risks associated with the migration to IP telephony and ensure that you are receiving the full mission-critical benefits of converged applications.

 

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 synchronousreceiving, 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


(Full Size Image)

Full Size Image)

The Socket Client application is implemented in the SocketClient class (file name SocketClient.cs). Compared to the server where you have a main Socket and an array of worker Sockets, here you only have a single Socket object (m_clientSocket).

The two important methods in Socket class used by the Socket Client application are the Connect() and BeginReceive() methods. Connect() is a synchronous method and is called to connect to a server that is listening for client connections. Because this call will succeed/fail immediately, depending on whether there is an active server listening or not at the specified IP and Port number, a synchronous method is okay for this purpose.

Once a connection is established, you call the BeginReceive() asynchronous function to wait for any socket write activity by the server. Here, if you call a synchronous method, the main thread on the client application will block and you will not be able to send any data to the server while the client is waiting for data from the server.

When there is any write activity on the socket from the server end, the internal thread started by BeginReceive() will invoke the callback function ("OnDataReceived()" in this case), which will take care of the further processing of the data written by the server.

When sending the data to the server, you just call the Send() method on the m_clientSocket object, which will synchronously write the data to the socket.

That is all there is for asynchronous socket communication using multiple clients.

Limitations/Possible Improvements

  • Up to 10 simultaneous clients are supported. You can easily modify and support unlimited number of clients by using a HashTable instead of an array.
  • For simplicity, when the server sends out a message, it is broadcast to all the connected clients. This could easily be modified to send messages to specific clients by using the Socket object pertaining to that particular client.
  • When a client is disconnected, proper action is not taken; the client count is not decremented. The correct way would be to reuse or release resources for other client connections.

Acknowledgement

Even though the content of this article is independently developed, the example program used is influenced by the article on Socket Programming in C# by Ashish Dhar.


Update added on 03/01/2005

For a more comprehensive example covering topics such as thread synchronization, please see Part II of this article.


About the Author
Jayan Nair is a Senior Software Engineer with 8+ years of experience working with cutting edge software technologies. Currently he is developing the next generation software applications for the telecommnunications testing industry. Jayan's passions: Object Oriented software design and developing reusable software components. His motto: "if the software you write is not reusable, you are not writing software, but hardware instead". Jayan finished his Masters degree in Computer Science from Virginia Tech, Blacksburg, VA. His expertise includes, C, C++, Java, J2EE, Visual Basic, C#, ASP.NET and distributed applications. He is also a Sun Certified Programmer for the Java Platform (SCPJ). You can contact him at [email protected].

Downloads

  • async_client_server_exe.zip - Windows Executable Files
  • async_client_server_src.zip - C# Source Code

 

 

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


(continued)

 

Featured Resources from the
Avaya IP Telephony and Mobility Center
WHITEPAPER : 
Driving Agility Through Business Communications Applications
Meet the growing demands for increased speed and precision in global business. 

CASE STUDY : 
B·A·R Honda Gains a Competitive Edge
Find out how an IP telephony solution has led to increased collaboration, reduced IT maintenance requirements, and a significant cost savings. 

WHITEPAPER : 
Migrating to Converged Networks and IP Telephony Applications 
Lower the risks associated with the migration to IP telephony and ensure that you are receiving the full mission-critical benefits of converged applications.

 

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 synchronousreceiving, 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


(Full Size Image)

Full Size Image)

The Socket Client application is implemented in the SocketClient class (file name SocketClient.cs). Compared to the server where you have a main Socket and an array of worker Sockets, here you only have a single Socket object (m_clientSocket).

The two important methods in Socket class used by the Socket Client application are the Connect() and BeginReceive() methods. Connect() is a synchronous method and is called to connect to a server that is listening for client connections. Because this call will succeed/fail immediately, depending on whether there is an active server listening or not at the specified IP and Port number, a synchronous method is okay for this purpose.

Once a connection is established, you call the BeginReceive() asynchronous function to wait for any socket write activity by the server. Here, if you call a synchronous method, the main thread on the client application will block and you will not be able to send any data to the server while the client is waiting for data from the server.

When there is any write activity on the socket from the server end, the internal thread started by BeginReceive() will invoke the callback function ("OnDataReceived()" in this case), which will take care of the further processing of the data written by the server.

When sending the data to the server, you just call the Send() method on the m_clientSocket object, which will synchronously write the data to the socket.

That is all there is for asynchronous socket communication using multiple clients.

Limitations/Possible Improvements

  • Up to 10 simultaneous clients are supported. You can easily modify and support unlimited number of clients by using a HashTable instead of an array.
  • For simplicity, when the server sends out a message, it is broadcast to all the connected clients. This could easily be modified to send messages to specific clients by using the Socket object pertaining to that particular client.
  • When a client is disconnected, proper action is not taken; the client count is not decremented. The correct way would be to reuse or release resources for other client connections.

Acknowledgement

Even though the content of this article is independently developed, the example program used is influenced by the article on Socket Programming in C# by Ashish Dhar.


Update added on 03/01/2005

For a more comprehensive example covering topics such as thread synchronization, please see Part II of this article.


About the Author
Jayan Nair is a Senior Software Engineer with 8+ years of experience working with cutting edge software technologies. Currently he is developing the next generation software applications for the telecommnunications testing industry. Jayan's passions: Object Oriented software design and developing reusable software components. His motto: "if the software you write is not reusable, you are not writing software, but hardware instead". Jayan finished his Masters degree in Computer Science from Virginia Tech, Blacksburg, VA. His expertise includes, C, C++, Java, J2EE, Visual Basic, C#, ASP.NET and distributed applications. He is also a Sun Certified Programmer for the Java Platform (SCPJ). You can contact him at [email protected].

Downloads

  • async_client_server_exe.zip - Windows Executable Files
  • async_client_server_src.zip - C# Source Code

 

 

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)

 

 

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 find which client sent a particular message
  4. How to reply or send messages to specific clients
  5. How to find when a particular client is disconnected
  6. How to get the list of all connected clients at any given time
  7. Are variables safe in AsyncCallback methods? What about thread synchronization? [Updated on 02/01/05]
How to support an unlimited number of clients
  • How to find which client sent a particular message
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

  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

    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.

About the Author
Jayan Nair is a Senior Software Engineer with 8+ years of experience working with cutting edge software technologies. Currently he is developing the next generation software applications for the telecommnunications testing industry. Jayan's passions: Object Oriented software design and developing reusable software components. His motto: "if the software you write is not reusable, you are not writing software, but hardware instead". Jayan finished his Masters degree in Computer Science from Virginia Tech, Blacksburg, VA. His expertise includes, C, C++, Java, J2EE, Visual Basic, C#, ASP.NET and distributed applications. He is also a Sun Certified Programmer for the Java Platform (SCPJ). You can contact him at [email protected].

Downloads

  • async_client_server_II_exe.zip - Windows executable files
  • async_client_server_II_src.zip - C# Source Code


Asynchronous Socket Programming in C#: Part I
Client-Server Example with Multiple Simultaneous Clients
Rating: 

  Jayan Nair (view profile)
October 6, 2005

 

Environment:  C#, Windows

 

 

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


(continued)

 

Featured Resources from the
Avaya IP Telephony and Mobility Center
WHITEPAPER : 
Driving Agility Through Business Communications Applications
Meet the growing demands for increased speed and precision in global business. 

CASE STUDY : 
B·A·R Honda Gains a Competitive Edge
Find out how an IP telephony solution has led to increased collaboration, reduced IT maintenance requirements, and a significant cost savings. 

WHITEPAPER : 
Migrating to Converged Networks and IP Telephony Applications 
Lower the risks associated with the migration to IP telephony and ensure that you are receiving the full mission-critical benefits of converged applications.

 

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 synchronousreceiving, 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


(Full Size Image)

Full Size Image)

The Socket Client application is implemented in the SocketClient class (file name SocketClient.cs). Compared to the server where you have a main Socket and an array of worker Sockets, here you only have a single Socket object (m_clientSocket).

The two important methods in Socket class used by the Socket Client application are the Connect() and BeginReceive() methods. Connect() is a synchronous method and is called to connect to a server that is listening for client connections. Because this call will succeed/fail immediately, depending on whether there is an active server listening or not at the specified IP and Port number, a synchronous method is okay for this purpose.

Once a connection is established, you call the BeginReceive() asynchronous function to wait for any socket write activity by the server. Here, if you call a synchronous method, the main thread on the client application will block and you will not be able to send any data to the server while the client is waiting for data from the server.

When there is any write activity on the socket from the server end, the internal thread started by BeginReceive() will invoke the callback function ("OnDataReceived()" in this case), which will take care of the further processing of the data written by the server.

When sending the data to the server, you just call the Send() method on the m_clientSocket object, which will synchronously write the data to the socket.

That is all there is for asynchronous socket communication using multiple clients.

Limitations/Possible Improvements

  • Up to 10 simultaneous clients are supported. You can easily modify and support unlimited number of clients by using a HashTable instead of an array.
  • For simplicity, when the server sends out a message, it is broadcast to all the connected clients. This could easily be modified to send messages to specific clients by using the Socket object pertaining to that particular client.
  • When a client is disconnected, proper action is not taken; the client count is not decremented. The correct way would be to reuse or release resources for other client connections.

Acknowledgement

Even though the content of this article is independently developed, the example program used is influenced by the article on Socket Programming in C# by Ashish Dhar.


Update added on 03/01/2005

For a more comprehensive example covering topics such as thread synchronization, please see Part II of this article.


About the Author
Jayan Nair is a Senior Software Engineer with 8+ years of experience working with cutting edge software technologies. Currently he is developing the next generation software applications for the telecommnunications testing industry. Jayan's passions: Object Oriented software design and developing reusable software components. His motto: "if the software you write is not reusable, you are not writing software, but hardware instead". Jayan finished his Masters degree in Computer Science from Virginia Tech, Blacksburg, VA. His expertise includes, C, C++, Java, J2EE, Visual Basic, C#, ASP.NET and distributed applications. He is also a Sun Certified Programmer for the Java Platform (SCPJ). You can contact him at [email protected].

Downloads

  • async_client_server_exe.zip - Windows Executable Files
  • async_client_server_src.zip - C# Source Code

 

 

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


(continued)

 

Featured Resources from the
Avaya IP Telephony and Mobility Center
WHITEPAPER : 
Driving Agility Through Business Communications Applications
Meet the growing demands for increased speed and precision in global business. 

CASE STUDY : 
B·A·R Honda Gains a Competitive Edge
Find out how an IP telephony solution has led to increased collaboration, reduced IT maintenance requirements, and a significant cost savings. 

WHITEPAPER : 
Migrating to Converged Networks and IP Telephony Applications 
Lower the risks associated with the migration to IP telephony and ensure that you are receiving the full mission-critical benefits of converged applications.

 

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 synchronousreceiving, 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


(Full Size Image)

Full Size Image)

The Socket Client application is implemented in the SocketClient class (file name SocketClient.cs). Compared to the server where you have a main Socket and an array of worker Sockets, here you only have a single Socket object (m_clientSocket).

The two important methods in Socket class used by the Socket Client application are the Connect() and BeginReceive() methods. Connect() is a synchronous method and is called to connect to a server that is listening for client connections. Because this call will succeed/fail immediately, depending on whether there is an active server listening or not at the specified IP and Port number, a synchronous method is okay for this purpose.

Once a connection is established, you call the BeginReceive() asynchronous function to wait for any socket write activity by the server. Here, if you call a synchronous method, the main thread on the client application will block and you will not be able to send any data to the server while the client is waiting for data from the server.

When there is any write activity on the socket from the server end, the internal thread started by BeginReceive() will invoke the callback function ("OnDataReceived()" in this case), which will take care of the further processing of the data written by the server.

When sending the data to the server, you just call the Send() method on the m_clientSocket object, which will synchronously write the data to the socket.

That is all there is for asynchronous socket communication using multiple clients.

Limitations/Possible Improvements

  • Up to 10 simultaneous clients are supported. You can easily modify and support unlimited number of clients by using a HashTable instead of an array.
  • For simplicity, when the server sends out a message, it is broadcast to all the connected clients. This could easily be modified to send messages to specific clients by using the Socket object pertaining to that particular client.
  • When a client is disconnected, proper action is not taken; the client count is not decremented. The correct way would be to reuse or release resources for other client connections.

Acknowledgement

Even though the content of this article is independently developed, the example program used is influenced by the article on Socket Programming in C# by Ashish Dhar.


Update added on 03/01/2005

For a more comprehensive example covering topics such as thread synchronization, please see Part II of this article.


About the Author
Jayan Nair is a Senior Software Engineer with 8+ years of experience working with cutting edge software technologies. Currently he is developing the next generation software applications for the telecommnunications testing industry. Jayan's passions: Object Oriented software design and developing reusable software components. His motto: "if the software you write is not reusable, you are not writing software, but hardware instead". Jayan finished his Masters degree in Computer Science from Virginia Tech, Blacksburg, VA. His expertise includes, C, C++, Java, J2EE, Visual Basic, C#, ASP.NET and distributed applications. He is also a Sun Certified Programmer for the Java Platform (SCPJ). You can contact him at [email protected].

Downloads

  • async_client_server_exe.zip - Windows Executable Files
  • async_client_server_src.zip - C# Source Code

 

Environment:  C#, Windows

 

 

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)

 

 

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 find which client sent a particular message
  4. How to reply or send messages to specific clients
  5. How to find when a particular client is disconnected
  6. How to get the list of all connected clients at any given time
  7. Are variables safe in AsyncCallback methods? What about thread synchronization? [Updated on 02/01/05]
How to support an unlimited number of clients
  • How to find which client sent a particular message
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

  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

    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.

About the Author
Jayan Nair is a Senior Software Engineer with 8+ years of experience working with cutting edge software technologies. Currently he is developing the next generation software applications for the telecommnunications testing industry. Jayan's passions: Object Oriented software design and developing reusable software components. His motto: "if the software you write is not reusable, you are not writing software, but hardware instead". Jayan finished his Masters degree in Computer Science from Virginia Tech, Blacksburg, VA. His expertise includes, C, C++, Java, J2EE, Visual Basic, C#, ASP.NET and distributed applications. He is also a Sun Certified Programmer for the Java Platform (SCPJ). You can contact him at [email protected].

Downloads

  • async_client_server_II_exe.zip - Windows executable files
  • async_client_server_II_src.zip - C# Source Code


Asynchronous Socket Programming in C#: Part I
Client-Server Example with Multiple Simultaneous Clients
Rating: 

  Jayan Nair (view profile)
October 6, 2005

 

Environment:  C#, Windows

 

 

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


(continued)

 

Featured Resources from the
Avaya IP Telephony and Mobility Center
WHITEPAPER : 
Driving Agility Through Business Communications Applications
Meet the growing demands for increased speed and precision in global business. 

CASE STUDY : 
B·A·R Honda Gains a Competitive Edge
Find out how an IP telephony solution has led to increased collaboration, reduced IT maintenance requirements, and a significant cost savings. 

WHITEPAPER : 
Migrating to Converged Networks and IP Telephony Applications 
Lower the risks associated with the migration to IP telephony and ensure that you are receiving the full mission-critical benefits of converged applications.

 

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 synchronousreceiving, 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


(Full Size Image)

Full Size Image)

The Socket Client application is implemented in the SocketClient class (file name SocketClient.cs). Compared to the server where you have a main Socket and an array of worker Sockets, here you only have a single Socket object (m_clientSocket).

The two important methods in Socket class used by the Socket Client application are the Connect() and BeginReceive() methods. Connect() is a synchronous method and is called to connect to a server that is listening for client connections. Because this call will succeed/fail immediately, depending on whether there is an active server listening or not at the specified IP and Port number, a synchronous method is okay for this purpose.

Once a connection is established, you call the BeginReceive() asynchronous function to wait for any socket write activity by the server. Here, if you call a synchronous method, the main thread on the client application will block and you will not be able to send any data to the server while the client is waiting for data from the server.

When there is any write activity on the socket from the server end, the internal thread started by BeginReceive() will invoke the callback function ("OnDataReceived()" in this case), which will take care of the further processing of the data written by the server.

When sending the data to the server, you just call the Send() method on the m_clientSocket object, which will synchronously write the data to the socket.

That is all there is for asynchronous socket communication using multiple clients.

Limitations/Possible Improvements

  • Up to 10 simultaneous clients are supported. You can easily modify and support unlimited number of clients by using a HashTable instead of an array.
  • For simplicity, when the server sends out a message, it is broadcast to all the connected clients. This could easily be modified to send messages to specific clients by using the Socket object pertaining to that particular client.
  • When a client is disconnected, proper action is not taken; the client count is not decremented. The correct way would be to reuse or release resources for other client connections.

Acknowledgement

Even though the content of this article is independently developed, the example program used is influenced by the article on Socket Programming in C# by Ashish Dhar.


Update added on 03/01/2005

For a more comprehensive example covering topics such as thread synchronization, please see Part II of this article.


About the Author
Jayan Nair is a Senior Software Engineer with 8+ years of experience working with cutting edge software technologies. Currently he is developing the next generation software applications for the telecommnunications testing industry. Jayan's passions: Object Oriented software design and developing reusable software components. His motto: "if the software you write is not reusable, you are not writing software, but hardware instead". Jayan finished his Masters degree in Computer Science from Virginia Tech, Blacksburg, VA. His expertise includes, C, C++, Java, J2EE, Visual Basic, C#, ASP.NET and distributed applications. He is also a Sun Certified Programmer for the Java Platform (SCPJ). You can contact him at [email protected].

Downloads

  • async_client_server_exe.zip - Windows Executable Files
  • async_client_server_src.zip - C# Source Code

 

 

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


(continued)

 

Featured Resources from the
Avaya IP Telephony and Mobility Center
WHITEPAPER : 
Driving Agility Through Business Communications Applications
Meet the growing demands for increased speed and precision in global business. 

CASE STUDY : 
B·A·R Honda Gains a Competitive Edge
Find out how an IP telephony solution has led to increased collaboration, reduced IT maintenance requirements, and a significant cost savings. 

WHITEPAPER : 
Migrating to Converged Networks and IP Telephony Applications 
Lower the risks associated with the migration to IP telephony and ensure that you are receiving the full mission-critical benefits of converged applications.

 

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 synchronousreceiving, 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


(Full Size Image)

Full Size Image)

The Socket Client application is implemented in the SocketClient class (file name SocketClient.cs). Compared to the server where you have a main Socket and an array of worker Sockets, here you only have a single Socket object (m_clientSocket).

The two important methods in Socket class used by the Socket Client application are the Connect() and BeginReceive() methods. Connect() is a synchronous method and is called to connect to a server that is listening for client connections. Because this call will succeed/fail immediately, depending on whether there is an active server listening or not at the specified IP and Port number, a synchronous method is okay for this purpose.

Once a connection is established, you call the BeginReceive() asynchronous function to wait for any socket write activity by the server. Here, if you call a synchronous method, the main thread on the client application will block and you will not be able to send any data to the server while the client is waiting for data from the server.

When there is any write activity on the socket from the server end, the internal thread started by BeginReceive() will invoke the callback function ("OnDataReceived()" in this case), which will take care of the further processing of the data written by the server.

When sending the data to the server, you just call the Send() method on the m_clientSocket object, which will synchronously write the data to the socket.

That is all there is for asynchronous socket communication using multiple clients.

Limitations/Possible Improvements

  • Up to 10 simultaneous clients are supported. You can easily modify and support unlimited number of clients by using a HashTable instead of an array.
  • For simplicity, when the server sends out a message, it is broadcast to all the connected clients. This could easily be modified to send messages to specific clients by using the Socket object pertaining to that particular client.
  • When a client is disconnected, proper action is not taken; the client count is not decremented. The correct way would be to reuse or release resources for other client connections.

Acknowledgement

Even though the content of this article is independently developed, the example program used is influenced by the article on Socket Programming in C# by Ashish Dhar.


Update added on 03/01/2005

For a more comprehensive example covering topics such as thread synchronization, please see Part II of this article.


About the Author
Jayan Nair is a Senior Software Engineer with 8+ years of experience working with cutting edge software technologies. Currently he is developing the next generation software applications for the telecommnunications testing industry. Jayan's passions: Object Oriented software design and developing reusable software components. His motto: "if the software you write is not reusable, you are not writing software, but hardware instead". Jayan finished his Masters degree in Computer Science from Virginia Tech, Blacksburg, VA. His expertise includes, C, C++, Java, J2EE, Visual Basic, C#, ASP.NET and distributed applications. He is also a Sun Certified Programmer for the Java Platform (SCPJ). You can contact him at [email protected].

Downloads

  • async_client_server_exe.zip - Windows Executable Files
  • async_client_server_src.zip - C# Source Code

 

 

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)

 

 

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 find which client sent a particular message
  4. How to reply or send messages to specific clients
  5. How to find when a particular client is disconnected
  6. How to get the list of all connected clients at any given time
  7. Are variables safe in AsyncCallback methods? What about thread synchronization? [Updated on 02/01/05]
How to support an unlimited number of clients
  • How to find which client sent a particular message
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

  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

    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.

About the Author
Jayan Nair is a Senior Software Engineer with 8+ years of experience working with cutting edge software technologies. Currently he is developing the next generation software applications for the telecommnunications testing industry. Jayan's passions: Object Oriented software design and developing reusable software components. His motto: "if the software you write is not reusable, you are not writing software, but hardware instead". Jayan finished his Masters degree in Computer Science from Virginia Tech, Blacksburg, VA. His expertise includes, C, C++, Java, J2EE, Visual Basic, C#, ASP.NET and distributed applications. He is also a Sun Certified Programmer for the Java Platform (SCPJ). You can contact him at [email protected].

Downloads

  • async_client_server_II_exe.zip - Windows executable files
  • async_client_server_II_src.zip - C# Source Code


Asynchronous Socket Programming in C#: Part I
Client-Server Example with Multiple Simultaneous Clients
Rating: 

  Jayan Nair (view profile)
October 6, 2005

 

Environment:  C#, Windows

 

 

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


(continued)

 

Featured Resources from the
Avaya IP Telephony and Mobility Center
WHITEPAPER : 
Driving Agility Through Business Communications Applications
Meet the growing demands for increased speed and precision in global business. 

CASE STUDY : 
B·A·R Honda Gains a Competitive Edge
Find out how an IP telephony solution has led to increased collaboration, reduced IT maintenance requirements, and a significant cost savings. 

WHITEPAPER : 
Migrating to Converged Networks and IP Telephony Applications 
Lower the risks associated with the migration to IP telephony and ensure that you are receiving the full mission-critical benefits of converged applications.

 

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 synchronousreceiving, 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


(Full Size Image)

Full Size Image)

The Socket Client application is implemented in the SocketClient class (file name SocketClient.cs). Compared to the server where you have a main Socket and an array of worker Sockets, here you only have a single Socket object (m_clientSocket).

The two important methods in Socket class used by the Socket Client application are the Connect() and BeginReceive() methods. Connect() is a synchronous method and is called to connect to a server that is listening for client connections. Because this call will succeed/fail immediately, depending on whether there is an active server listening or not at the specified IP and Port number, a synchronous method is okay for this purpose.

Once a connection is established, you call the BeginReceive() asynchronous function to wait for any socket write activity by the server. Here, if you call a synchronous method, the main thread on the client application will block and you will not be able to send any data to the server while the client is waiting for data from the server.

When there is any write activity on the socket from the server end, the internal thread started by BeginReceive() will invoke the callback function ("OnDataReceived()" in this case), which will take care of the further processing of the data written by the server.

When sending the data to the server, you just call the Send() method on the m_clientSocket object, which will synchronously write the data to the socket.

That is all there is for asynchronous socket communication using multiple clients.

Limitations/Possible Improvements

  • Up to 10 simultaneous clients are supported. You can easily modify and support unlimited number of clients by using a HashTable instead of an array.
  • For simplicity, when the server sends out a message, it is broadcast to all the connected clients. This could easily be modified to send messages to specific clients by using the Socket object pertaining to that particular client.
  • When a client is disconnected, proper action is not taken; the client count is not decremented. The correct way would be to reuse or release resources for other client connections.

Acknowledgement

Even though the content of this article is independently developed, the example program used is influenced by the article on Socket Programming in C# by Ashish Dhar.


Update added on 03/01/2005

For a more comprehensive example covering topics such as thread synchronization, please see Part II of this article.


About the Author
Jayan Nair is a Senior Software Engineer with 8+ years of experience working with cutting edge software technologies. Currently he is developing the next generation software applications for the telecommnunications testing industry. Jayan's passions: Object Oriented software design and developing reusable software components. His motto: "if the software you write is not reusable, you are not writing software, but hardware instead". Jayan finished his Masters degree in Computer Science from Virginia Tech, Blacksburg, VA. His expertise includes, C, C++, Java, J2EE, Visual Basic, C#, ASP.NET and distributed applications. He is also a Sun Certified Programmer for the Java Platform (SCPJ). You can contact him at [email protected].

Downloads

  • async_client_server_exe.zip - Windows Executable Files
  • async_client_server_src.zip - C# Source Code

 

 

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


(continued)

 

Featured Resources from the
Avaya IP Telephony and Mobility Center
WHITEPAPER : 
Driving Agility Through Business Communications Applications
Meet the growing demands for increased speed and precision in global business. 

CASE STUDY : 
B·A·R Honda Gains a Competitive Edge
Find out how an IP telephony solution has led to increased collaboration, reduced IT maintenance requirements, and a significant cost savings. 

WHITEPAPER : 
Migrating to Converged Networks and IP Telephony Applications 
Lower the risks associated with the migration to IP telephony and ensure that you are receiving the full mission-critical benefits of converged applications.

 

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 synchronousreceiving, 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


(Full Size Image)

Full Size Image)

The Socket Client application is implemented in the SocketClient class (file name SocketClient.cs). Compared to the server where you have a main Socket and an array of worker Sockets, here you only have a single Socket object (m_clientSocket).

The two important methods in Socket class used by the Socket Client application are the Connect() and BeginReceive() methods. Connect() is a synchronous method and is called to connect to a server that is listening for client connections. Because this call will succeed/fail immediately, depending on whether there is an active server listening or not at the specified IP and Port number, a synchronous method is okay for this purpose.

Once a connection is established, you call the BeginReceive() asynchronous function to wait for any socket write activity by the server. Here, if you call a synchronous method, the main thread on the client application will block and you will not be able to send any data to the server while the client is waiting for data from the server.

When there is any write activity on the socket from the server end, the internal thread started by BeginReceive() will invoke the callback function ("OnDataReceived()" in this case), which will take care of the further processing of the data written by the server.

When sending the data to the server, you just call the Send() method on the m_clientSocket object, which will synchronously write the data to the socket.

That is all there is for asynchronous socket communication using multiple clients.

Limitations/Possible Improvements

  • Up to 10 simultaneous clients are supported. You can easily modify and support unlimited number of clients by using a HashTable instead of an array.
  • For simplicity, when the server sends out a message, it is broadcast to all the connected clients. This could easily be modified to send messages to specific clients by using the Socket object pertaining to that particular client.
  • When a client is disconnected, proper action is not taken; the client count is not decremented. The correct way would be to reuse or release resources for other client connections.

Acknowledgement

Even though the content of this article is independently developed, the example program used is influenced by the article on Socket Programming in C# by Ashish Dhar.


Update added on 03/01/2005

For a more comprehensive example covering topics such as thread synchronization, please see Part II of this article.


About the Author
Jayan Nair is a Senior Software Engineer with 8+ years of experience working with cutting edge software technologies. Currently he is developing the next generation software applications for the telecommnunications testing industry. Jayan's passions: Object Oriented software design and developing reusable software components. His motto: "if the software you write is not reusable, you are not writing software, but hardware instead". Jayan finished his Masters degree in Computer Science from Virginia Tech, Blacksburg, VA. His expertise includes, C, C++, Java, J2EE, Visual Basic, C#, ASP.NET and distributed applications. He is also a Sun Certified Programmer for the Java Platform (SCPJ). You can contact him at [email protected].

 

Downloads

  • async_client_server_exe.zip - Windows Executable Files
  • async_client_server_src.zip - C# Source Code

你可能感兴趣的:(GJM :异步Socket [转载])