Translation: How to use the SocketAsyncEventArgs class

Reprinted from: http://blog.csdn.net/hulihui/article/details/3244520

  • Original text: How to use the SocketAsyncEventArgs class. by Marcos Hidalgo Nunes
  • Download client – 4.09 KB
  • Download server – 7.5 KB

Introduction

Connecting to .NET Framework 3.5″) (see part of the content at the end of the text
Translation Notes – Translator’s Note).

Background

Translation Note – Translator’s Note), thereby reducing object allocation and garbage collection work.

Use code

Sample program on MSDN, but the article is missing something: the AsyncUserToken class. I think this class should expose a Socket property, which corresponds to the Socket that performs I/O operations. After some time I realized that this class is not necessary because the property UserToken is an Object and it can accept anything. In the following modification method, a Socket instance is directly used as UserToken.

 // Handle Socket listener reception.<br> private void ProcessAccept(SocketAsyncEventArgs e)<br> {<!-- --><br>     if (e.BytesTransferred > 0)<br>     {<!-- --><br>         Interlocked.Increment(ref numConnectedSockets);<br>         Console.WriteLine( "Client connection accepted. "<br>                 "There are {0} clients connected to the server",<br>                 numConnectedSockets);<br>     }<br><br>     // Get the accepted client connection and assign it to the UserToken of the ReadEventArg object.<br>     SocketAsyncEventArgs readEventArgs = readWritePool.Pop();<br>     readEventArgs.UserToken = e.AcceptSocket;<br><br>     // Once the client is connected, submit a connection accept.<br>     Boolean willRaiseEvent = e.AcceptSocket.ReceiveAsync(readEventArgs);<br>     if (!willRaiseEvent)<br>     {<!-- --><br>         ProcessReceive(readEventArgs);<br>     }<br><br>     //Accept the next connection request.<br>     StartAccept(e);<br> }<br><br> // This method is called when an asynchronous receive operation completes.<br> // If the remote host closes the connection, the Socket is also closed.<br> //If data is received, return to client.<br> private void ProcessReceive(SocketAsyncEventArgs e)<br> {<!-- --><br>     // Check if the remote host has closed the connection.<br>     if (e.BytesTransferred > 0)<br>     {<!-- --><br>         if (e.SocketError == SocketError.Success)<br>         {<!-- --><br>             Socket s = e.UserToken as Socket;<br><br>             Int32 bytesTransferred = e.BytesTransferred;<br><br>             // Get the received message from the listener.<br>             String received = Encoding.ASCII.GetString(e.Buffer,<br>                               e.Offset, bytesTransferred);<br><br>             // Increase the total number of bytes received by the server.<br>             Interlocked.Add(ref totalBytesRead, bytesTransferred);<br>             Console.WriteLine("Received: /"{0}/". The server has read" +<br>                               " a total of {1} bytes.", received,<br>                               totalBytesRead);<br><br>             //Format the data and send it back to the client.<br>             Byte[] sendBuffer =<br>               Encoding.ASCII.GetBytes("Returning " + received);<br><br>             //Set the buffer sent back to the client.<br>             e.SetBuffer(sendBuffer, 0, sendBuffer.Length);<br>             Boolean willRaiseEvent = s.SendAsync(e);<br>             if (!willRaiseEvent)<br>             {<!-- --><br>                 ProcessSend(e);<br>             }<br>         }<br>         else<br>         {<!-- --><br>             CloseClientSocket(e);<br>         }<br>     }<br> }<br><br> // This method is called when the asynchronous send operation is completed.<br> // This method starts another receive operation when the Socket reads any additional data from the client.<br> private void ProcessSend(SocketAsyncEventArgs e)<br> {<!-- --><br>     if (e.SocketError == SocketError.Success)<br>     {<!-- --><br>         // Complete sending data back to the client.<br>         Socket s = e.UserToken as Socket;<br>         // Read the next chunk of data sent from the sending client.<br>         Boolean willRaiseEvent = s.ReceiveAsync(e);<br>         if (!willRaiseEvent)<br>         {<!-- --><br>             ProcessReceive(e);<br>         }<br>     }<br>     else<br>     {<!-- --><br>         CloseClientSocket(e);<br>     }<br> }</code>I modified the code on how the listener receives the message - instead of simply posting it back to the client (see ProcessReceive method). In the sample program, I use the properties Buffer, Offset and BytesTransfered to receive messages, and the SetBuffer method returns the modified message to the client.
 // Start the server and start listening for incoming connection requests.<br> internal void Start(Object data)<br> {<!-- --><br>     Int32 port = (Int32)data;<br><br>     // Get host related information.<br>     IPAddress[] addressList =<br>            Dns.GetHostEntry(Environment.MachineName).AddressList;<br>     // Get the endpoint required by the listener.<br>     IPEndPoint localEndPoint =<br>            new IPEndPoint(addressList[addressList.Length - 1], port);<br><br>     // Create a Socket that listens for incoming connections.<br>     this.listenSocket = new Socket(localEndPoint.AddressFamily,<br>                         SocketType.Stream, ProtocolType.Tcp);<br><br>     if (localEndPoint.AddressFamily == AddressFamily.InterNetworkV6)<br>     {<!-- --><br>         //Set the dual mode (IPv4 and IPv6) of the Socket listener.<br>         // 27 is equivalent to IPV6_V6ONLY Socket<br>         //The following options in the Winsock fragment,<br>         // According to Creating IP Agnostic Applications - Part 2 (Dual Mode Sockets)<br>         // Creating IP-Agnostic Applications - Part 2 (Dual-Mode Sockets)<br>         <br>         this.listenSocket.SetSocketOption(SocketOptionLevel.IPv6,<br>                                          (SocketOptionName)27, false);<br>         this.listenSocket.Bind(new IPEndPoint(IPAddress.IPv6Any,<br>                                localEndPoint.Port));<br>     }<br>     else<br>     {<!-- --><br>         // Socket is associated with the local endpoint.<br>         this.listenSocket.Bind(localEndPoint);<br>     }<br><br>     // Start a server with a maximum number of waiting connections in the listening queue of 100.<br>     this.listenSocket.Listen(100);<br><br>     //Submit a receiving task that listens to the Socket.<br>     this.StartAccept(null);<br><br>     mutex.WaitOne();<br> }<br><br> // Stop the server.<br> internal void Stop()<br> {<!-- --><br>     mutex.ReleaseMutex();<br> }</code>Now that we have a Socket server, the next step is to create a Socket client using the SocketAsyncEventArgs class. Although MSDN says that this class is specifically designed for web server applications, there is no restriction on the use of APM in client code. The sample code of the SocketClient class is given below: 
 using System;<br> using System.Net;<br> using System.Net.Sockets;<br> using System.Text;<br> using System.Threading;<br><br> namespace SocketAsyncClient<br> {<!-- --><br>     // Implement the connection logic of the Socket client.<br>     internal sealed class SocketClient: IDisposable<br>     {<!-- --><br>         // Socket operation constants.<br>         private const Int32 ReceiveOperation = 1, SendOperation = 0;<br><br>         // Socket used to send/receive messages.<br>         private Socket clientSocket;<br><br>         //Socket connection flag.<br>         private Boolean connected = false;<br><br>         //Listener endpoint.<br>         private IPEndPoint hostEndPoint;<br><br>         // Trigger connection.<br>         private static AutoResetEvent autoConnectEvent =<br>                               new AutoResetEvent(false);<br><br>         // Trigger send/receive operations.<br>         private static AutoResetEvent[]<br>                 autoSendReceiveEvents = new AutoResetEvent[]<br>         {<!-- --><br>             new AutoResetEvent(false),<br>             new AutoResetEvent(false)<br>         };<br><br>         // Create an uninitialized client instance.<br>         // Starting the transmit/receive process will call the Connect method, followed by the SendReceive method.<br>         internal SocketClient(String hostName, Int32 port)<br>         {<!-- --><br>             // Get information about the host.<br>             IPHostEntry host = Dns.GetHostEntry(hostName);<br><br>             // Host address.<br>             IPAddress[] addressList = host.AddressList;<br><br>             // Instantiate the endpoint and Socket.<br>             hostEndPoint = new IPEndPoint(addressList[addressList.Length - 1], port);<br>             clientSocket = new Socket(hostEndPoint.AddressFamily,<br>                                SocketType.Stream, ProtocolType.Tcp);<br><br>         // Connect to the host.<br>         internal void Connect()<br>         {<!-- --><br>             SocketAsyncEventArgs connectArgs = new SocketAsyncEventArgs();<br><br>             connectArgs.UserToken = clientSocket;<br>             connectArgs.RemoteEndPoint = hostEndPoint;<br>             connectArgs.Completed + =<br>                new EventHandler<socketasynceventargs>(OnConnect);<br><br>             clientSocket.ConnectAsync(connectArgs);<br>             autoConnectEvent.WaitOne();<br><br>             SocketError errorCode = connectArgs.SocketError;<br>             if (errorCode != SocketError.Success)<br>             {<!-- --><br>                 throw new SocketException((Int32)errorCode);<br>             }<br>         }<br><br>         /// Disconnect from the host.<br>         internal void isconnect()<br>         {<!-- --><br>             clientSocket.Disconnect(false);<br>         }<br><br>         //Callback method for connection operation<br>         private void OnConnect(object sender, SocketAsyncEventArgs e)<br>         {<!-- --><br>             //Send a connection completion signal.<br>             autoConnectEvent.Set();<br><br>             //Set the Socket connected flag.<br>             connected = (e.SocketError == SocketError.Success);<br>         }<br><br>         // Callback method for receiving operations<br>         private void OnReceive(object sender, SocketAsyncEventArgs e)<br>         {<!-- --><br>             //Send the reception completion signal.<br>             autoSendReceiveEvents[SendOperation].Set();<br>         }<br><br>         // Callback method for send operation<br>         private void OnSend(object sender, SocketAsyncEventArgs e)<br>         {<!-- --><br>             //Send a send completion signal.<br>             autoSendReceiveEvents[ReceiveOperation].Set();<br><br>             if (e.SocketError == SocketError.Success)<br>             {<!-- --><br>                 if (e.LastOperation == SocketAsyncOperation.Send)<br>                 {<!-- --><br>                     //Ready to receive.<br>                     Socket s = e.UserToken as Socket;<br><br>                     byte [] receiveBuffer = new byte [255];<br>                     e.SetBuffer(receiveBuffer, 0, receiveBuffer.Length);<br>                     e.Completed + = new EventHandler<socketasynceventargs>(OnReceive);<br>                     s.ReceiveAsync(e);<br>                 }<br>             }<br>             else<br>             {<!-- --><br>                 ProcessError(e);<br>             }<br>         }<br><br>         // Close the Socket on failure and throw an exception based on SocketError.<br>         private void ProcessError(SocketAsyncEventArgs e)<br>         {<!-- --><br>             Socket s = e.UserToken as Socket;<br>             if (s.Connected)<br>             {<!-- --><br>                 //Close the Socket associated with the client<br>                 try<br>                 {<!-- --><br>                     s.Shutdown(SocketShutdown.Both);<br>                 }<br>                 catch(Exception)<br>                 {<!-- --><br>                     // If client processing has been closed, throw an exception<br>                 }<br>                 finally<br>                 {<!-- --><br>                     if (s.Connected)<br>                     {<!-- --><br>                         s.Close();<br>                     }<br>                 }<br>             }<br><br>             // throw SocketException<br>             throw new SocketException((Int32)e.SocketError);<br>         }<br><br>         //Exchange messages with the host.<br>         internal String SendReceive(String message)<br>         {<br>             if(connected)<br>             {<!-- --><br>                 //Create a send buffer.<br>                 Byte [] sendBuffer = Encoding.ASCII.GetBytes(message);<br><br>                 // Prepare parameters for send/receive operations.<br>                 SocketAsyncEventArgs completeArgs = new SocketAsyncEventArgs();<br>                 completeArgs.SetBuffer(sendBuffer, 0, sendBuffer.Length);<br>                 completeArgs.UserToken = clientSocket;<br>                 completeArgs.RemoteEndPoint = hostEndPoint;<br>                 completeArgs.Completed + =<br>                   new EventHandler<socketasynceventargs>(OnSend);<br><br>                 // Start sending asynchronously.<br>                 clientSocket.SendAsync(completeArgs);<br><br>                 // Wait for send/receive to complete.<br>                 AutoResetEvent.WaitAll(autoSendReceiveEvents);<br><br>                 // Return data from the SocketAsyncEventArgs buffer.<br>                 return Encoding.ASCII.GetString(completeArgs.Buffer,<br>                        completeArgs.Offset, completeArgs.BytesTransferred);<br>             }<br>             else<br>             {<!-- --><br>                 throw new SocketException((Int32)SocketError.NotConnected);<br>             }<br>         }<br><br>         #region IDisposable Members<br><br>         // Release the SocketClient instance.<br>         public void Dispose()<br>         {<!-- --><br>             autoConnectEvent.Close();<br>             autoSendReceiveEvents[SendOperation].Close();<br>             autoSendReceiveEvents[ReceiveOperation].Close();<br>             if (clientSocket.Connected)<br>             {<!-- --><br>                 clientSocket.Close();<br>             }<br>         }<br><br>         #endregion<br>     }<br> }

Points of interest

History

  • 15 January, 2008 – First version submitted.

Translation notes

Part of “Connecting .NET Framework 3.5” is as follows:

 void OnSendCompletion(IAsyncResult ar) { }<br>  IAsyncResult ar = socket.BeginSend(buffer, 0, buffer.Length,<br>    SocketFlags.None, OnSendCompletion, state);</code>In the new version, you can also implement: 
 void OnSendCompletion(object src, SocketAsyncEventArgs sae) { }<br><br>  SocketAsyncEventArgs sae = new SocketAsyncEventArgs();<br>  sae.Completed + = OnSendCompletion;<br>  sae.SetBuffer(buffer, 0, buffer.Length);<br>  socket.SendAsync(sae);</code>There are some obvious differences here. It is a SocketAsyncEventArgs object that encapsulates the operation context, not an IAsyncResult object. The application creates and manages (and can even reuse) SocketAsyncEventArgs objects. All parameters for socket operations are specified by the properties and methods of the SocketAsyncEventArgs object. Completion status is also provided by properties of the SocketAsyncEventArgs object. Finally, you need to use an event handler to call back the completion method.