Click here to Skip to main content
6,822,613 members and growing! (16,259 online)
Email Password   helpLost your password?
General Programming » Internet / Network » Client/Server Development     Intermediate License: The Code Project Open License (CPOL)

How to use the SocketAsyncEventArgs class

By Marcos Hidalgo Nunes

An article about how to use the SocketAsyncEventArgs class.
C# (C#1.0, C#2.0, C#3.0), .NET (.NET2.0, .NET3.0, .NET3.5), Architect, Dev
Posted:14 Jan 2008
Views:33,102
Bookmarked:35 times
printPrint   add Share
      Discuss Discuss   Broken Article?Report  
8 votes for this article.
Popularity: 3.85 Rating: 4.26 out of 5
1 vote, 12.5%
1

2

3
2 votes, 25.0%
4
5 votes, 62.5%
5

Introduction

I was looking for a high performance code for a client socket. Previously, I wrote a code based on the traditional asynchronous programming model methods from the Socket 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 the .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 reduction of blocking threads. APM is implemented in the .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 of the SocketAsyncEventArgs class to keep the context between I/O operations, which reduces object allocation and the work of garbage collection.

The SocketAsyncEventArgs 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 with the SocketAsyncEventArgs class, I studied the example from MSDN, but there was something missing: the AsyncUserToken class. I understood the class should expose a property Socket, corresponding to the socket used to perform the I/O operations. Soon, I realized the class wasn't necessary, since the property UserToken is an Object, so it could accept anything. Below shown are the modified methods to directly use the instance of a socket as the UserToken.

// 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 by the listener, instead of simply echoing back to the client, as seen in the ProcessReceive method. In the example, I used the properties Buffer, Offset, and BytesTransfered to get the received message, and the SetBuffer method to put the modified answer to the client.

To control the listener lifetime, is used an instance of the Mutex class. The Start method, which is a based on the original Init method, creates the mutex, and the corresponding Stop 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 using the SocketAsyncEventArgs 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 is the SocketClien 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 use the last address, as shown in the Start 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 should 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.

History

  • 15 January, 2008 - Original version posted.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Marcos Hidalgo Nunes


Member
I am working with software development since 1989, mainly in distributed environments.
Occupation: Architect
Location: Brazil Brazil

Other popular Internet / Network articles:

Article Top
You must Sign In to use this message board.
FAQ FAQ 
 
Noise Tolerance  Layout  Per page   
 Msgs 1 to 25 of 32 (Total in Forum: 32) (Refresh)FirstPrevNext
GeneralAnd for multiple Clients? PinmemberFILLY861:45 3 Feb '10  
Generalsending huge amount of data between client and server PinmemberEng.Ashraf Gomaa1:18 21 Dec '09  
GeneralSending buffer using the server Pinmembertiran_kaskas6:04 2 Jul '09  
QuestionUpdated Code for This Project? PinmemberRob Wells12:18 21 May '09  
GeneralMeesage Breaks into 2 parts PinmemberMember 245269823:38 28 Apr '09  
GeneralRe: Meesage Breaks into 2 parts Pinmemberzzdabing@yahoo.cn21:41 12 Jul '09  
GeneralHow to make seprate methods instead of SendRecevie() method. PinmemberMember 245269822:30 4 Apr '09  
GeneralRe: How to make seprate methods instead of SendRecevie() method. Pinmemberzzdabing@yahoo.cn21:43 12 Jul '09  
QuestionHow about UDP version ? Pinmembergattaca88077@yahoo.com.tw1:21 2 Apr '09  
GeneralMemory Release PinmemberMember 41068852:04 19 Nov '08  
GeneralRe: Memory Release PinmemberMember 41068859:04 11 Dec '08  
GeneralStop Does Not Stop Listener Pinmembertsgood9:58 16 Sep '08  
GeneralRe: Stop Does Not Stop Listener Pinmembertsgood6:25 26 Sep '08  
GeneralRe: Stop Does Not Stop Listener Pinmembertsgood4:26 29 Sep '08  
Generaldata coming over in chunks Pinmemberjeff kennedy8:29 13 Aug '08  
QuestionRe: data coming over in chunks Pinmembertsgood7:14 28 Aug '08  
AnswerRe: data coming over in chunks Pinmemberjeff kennedy7:27 28 Aug '08  
GeneralOnly getting one message from Client Pinmemberjeff kennedy5:22 13 Aug '08  
GeneralRe: Only getting one message from Client Pinmemberjeff kennedy8:29 13 Aug '08  
GeneralBuffers inconsistent PinmemberAndyLippitt16:24 2 Aug '08  
GeneralRe: Buffers inconsistent PinmemberMarcos Nunes14:26 7 Aug '08  
GeneralSocket doesn't free memory PinmemberMember 38323013:44 17 Jun '08  
GeneralRe: Socket doesn't free memory PinmemberMarcos Nunes15:29 16 Jul '08  
GeneralRe: Socket doesn't free memory Pinmembertigerfist55517:38 4 Aug '08  
GeneralThe CloseClientSocket method is never called PinmemberMarkHaliday17:41 23 Feb '08  

General General    News News    Question Question    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads.

PermaLink | Privacy | Terms of Use
Last Updated: 14 Jan 2008
Editor: Smitha Vijayan
Copyright 2008 by Marcos Hidalgo Nunes
Everything else Copyright © CodeProject, 1999-2010
Web22 | Advertise on the Code Project