Document directory
- How to Use the socketasynceventargs class
How to Use the socketasynceventargs class
ByMarcos Hidalgo Nunes| 14 Jan 2008
An article about how to use the socketasynceventargs class.
- Download client-4.09 KB
- Download server-7.5 KB
Introduction
I was looking for a high performance code for a client socket. previusly, I wrote a code based on the traditional asynchronous programming model methods fromSocket
Class (BeginSend
,BeginReceive
, And so on ). but it didn't fill the performance requirements I needed. then, I found the new model for event-based asynchronous operations (see "get connected with. net Framework 3.5 "in the September 2007 issue of The msdn magazine ).
Background
The asynchronous programming model (APM) is used widely in I/O-bound applications to achieve high performance, due to the role ction of blocking threads. APM is implemented in. NET framework since its first version, and is improving since then, using new techniques like lambda expressions from C #3.0. specifically for socket programming, the new model for APM delivers an easier coding, not to mention the performance benefits. it is done towards the use ofSocketAsyncEventArgs
Class to keep the context between I/O operations, which reduces object allocation and the work of garbage collection.
TheSocketAsyncEventArgs
Class is also available in the. NET Framework 2.0 SP1, and the code from this article was written using Microsoft Visual Studio. NET 2005.
Using the code
To begin withSocketAsyncEventArgs
Class, I studied the example from msdn, but there was something missing:AsyncUserToken
Class. I understood the class shocould expose a propertySocket
, Corresponding to the socket used to perform the I/O operations. Soon, I realized the class wasn' t necessary, since the propertyUserToken
IsObject
, So it cocould accept anything. below shown are the modified methods to directly use the instance of a socket asUserToken
.
// Process the accept for the socket listener.private void ProcessAccept(SocketAsyncEventArgs e){ if (e.BytesTransferred > 0) { Interlocked.Increment(ref numConnectedSockets); Console.WriteLine("Client connection accepted. " + "There are {0} clients connected to the server", numConnectedSockets); } // Get the socket for the accepted client // connection and put it into the // ReadEventArg object user token. SocketAsyncEventArgs readEventArgs = readWritePool.Pop(); readEventArgs.UserToken = e.AcceptSocket; // As soon as the client is connected, // post a receive to the connection. Boolean willRaiseEvent = e.AcceptSocket.ReceiveAsync(readEventArgs); if (!willRaiseEvent) { ProcessReceive(readEventArgs); } // Accept the next connection request. StartAccept(e);}// This method is invoked when an asynchronous receive operation completes. // If the remote host closed the connection, then the socket is closed.// If data was received then the data is echoed back to the client.private void ProcessReceive(SocketAsyncEventArgs e){ // Check if the remote host closed the connection. if (e.BytesTransferred > 0) { if (e.SocketError == SocketError.Success) { Socket s = e.UserToken as Socket; Int32 bytesTransferred = e.BytesTransferred; // Get the message received from the listener. String received = Encoding.ASCII.GetString(e.Buffer, e.Offset, bytesTransferred); // Increment the count of the total bytes receive by the server. Interlocked.Add(ref totalBytesRead, bytesTransferred); Console.WriteLine("Received: \"{0}\". The server has read" + " a total of {1} bytes.", received, totalBytesRead); // Format the data to send to the client. Byte[] sendBuffer = Encoding.ASCII.GetBytes("Returning " + received); // Set the buffer to send back to the client. e.SetBuffer(sendBuffer, 0, sendBuffer.Length); Boolean willRaiseEvent = s.SendAsync(e); if (!willRaiseEvent) { ProcessSend(e); } } else { CloseClientSocket(e); } }}// This method is invoked when an asynchronous send operation completes.// The method issues another receive on the socket to read any additional // data sent from the client.private void ProcessSend(SocketAsyncEventArgs e){ if (e.SocketError == SocketError.Success) { // Done echoing data back to the client. Socket s = e.UserToken as Socket; // Read the next block of data send from the client. Boolean willRaiseEvent = s.ReceiveAsync(e); if (!willRaiseEvent) { ProcessReceive(e); } } else { CloseClientSocket(e); }}
I also modified the code to show how to manipulate the message received ed by the listener, instead of simply echoing back to the client, as seen inProcessReceive
Method. In the example, I used the propertiesBuffer
,Offset
, AndBytesTransfered
To get the received message, andSetBuffer
Method to put the modified answer to the client.
To control the listener lifetime, is used an instance ofMutex
Class.Start
Method, which is a based on the originalInit
Method, creates the mutex, and the correspondingStop
Method releases the mutex. These methods are suitable to implement the socket server as a Windows service.
// Starts the server such that it is listening// for incoming connection requests.internal void Start(Object data){ Int32 port = (Int32)data; // Get host related information. IPAddress[] addressList = Dns.GetHostEntry(Environment.MachineName).AddressList; // Get endpoint for the listener. IPEndPoint localEndPoint = new IPEndPoint(addressList[addressList.Length - 1], port); // Create the socket which listens for incoming connections. this.listenSocket = new Socket(localEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp); if (localEndPoint.AddressFamily == AddressFamily.InterNetworkV6) { // Set dual-mode (IPv4 & IPv6) for the socket listener. // 27 is equivalent to IPV6_V6ONLY socket // option in the winsock snippet below, // based on http://blogs.msdn.com/wndp/archive/2006/10/24/ // creating-ip-agnostic-applications-part-2-dual-mode-sockets.aspx this.listenSocket.SetSocketOption(SocketOptionLevel.IPv6, (SocketOptionName)27, false); this.listenSocket.Bind(new IPEndPoint(IPAddress.IPv6Any, localEndPoint.Port)); } else { // Associate the socket with the local endpoint. this.listenSocket.Bind(localEndPoint); } // Start the server with a listen backlog of 100 connections. this.listenSocket.Listen(100); // Post accepts on the listening socket. this.StartAccept(null); mutex.WaitOne();}// Stop the server.internal void Stop(){ mutex.ReleaseMutex();}
Now that we have a socket server, the next step is to create a socket client usingSocketAsyncEventArgs
Class. Although the msdn says the class is specifically designed for network server applications, there is no restriction in using this APM in a client code. below, there isSocketClien
Class, written in this way:
using System;using System.Net;using System.Net.Sockets;using System.Text;using System.Threading;namespace SocketAsyncClient{ // Implements the connection logic for the socket client. internal sealed class SocketClient : IDisposable { // Constants for socket operations. private const Int32 ReceiveOperation = 1, SendOperation = 0; // The socket used to send/receive messages. private Socket clientSocket; // Flag for connected socket. private Boolean connected = false; // Listener endpoint. private IPEndPoint hostEndPoint; // Signals a connection. private static AutoResetEvent autoConnectEvent = new AutoResetEvent(false); // Signals the send/receive operation. private static AutoResetEvent[] autoSendReceiveEvents = new AutoResetEvent[] { new AutoResetEvent(false), new AutoResetEvent(false) }; // Create an uninitialized client instance. // To start the send/receive processing call the // Connect method followed by SendReceive method. internal SocketClient(String hostName, Int32 port) { // Get host related information. IPHostEntry host = Dns.GetHostEntry(hostName); // Addres of the host. IPAddress[] addressList = host.AddressList; // Instantiates the endpoint and socket. hostEndPoint = new IPEndPoint(addressList[addressList.Length - 1], port); clientSocket = new Socket(hostEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp); } // Connect to the host. internal void Connect() { SocketAsyncEventArgs connectArgs = new SocketAsyncEventArgs(); connectArgs.UserToken = clientSocket; connectArgs.RemoteEndPoint = hostEndPoint; connectArgs.Completed += new EventHandler<socketasynceventargs />(OnConnect); clientSocket.ConnectAsync(connectArgs); autoConnectEvent.WaitOne(); SocketError errorCode = connectArgs.SocketError; if (errorCode != SocketError.Success) { throw new SocketException((Int32)errorCode); } } /// Disconnect from the host. internal void Disconnect() { clientSocket.Disconnect(false); } // Calback for connect operation private void OnConnect(object sender, SocketAsyncEventArgs e) { // Signals the end of connection. autoConnectEvent.Set(); // Set the flag for socket connected. connected = (e.SocketError == SocketError.Success); } // Calback for receive operation private void OnReceive(object sender, SocketAsyncEventArgs e) { // Signals the end of receive. autoSendReceiveEvents[SendOperation].Set(); } // Calback for send operation private void OnSend(object sender, SocketAsyncEventArgs e) { // Signals the end of send. autoSendReceiveEvents[ReceiveOperation].Set(); if (e.SocketError == SocketError.Success) { if (e.LastOperation == SocketAsyncOperation.Send) { // Prepare receiving. Socket s = e.UserToken as Socket; byte[] receiveBuffer = new byte[255]; e.SetBuffer(receiveBuffer, 0, receiveBuffer.Length); e.Completed += new EventHandler<socketasynceventargs />(OnReceive); s.ReceiveAsync(e); } } else { ProcessError(e); } } // Close socket in case of failure and throws // a SockeException according to the SocketError. private void ProcessError(SocketAsyncEventArgs e) { Socket s = e.UserToken as Socket; if (s.Connected) { // close the socket associated with the client try { s.Shutdown(SocketShutdown.Both); } catch (Exception) { // throws if client process has already closed } finally { if (s.Connected) { s.Close(); } } } // Throw the SocketException throw new SocketException((Int32)e.SocketError); } // Exchange a message with the host. internal String SendReceive(String message) { if (connected) { // Create a buffer to send. Byte[] sendBuffer = Encoding.ASCII.GetBytes(message); // Prepare arguments for send/receive operation. SocketAsyncEventArgs completeArgs = new SocketAsyncEventArgs(); completeArgs.SetBuffer(sendBuffer, 0, sendBuffer.Length); completeArgs.UserToken = clientSocket; completeArgs.RemoteEndPoint = hostEndPoint; completeArgs.Completed += new EventHandler<socketasynceventargs />(OnSend); // Start sending asyncronally. clientSocket.SendAsync(completeArgs); // Wait for the send/receive completed. AutoResetEvent.WaitAll(autoSendReceiveEvents); // Return data from SocketAsyncEventArgs buffer. return Encoding.ASCII.GetString(completeArgs.Buffer, completeArgs.Offset, completeArgs.BytesTransferred); } else { throw new SocketException((Int32)SocketError.NotConnected); } } #region IDisposable Members // Disposes the instance of SocketClient. public void Dispose() { autoConnectEvent.Close(); autoSendReceiveEvents[SendOperation].Close(); autoSendReceiveEvents[ReceiveOperation].Close(); if (clientSocket.Connected) { clientSocket.Close(); } } #endregion }}
Points of interest
I had an experience with a socket server running in a clustered environment. In this scenario, you can't use the first entry in the address list from the host. Instead, you should useLastAddress, as shown inStart
Method. another technique presented here is how to set the dual mode for an ip6 address family, which is helpful if you want to run the server in a Windows Vista or Windows Server 2008, which enables ip6 by default.
Both programs use command-line arguments to run. in the client example, you shoshould inform "localhost" as the name of the host instead of the machine name if both the server and the client are running in a machine out of a Windows domain.