Home arrow C# arrow Page 2 - Asynchronous Socket Utility Classes – Part I
C#

Asynchronous Socket Utility Classes – Part I


William has created a two part series on using Asynchronous Socket Utility Classes in C#. Part one will describe client side socket programming.

Author Info:
By: William Kennedy
Rating: 4 stars4 stars4 stars4 stars4 stars / 39
March 28, 2003
TABLE OF CONTENTS:
  1. · Asynchronous Socket Utility Classes – Part I
  2. · Component Design and Coding
  3. · Sample Application 1
  4. · Review

print this article
SEARCH DEVARTICLES

Asynchronous Socket Utility Classes – Part I - Component Design and Coding
(Page 2 of 4 )

CSocketClient

Setup Project

CSocketClient is the class that we will construct to make it easy to access and send messages to socket servers.  The first thing we want to do is start a new C# Console Application project called SocketSystem.

This will generate a project with one class module file called class1.cs.  We will use the class module file later to write our sample test code. 

Now add a new class module to the project.

Call the new class module UtilSocket.cs.

Now we have the following code generated for us in the UtilSocket.cs file.

using System;
namespace SocketSystem
{
 /// <summary>
 /// Summary description for UtilSocket.
 /// </summary>
 public class UtilSocket
 {
  public UtilSocket()
  {
   //
   // TODO: Add constructor logic here
   //
  }
 }
}

We will start by making a few changes to this generated code.

First we need to add 3 new namespaces to the class module.  The System.Net and System.Net.Sockets namespaces provide us with all of the .NET socket support we need.  The System.Threading namespace provides us with all of the .NET threading support we need.

Change the name of the class to CSocketClient and change the name of the constructor function as well.  Fix the comment section of the class and the constructor function using the XML documentation tags.

If you work in an environment that requires documentation for everything you do, learn how to exploit the new XML documentation support provided in C#.  You will be able to generate a complete document for your project, while you’re coding, and with detail you never had time to add before.  This feature is awesome and I am constantly enhancing my coding standard in this area.  To start you on your way check out this Microsoft link: http://msdn.microsoft.com/msdnmag/issues/02/06/XMLC/default.aspx.

Last add the different section headers.  I like to maintain two line feeds between each section header and I like to have the section headers in the same column of the class for easy readability.

using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
namespace SocketSystem
{
  //=============================================
  /// <summary> This class abstracts a socket </summary>
  public class CSocketClient
  {
  // Delegate Method Types
  // Private Properties
  // Public Properties
  // Constructor, Finalize, Dispose
    //*******************************************
    /// <summary> Constructor for client support </summary>
    public CSocketClient()
    {
    }
  // Private Methods
  // Public Methods
  }
}

Delegate Method Types Section

We need to add three delegate method types.

The first delegate method type is for the Message Handler.  We will call the method referenced by a property of this type whenever a message is received on the socket.  The second delegate method type is for the Close Handler.  We will call the method referenced by a property of this type when the socket connection is closed or terminated.  The last delegate method type is the Error Handler.  We will call the method referenced by a property of this type when a socket error occurs on one of the threads in the NetworkStream property thread pool.

All of these delegate methods return a CSocketClient reference.  This allows the application developer to know which connection needs to be processed for the type of method call.  In the case of the Message Handler the size of the message is returned as well.  This allows the application developer to know the size of the message received.  The message buffer is a property of the CSocketClient object.
 
/// <summary> DelType: Called when a message is received </summary>
public delegate void MESSAGE_HANDLER(CSocketClient pSocket, Int32 iNumberOfBytes);
   
/// <summary> DelType: Called when a connection is closed </summary>
public delegate void CLOSE_HANDLER(CSocketClient pSocket);
   
/// <summary> DelType: Called when a socket error occurs </summary>
public delegate void ERROR_HANDLER(CSocketClient pSocket, Exception pException);

Private Properties Section

We have eight private properties to add. 

The System.Net.Sockets namespace provides the NetworkStream class.  This property provides the functionality for managing socket messages asynchronously.

private NetworkStream m_pNetworkStream;
  /// <summary> RefType: A network stream object </summary>
  private NetworkStream GetNetworkStream { get { return m_pNetworkStream; } set { m_pNetworkStream = value; } }

The System.Net.Sockets namespace provides this class.  This property provides the functionality for creating and managing socket client connections.

private TcpClient m_pTcpClient;
  /// <summary> RefType: A TcpClient object for socket connection </summary>
  private TcpClient GetTcpClient { get { return m_pTcpClient; } set { m_pTcpClient = value; } }

The System namespace provides this delegate method type.  This delegate method type is used to define .NET callback methods.  In this case we need properties to hold references to methods to be called by the NetworkStream property.  The NetworkStream property will call these methods when socket read and write operations are complete.

private AsyncCallback m_pCallbackReadMethod;
  /// <summary> RetType: A callback object for processing recieved socket data </summary>
  private AsyncCallback GetCallbackReadMethod { get { return m_pCallbackReadMethod; } set { m_pCallbackReadMethod = value; } }

private AsyncCallback m_pCallbackWriteMethod;
  /// <summary> RetType: A callback object for processing send socket data </summary>
  private AsyncCallback GetCallbackWriteMethod { get { return m_pCallbackWriteMethod; } set { m_pCallbackWriteMethod = value; } }

Now it is time to add our own delegate method type properties.  These properties will contain references to application developer methods.  We will call these application developer methods through these handler properties at the appropriate time. 
    
private MESSAGE_HANDLER m_pfnMessageHandler;
  /// <summary> DelType: A reference to a user supplied function to be called when a socket message arrives </summary>
  private MESSAGE_HANDLER GetMessageHandler { get { return m_pfnMessageHandler; } set { m_pfnMessageHandler = value; } }
    
private CLOSE_HANDLER m_pfnCloseHandler;
  /// <summary> DelType: A reference to a user supplied function to be called when a socket connection is closed </summary>
  private CLOSE_HANDLER GetCloseHandler { get { return m_pfnCloseHandler; } set { m_pfnCloseHandler = value; } }
 
private ERROR_HANDLER m_pfnErrorHandler;
  /// <summary> DelType: A reference to a user supplied function to be called when a socket error occurs </summary>
  private ERROR_HANDLER GetErrorHandler { get { return m_pfnErrorHandler; } set { m_pfnErrorHandler = value; } }

The last private property for now is the dispose flag property.  The finalize method will use this flag to determine if the class has already been disposed or not.
     
private Boolean m_bDisposeFlag;
  /// <summary> SimType: Flag to indicate if the class has been disposed </summary>
  private Boolean IsDisposed { get { return m_bDisposeFlag; } set { m_bDisposeFlag = value; } }

Public Properties Section

We have five public properties to add.

This property provides the IpAddress of a server to which we wish to establish a connection.

private String m_strIpAddress;
  /// <summary> RefType: The IpAddress the client is connect to </summary>
  public String GetIpAddress { get { return m_strIpAddress; } set { m_strIpAddress = value; } }

This property provides the port of a server to which we wish to establish a connection.
    
private Int16 m_iPort;
  /// <summary> SimType: The Port to either connect to or listen on </summary>
  public Int16 GetPort { get { return m_iPort; } set { m_iPort = value; } }

This property is for the application developer. If the application developer would like to store some state associated with the socket connection they can use this property.
 
private Object m_pUserArg;
  /// <summary> RefType: A reference to a user defined object </summary>
  public Object GetUserArg { get { return m_pUserArg; } set { m_pUserArg = value; } }
 
These two properties are used to receive socket messages from the NetworkStream property.  Because each application is different, we need to allow the application developer to define the size of the buffer.  Some applications move very little data back and forth while some applications move large amounts of data.
   
private Byte[] m_bytRawBuffer;
  /// <summary> SimType: A raw buffer to capture data comming off the socket </summary>
  public Byte[] GetRawBuffer { get { return m_bytRawBuffer; } set { m_bytRawBuffer = value; } }

private Int32 m_iSizeOfRawBuffer;
  /// <summary> SimType: Size of the raw buffer for received socket data </summary>
  public Int32 GetSizeOfRawBuffer { get { return m_iSizeOfRawBuffer; } set { m_iSizeOfRawBuffer = value; } }

Constructor, Finalize, and Dispose Section

We need to add five arguments to the existing constructor.  The XML documentation for the constructor describes the arguments.  In the constructor we are setting the class properties that should not change during the lifetime of an object instance of this class.

In the constructor method we need to instantiate the raw buffer for acquiring socket messages and store the reference to the user-defined argument.  The application developer callback method references need to be set next.  These methods provide the application developer access to any received messages and notifications about the socket connection.  Setting our callback method references for the NetworkStream property and setting the dispose flag to false are the last things we need to do in the constructor.

//*************************************************
/// <summary> Constructor for client support </summary>
/// <param name="iSizeOfRawBuffer"> SimType: The size of the raw buffer </param>
/// <param name="pUserArg"> RefType: A Reference to the Users arguments </param>
/// <param name="pfnMessageHandler"> DelType: Reference to the user defined message handler method </param>
/// <param name="pfnCloseHandler"> DelType: Reference to the user defined close handler method </param>
/// <param name="pfnErrorHandler"> DelType: Reference to the user defined error handler method </param>
public CSocketClient(Int32 iSizeOfRawBuffer, Object pUserArg,
  MESSAGE_HANDLER pfnMessageHandler, CLOSE_HANDLER pfnCloseHandler, ERROR_HANDLER pfnErrorHandler)
{
  // Create the raw buffer
  GetSizeOfRawBuffer = iSizeOfRawBuffer;
  GetRawBuffer       = new Byte[GetSizeOfRawBuffer];
  // Save the user argument
  GetUserArg = pUserArg;
  // Set the handler methods
  GetMessageHandler = pfnMessageHandler;
  GetCloseHandler   = pfnCloseHandler;
  GetErrorHandler   = pfnErrorHandler;
  // Set the async socket method handlers
  GetCallbackReadMethod  = new AsyncCallback(ReceiveComplete);
  GetCallbackWriteMethod = new AsyncCallback(SendComplete);
  // Init the dispose flag
  IsDisposed = false;
}

The finalize method verifies that the object instance has not been disposed.  If the application developer remembers to properly call dispose, there is nothing for finalize to do.  This finalize function only exists as a safe guard.
   
//**************************************************
/// <summary> Finialize </summary>
~CSocketClient()
{
  if (!IsDisposed)
    Dispose();
}

The dispose method sets the dispose flag to true so the finalize function does not try to dispose the object instance again.  All that needs to be done is to make sure the socket connection is disconnected.

//***********************************************
/// <summary> Dispose </summary>
public void Dispose()
{
  try
  {
    // Flag that dispose has been called
    IsDisposed = true;
    // Disconnect the client from the server
    Disconnect();
  }
  catch
  {
  }
}

Private Methods Section
 
If you look back at the constructor you will see the following line of code.

GetCallbackReadFunction = new AsyncCallback(ReceiveComplete);

ReceiveComplete is the method that will be called by the NetworkStream property when a socket message is received.  This method is called by a thread owned by the NetworkStream property.  The NetworkStream property maintains a thread pool for processing socket messages.  This pool is controlled by the NetworkStream property and provides scalability and performance.

The ReceiveComplete method will never be called by the NetworkStream property unless the CSocketClient Connect method is called first by the application developer.  The Connect method establishes a connection to a socket server via the TcpClient property and calls the CSocketClient Receive method. 

The CSocketClient Receive method activates the NetworkStream property so it will call the ReceiveComplete method when a message is received.  In summary, once a message is received by the TcpClient property and passed to the NetworkStream property, the NetworkStream property will call the ReceiveComplete method.  The ReceiveComplete method will in turn call the application developer’s Message Handler.  We will write the Connect and Receive methods later in the public methods section.

When a thread in the NetworkStream property calls the ReceiveComplete method we need to make sure the socket is still valid.  There is a chance the socket is in a bad state when this method is called.  If we cannot read from the socket, we just allow this function to complete and we will no longer wait for any new socket messages to be received.

Inside of the ReceiveComplete method we call the EndRead method from the NetworkStream property.  We pass the IAsyncResult argument into the call to EndRead.  The IAsyncResult argument contains all of the state about the current message we received on the socket.  When this call returns, we will have a Byte array filled will the socket message we received.  The ReceiveComplete method returns the number of bytes that were copied into the Byte array.  When we look at the Receive method later you will see how the Byte array and this callback function integrate into the NetworkStream property.

If the number of bytes returned by the EndRead method is 0 then the socket connection has been closed.  In this case we throw an exception in the ReceiveComplete method that is caught by the ReceiveComplete method.  We do this to reuse the code in the catch statement.  The catch statement will call the Close Handler, informing the application developer that the connection has been closed. When the client socket connection is closed, either by the server or the application developer, the NetworkStream property will call the ReceiveComplete method and the EndRead method will return 0 bytes.

If the number of bytes returned by EndRead is greater than 0, we have a socket message.  All we need to do in this case is call the Message Handler informing the application developer that he has received a message.  The application developer inside his Message Handler can perform any actions on the message.

It is important to note here that a thread owned by the NetworkStream property is processing client socket messages that are received.  While this thread is processing the message, all new messages are being stored inside of an internal socket buffer.  In the Connect method, we set the size of the internal socket buffer to 1 Meg through the TcpClient property.  If the internal socket buffer becomes full because data is not being cleared fast enough, the server sending the data could experience blocking on threads issuing socket sends.  This is very bad because it may cause unpredictable behavior.  We need to make sure the Message Handler performs quickly and does not block for any real length of time.

Once the Message Handler returns, we call the CSocketClient Receive function.  This tells the NetworkStream property to call the ReceiveComplete method again when it receives a new message.  If the call to Receive is not made, no more messages can be processed.  The internal socket buffer will just fill up and the server thread sending the messages will potentially block.

Notice that inside the catch statement we call the Close Handler, notifying the application developer the socket connection is closed.  At this point, we need to destroy the CSocketClient object.  The call to the CSocketClient Dispose method will call the CSocketClient Disconnect method and de-reference any memory no longer required.  As you will see later the call to the Disconnect method will de-reference all of the properties of this class and allow garbage collection to clean up.

//*********************************************
/// <summary> Called when a message arrives </summary>
/// <param name="ar"> RefType: An async result interface </param>
private void ReceiveComplete(IAsyncResult ar)
{
  try
  {
    // Is the Network Stream object valid
    if (GetNetworkStream.CanRead)
    {
      // Read the current bytes from the stream buffer
      Int32 iBytesRecieved = GetNetworkStream.EndRead(ar);
      // If there are bytes to process else the connection is lost
      if (iBytesRecieved > 0)
      {
        try
        {
          // A message came in send it to the MessageHandler
          GetMessageHandler(this, iBytesRecieved);
        }
        catch
        {
        }
        // Wait for a new message
        Receive();
      }
      else
        throw new Exception("Shut Down");
    }
  }
  catch (Exception)
  {
    try
    {
      // The connection must have dropped call the CloseHandler
      GetCloseHandler(this);
    }
    catch
    {
    }
    // Dispose of the class
    Dispose();
  }
}

If you look back at the constructor you will see the following line of code.

GetCallbackWriteFunction = new AsyncCallback(SendComplete);

The SendComplete method will be called by a NetworkStream property thread when a socket message is being sent.  By performing an asynchronous send, we can avoid the blocking situation I described earlier and prevent threads that are calling the Send method from blocking.  In the SendComplete method, we check that the socket is not in a bad state by calling CanWrite.  If we can write to the socket, we send the message out to the server.  The IAsyncResult argument contains all of the state about the current message we are about to send on the socket.
   
//*********************************************
/// <summary> Called when a message is sent </summary>
/// <param name="ar"> RefType: An async result interface </param>
private void SendComplete(IAsyncResult ar)
{
  try
  {
    // Is the Network Stream object valid
    if (GetNetworkStream.CanWrite)
      GetNetworkStream.EndWrite(ar);
  }
  catch (Exception)
  {
  }
}

Public Methods Section
 
After the application developer instantiates a CSocketClient object, he will call the Connect method.  The Connect method will allow the application developer to connect to a server.  The application developer passes the ipaddress and port of the server.
 
In the Connect method, we check to make sure there is no connection.  When the NetworkStream property is null no connection has been established.  Then we need to store the ipaddress and port that is passed into the method.  The TcpClient property can be instantiated now by passing the ipaddress and port of the server.  This instantiation of the TcpClient property attempts a connection to the specified server and port. 

If this instantiation does not throw an exception, a connection was established.  The application developer needs to place the call to the Connect method inside of a try statement, attempting to catch a standard Exception.  Once the connection is established, the TcpClient property is associated with the NetworkStream property.  The TcpClient GetStream method returns a NetworkStream object.  We assign the TcpClient NetworkStream object to our NetworkStream property.  This will provide the asynchronous message processing support for the new socket connection.

Five socket options are set for the socket.  First we set the size of the receive and send buffers.  I choose 1 Meg for each.  This really isn't a lot of memory and it will give you time to process messages even in the busiest applications.  By setting the NoDelay option to true we are making sure that when there is data to send or receive, it is provided immediately.  We don't want to wait until our buffers are full.

The last setting is the LingerState.  This option is interesting.  By default when a socket connection is signaled to the application as closed, it really isn't closed.  If you ran a “netstat –a” command from the command line you would still see references to these "closed" connections.  This is done to allow you to process any data that may have been in the buffer or in transit before the connection was closed.  In my world, if the connection is dropped, I don't care about any remaining data and I want the connection reference dropped immediately.

The very last thing to do in the Connect method is to call the Receive method.  This is very important.  This allows the processing of messages to be performed asynchronously.  Remember when we implemented the ReceiveComplete method?  In that method we call Receive after we finish processing a message so we can receive another one.  Connect makes the first call to Receive and starts the processing of messages.

If an error occurs, we catch it and convert the error to a standard Exception object.  This way the application developer only has to catch an Exception object to get the error information they need.

//********************************************
/// <summary> Function used to connect to a server </summary>
/// <param name="strIpAddress"> RefType: The address to connect to </param>
/// <param name="iPort"> SimType: The Port to connect to </param>
public void Connect(String strIpAddress, Int16 iPort)
{
  try
  {
    if (GetNetworkStream == null)
    {
      // Set the Ipaddress and Port
      GetIpAddress = strIpAddress;
      GetPort      = iPort;
      // Attempt to establish a connection
      GetTcpClient     = new TcpClient(GetIpAddress, GetPort);
      GetNetworkStream = GetTcpClient.GetStream();
      // Set these socket options
      GetTcpClient.ReceiveBufferSize = 1048576;
      GetTcpClient.SendBufferSize    = 1048576;
      GetTcpClient.NoDelay           = true;
      GetTcpClient.LingerState       = new System.Net.Sockets.LingerOption(false,0);
      // Start to receive messages
      Receive();
    }
  }
  catch (System.Net.Sockets.SocketException e)
  {
    throw new Exception(e.Message, e.InnerException);
  }
}

If the application developer would like to disconnect from the server, he can call disconnect.  The Disconnect method will initialize any properties that need to be initialized so the call to the Connect method can be made again.

Inside of the Close method we need to call the Close methods of both the NetworkStream property and the TcpClient property.  In order to mark these properties for destruction, the references are de-referenced.

//***********************************************
/// <summary> Function used to disconnect from the server </summary>
public void Disconnect()
{
  // Close down the connection
  if (GetNetworkStream != null)
    GetNetworkStream.Close();
  if (GetTcpClient != null)
    GetTcpClient.Close();
  // Clean up the connection state
  GetNetworkStream = null;
  GetTcpClient     = null;
}

There are two versions of the Send method.  The first version accepts a string and the other accepts a byte array.

Inside the Send method, we need to verify that we have a connection and that the socket is not in a bad state.  In the version of the Send method that takes a String, we must convert the String into a byte array.  This is because the NetworkStream BeginWrite method only takes a byte array.  To convert the string to a byte array, use the System.Text.Encoding.ASCII.GetBytes method. 

Once the string argument is converted to a byte array, we can send the message to the server.  In the version of the send method that takes a byte array we only need to call the NetworkStream BeginWrite method.

We need to pass five arguments to the BeginWrite method.  The first argument is a byte array that holds the message to send.  The next argument is the offset into the byte array.  The third argument is the number of the byte array to send from the byte array.  The fourth argument is the method to call when the NetworkStream property attempts to send the message.  The last argument is any state we wish to receive when the callback method is called.

The NetworkStream BeginWrite method will have a NetworkStream thread perform the write so the thread calling the Send method will never block.

//***************************************************
/// <summary> Function to send a string to the server </summary>
/// <param name="strMessage"> RefType: A string to send </param>
public void Send(String strMessage)
{
  if ((GetNetworkStream != null) && (GetNetworkStream.CanWrite))
  {
    // Convert the string into a Raw Buffer
    Byte[] pRawBuffer = System.Text.Encoding.ASCII.GetBytes(strMessage);
// Issue an asynchronus write
    GetNetworkStream.BeginWrite(pRawBuffer, 0, pRawBuffer.GetLength(0), GetCallbackWriteFunction, null);
  }
  else
    throw new Exception("Socket Closed");
}
//************************************************
/// <summary> Function to send a raw buffer to the server </summary>
/// <param name="pRawBuffer"> RefType: A Raw buffer of bytes to send </param>
public void Send(Byte[] pRawBuffer)
{
  if ((GetNetworkStream != null) && (GetNetworkStream.CanWrite))
  {
    // Issue an asynchronus write
    GetNetworkStream.BeginWrite(pRawBuffer, 0, pRawBuffer.GetLength(0), GetCallbackWriteMethod, null);
  }
  else
    throw new Exception("Socket Closed");
}

The last method to code is Receive.  This method will have a NetworkStream thread wait for a message to arrive on the socket.  We need to pass five arguments to the BeginRead method.  The first argument is a byte array to hold any message that is received.  The next argument is the offset into the byte array. 

The third argument is the size of the byte array.  The forth argument is the method to call when the NetworkStream property receives a message.  The last argument is any state we wish to receive when the callback method is called.  Remember if there is no call to Receive, no message will be sent to the ReceiveComplete method and we will not be able to pass the data to the application developer’s Message Handler.

//**********************************************
/// <summary> Wait for a message to arrive </summary>
public void Receive()
{
  if ((GetNetworkStream != null) && (GetNetworkStream.CanRead))
  {
    // Issue an asynchronous read
    GetNetworkStream.BeginRead(GetRawBuffer, 0, GetSizeOfRawBuffer, GetCallbackReadMethod, null);
  }
  else
    throw new Exception("Socket Closed");
}


blog comments powered by Disqus
C# ARTICLES

- Introduction to Objects and Classes in C#, P...
- Visual C#.NET, Part 1: Introduction to Progr...
- C# - An Introduction
- Hotmail Exposed: Access Hotmail using C#
- Razor Sharp C#
- Introduction to Objects and Classes in C#
- Making Your Code CLS Compliant
- Programming with MySQL and .NET Technologies
- Socket Programming in C# - Part II
- Socket Programming in C# - Part I
- Creational Patterns in C#
- Type Conversions
- Creating Custom Delegates and Events in C#
- Inheritance and Polymorphism
- Understanding Properties in C#

Watch our Tech Videos 
Dev Articles Forums 
 RSS  Articles
 RSS  Forums
 RSS  All Feeds
Write For Us 
Weekly Newsletter
 
Developer Updates  
Free Website Content 
Contact Us 
Site Map 
Privacy Policy 
Support 

Developer Shed Affiliates

 




© 2003-2017 by Developer Shed. All rights reserved. DS Cluster - Follow our Sitemap
Popular Web Development Topics
All Web Development Tutorials