Asynchronous Socket Utility Classes – Part II

类别:.NET开发 点击:0 评论:0 推荐:

Asynchronous Socket Utility Classes – Part II

Author: William Kennedy
Date Added: 31st Mar 2003
Type: Tutorial
Rating: Average visitor rating of 8 out of 10Average visitor rating of 8 out of 10Average visitor rating of 8 out of 10Average visitor rating of 8 out of 10Average visitor rating of 8 out of 10Average visitor rating of 8 out of 10Average visitor rating of 8 out of 10Average visitor rating of 8 out of 10Average visitor rating of 8 out of 10Average visitor rating of 8 out of 10

This is a printable version of "Asynchronous Socket Utility Classes – Part II". For the complete online version, please visit http://www.devarticles.com/content.php?articleId=493


Page 1: Introduction

A socket is a connection oriented communication channel that is established between two computers.  In the OSI network model, http://www2.rad.com/networks/1994/osi/layers.htm, sockets are implemented at the session layer.  This means that sockets can be used with any transport protocol like TCP or UDP.  Session layer protocols, like sockets, require that a computer must establish a connection to any computer it wished to communicate with.  Once a connection is established, messages can be passed between both computers. 


A socket connection can be thought of as a virtual pipe between two computers.  Each pipe provides an exclusive communication channel.  If multiple computers connect to the same computer, each will have their own pipe.  It is easy to identify which computer sent a message because each message flows through that computers pipe.  This means an application must be looking for messages on every pipe that exists.  The most common way to do this is to spawn a thread for each pipe. 

This thread just waits for messages to arrive on their respective pipe and then processes the message.  This design works well until you have hundreds of pipes.  Each pipe requires a thread and each thread has the potential to be in a running state at the same time.  This solution is not scalable and will cause performance problems as the number of pipes and messages going through those pipes increase.


Asynchronous sockets provide a way to have a pool of threads process messages for all established pipes, instead of having a thread per pipe.  This solution is scalable and improves performance.  The .NET framework provides nice low-level classes to help us add asynchronous socket support into our applications.  However, the implementation still lacks an organizational structure that is easy to use.


For more information on socket programming look at the follow sites:


http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/cpconnon-blockingserversocketexample.asp

http://squirl.nightmare.com/medusa/async_sockets.html


When adding asynchronous socket support into our applications it can seem initially confusing, and at times overwhelming.  We have to understand how the asynchronous socket framework is implemented. We need to understand threading and how it is applied.  Most importantly we need to have some understanding of how to integrate this into our application.


To help us through these issues, I have built two classes.  These classes provide reusable interfaces, which cleanly and quickly add asynchronous socket support into our applications.  Both of these classes take care of the low-level details that allow us to only have to worry about coding the business problem at hand.  Low level details such as implementing the asynchronous socket thread pool, identifying which messages came from which computers, and handling any error conditions that occur in a consistent way.


These classes will save us hours of time developing and debugging our asynchronous socket applications.  In this article I will build the server socket class and sample application to test it.


System Requirements


A basic understanding of C# is required to follow through the examples and the classes.  Basic concepts of type, properties, threading, synchronization, and delegates are required.  It is important that you have completed part I of this article.  In that article we build the CSocketServer class.


Defining The Problem


High-level class needs to be developed that abstract the .NET framework classes used to develop asynchronous socket applications.  This new class needs to abstract the details behind asynchronous socket programming.  This class must be easy to use and reliable.  Any classes that are developed need to be reusable so they can be shared among many different types of applications.


Defining The Solution


A single class will be developed.  We will develop a class that abstracts the intricacies of building asynchronous socket servers.  This class will allow us to be ignorant of the details and yet allow us to access the underlining framework if we need to.



Page 2: Component Design and Coding

CSocketServer


Now we will build a class that provides socket server support.
 
Setup CSocketServer Class


Open the UtilSocket.cs file created during Part I of this article and add the follow class just after the CSocketClient class inside the namespace.


  } // End of CSocketClient
  //========================================================================
  /// <summary> This class abstracts a socket server </summary>
  public class CSocketServer
  {
  // Delegate Method Types
  // Private Properties
  // Public Properties
  // Constructor, Finalize, Dispose
    //********************************************************************
    /// <summary> Constructor for client support </summary>
    public CSocketServer()
    {
    }
  // Private Methods
  // Public Methods
  }
} // End of Namespace


Delegate Method Types Section


We need to add four delegate method types.


The first three delegate method types are identical to the delegate method types we added to the CSocketClient class and perform the same function.  The new delegate method type is for the Accept Handler.  We will call the method referenced by a property of this type when a client socket connection request is accepted.


/// <summary> DelType: Called when a message is extracted from the socket </summary>
public delegate void MESSAGE_HANDLER(CSocketClient pSocket, Int32 iNumberOfBytes);
   
/// <summary> DelType: Called when a socket 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);
   
/// <summary> DelType: Called when a socket connection is accepted </summary>
public delegate void ACCEPT_HANDLER(CSocketClient pSocket);


Private Properties Section


We need to add eleven private properties


In the CSocketClient class, we used the TcpClient class to provide the support we needed to establish socket connections to a server.  In this class we will create a property using the TcpListener class.  This property will provide the support we need to listen for and accept those client connections requests coming from the TcpClient class or similar technologies.


private TcpListener m_pTcpListener;
  /// <summary> RefType: A TcpListener object to accept socket connections </summary>
  private TcpListener GetTcpListener { get { return m_pTcpListener; } set { m_pTcpListener = value; } }


The CSocketServer class will be responsible for listening and accepting client socket connection requests via the TcpListener property.  We need a thread to perform this responsibility.   This property is a thread object to manage the Accept thread.


private Thread m_pAcceptThread;
  /// <summary> RetType: A thread to process accepting socket connections </summary>
  private Thread GetAcceptThread { get { return m_pAcceptThread; } set { m_pAcceptThread = value; } }


Once a connection request is accepted the Accept thread, the thread will instantiate a CSocketClient object.  The new CSocketClient object will be used to encapsulate the new socket connection.  The CSocketServer will maintain an array of the CSocketClient objects it instantiates and will be responsible for the lifetime of these objects. 


private CSocketClient[] m_pSocketClientList;
  /// <summary> RefTypeArray: An Array of SocketClient objects </summary>
  private CSocketClient[] GetSocketClientList { get { return m_pSocketClientList; } set { m_pSocketClientList = value; } }


The application developer will decide the maximum number of client socket connections that will be accepted.  This property maintains this number.  It is used later in the constructor to instantiate the SocketClientList property array.


private Int32 m_iMaxClientConnections;
  /// <summary> SimType: Maximum number of client connections </summary>
  public Int32 GetMaxClientConnections { get { return m_iMaxClientConnections; } set { m_iMaxClientConnections = value; } }


Since the Accept thread is instantiating CSocketClient objects, it must pass the required arguments to its constructor.  This property is maintained by the CSocketServer class so the Accept thread can pass this value to the constructor of the CSocketClient objects it creates.


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


The following delegate method type properties are maintained because they need to be passed to the constructor of the CSocketClient objects created by the Accept thread.  The Accept Handler property is used by the Accept thread and not passed to the instantiated CSocketClient object.  When the Accept thread accepts a client socket connection request and instantiates a CSocketClient object, the Accept thread will call the Accept Handler method.  This is when the application developer can perform any application specific actions required when a client connection is accepted.


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; } }


private ACCEPT_HANDLER m_pfnAcceptHandler;
  /// <summary> DelType: A reference to a user supplied function to be called when a socket connection is accepted </summary>
  private ACCEPT_HANDLER GetAcceptHandler { get { return m_pfnAcceptHandler; } set { m_pfnAcceptHandler = value; } }


There will be a need for synchronization because of the SocketClientList array.  Different threads will need to add and remove CSocketClient object references from this array at possibly the same time.  This object property will be used in a Monitor object to provide critical section support to the required member methods.


private Object m_pSocketListCS;
  /// <summary> RefType: A synchronization object to protect the class state </summary>
  private Object GetSocketListCS { get { return m_pSocketListCS; } set { m_pSocketListCS = value; } }


The last private property 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 is disposing </summary>
  private Boolean IsDisposed { get { return m_bDisposeFlag; } set { m_bDisposeFlag = value; } }


Public Properties Section


We need to add three public properties.


This property maintains the ipaddress of a local nic card used to listen for client socket connections.  If you want to listen on multiple nic cards you will need to instantiate multiple CSocketServer objects, one for each card.  This property can store a host name or an ipaddress.  If this value is a host name it must be resolvable by a DNS lookup.


private String m_strIpAddress;
  /// <summary> RefType: The IpAddress to either connect to or listen on </summary>
  public String GetIpAddress { get { return m_strIpAddress; } set { m_strIpAddress = value; } }


This property maintains the port the server is to listen for client socket connections on.  You must make sure the port is currently not in use.  To manually check if a port is in use, run the “netstat –a” command from the command line.
     
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 server, he can use this property.
     
private Object m_pUserArg;
  /// <summary> RefType: A reference to a user defined object to be passed through the handler functions </summary>
  public Object GetUserArg { get { return m_pUserArg; } set { m_pUserArg = value; } }


Constructor, Finalize, and Dispose Section


In the constructor, all we need to do is instantiate the object for the critical section property and set the dispose property flag to false.


//********************************************************************
/// <summary> Constructor </summary>
public CSocketServer()
{
  // Create the critical section object
  GetSocketListCS = new Object(); 
  // Init the dispose flag
  IsDisposed = false;
}


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


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 Accept thread is terminated.  The Stop method performs that action as you will see later in the public methods section.
  
//********************************************************************
/// <summary> Dispose function to shutdown the SocketManager </summary>
public void Dispose()
{
  try
  {
    // Mark the object as disposed
    IsDisposed = true; 
    // Stop the server if the thread is running
    if (GetAcceptThread != null)
      Stop();
  }
  catch
  {
  }
}


Private Methods Section


We need to add two private methods.


The LocateSocketIndex method is used to locate an empty slot in the SocketClientList property array.  This is used by the Accept thread to save a reference to a newly instantiated CSocketClient object.
 
In the LocateSocketIndex method, a local simple type variable called iSocket is set to the max number of client connections the CSocketServer object is allowed to accept.  This is because the method returns the value of iSocket at the end of the method.  If no slots are available, we need to return the max client connections value to indicate the maximum number of client connections currently exist.


Next, we wait to enter the Monitor object.  We only want one thread accessing the SocketClientList property array at a time.  In the call to Monitor.Enter, we pass the SocketList object property to protect the SocketClientList property array.   Now we safely search the SocketClientList property array for an empty slot.  Once we find an empty slot, we break out of the loop.  After the loop, we call Monitor.Exit passing the SocketList object property again and return the value of iSocket.


Notice how the calls to Monitor.Enter and Monitor.Exit are outside the try and catch statements.  This is done to make sure that if we successfully enter the critical section we absolutely exit from it.  If you do not call Monitor.Exit after passing through Monitor.Enter, no other threads will ever get through the critical section again.  Those threads waiting to pass through the Monitor.Enter call will block forever.  It is important to always make sure Monitor.Exit is called and only called once.  I have found this to be the most efficient and safest way to guarantee critical sections get released properly.


//********************************************************************
/// <summary> Function to locate an empty slot in the client socket list array </summary>
/// <returns> Will return the index slot or user defined MaxClientConnections if none found </returns>
private Int32 LocateSocketIndex()
{
  Int32 iSocket = GetMaxClientConnections;
  Monitor.Enter(GetSocketListCS);
  try
  {
    // Find an empty slot in the list
    for (iSocket = 0; iSocket < GetMaxClientConnections; ++iSocket)
      if (GetSocketClientList[iSocket] == null)
        break;
  }  
  catch
  {
  }
  Monitor.Exit(GetSocketListCS);
  return iSocket;
}


The AcceptThread method is spawned as a worker thread.  It performs most of the work for the CSocketServer class.  The job of the AcceptThread method is to listen for incoming client socket connection requests and accept those requests.  Once a client socket connection request is accepted by the call to the TcpListener AcceptSocket method, the Accept thread instantiates a CSocketClient object. 


The new CSocketClient object exists to encapsulate the accepted socket connection.  We then store a reference to the CSocketClient object in the SocketClientList property array.  Once these activities are complete, then we call the Accept Handler.  This allows the application developer to be notified a client socket connection request has been accepted.


In both of the catch statements, we call the Error Handler.  This notifies the application developer to be notified about an error accepting a socket client connection.  In the catch statement that catches the System.Net.Sockets.SocketException object, we check for error code 10004. 


This error code means that a socket blocking call was cancelled.  We get this error when the TcpListener property is stopped.  In this case, we do not call the Error Handler since this is a known error that is unavoidable.  In both catch statements, we close the socket client connection if we accepted the connection before the error.


//********************************************************************
/// <summary> Function to process and accept socket connection requests </summary>
private void AcceptThread()
{
  Socket pClientSocket = null;
  try
  {
    // Create a new TCPListner and start it up
    GetTcpListener = new TcpListener(Dns.Resolve(GetIpAddress).AddressList[0],GetPort);
    GetTcpListener.Start();
    for (;;)
    {
      // If a client connects, accept the connection
      pClientSocket = GetTcpListener.AcceptSocket();
      if (pClientSocket.Connected)
      {
        // Locate a socket index
        Int32 iSocketIndex = LocateSocketIndex();
        // If we got a valid index
        if (iSocketIndex != GetMaxClientConnections)
        {
          // Create a SocketClient object
          GetSocketClientList[iSocketIndex] = new CSocketClient(this,
            pClientSocket,                                           // The socket object for the connection
            iSocketIndex,                                            // The index into the SocketClientList
            pClientSocket.RemoteEndPoint.ToString().Substring(0,15), // The IpAddress of the client
            GetPort,                                                 // The port the client connected to
            GetSizeOfRawBuffer,                                      // The size of the byte array for storing messages
            GetUserArg,                                              // Application developer state
            new CSocketClient.MESSAGE_HANDLER(GetMessageHandler),    // Application developer Message Handler
            new CSocketClient.CLOSE_HANDLER(GetCloseHandler),        // Application developer Close Handler
            new CSocketClient.ERROR_HANDLER(GetErrorHandler));       // Application developer Error Handler
          // Call the Accept Handler
          GetAcceptHandler(GetSocketClientList[iSocketIndex]);
        }
        else
        {
          // Call the Error Handler
          GetErrorHandler(null, new Exception("Unable To Accept Socket Connection"));
          // Close the socket connection
          pClientSocket.Close();
        }
      }
    }
  }
  catch (System.Net.Sockets.SocketException e)
  {
    // Did we stop the TCPListener
    if (e.ErrorCode != 10004)
    {
      // Call the error handler
      GetErrorHandler(null, e);
      // Close the socket down if it exists
      if (pClientSocket != null)
        if (pClientSocket.Connected)
          pClientSocket.Close();
    }
  }
  catch (Exception e)
  {
    // Call the error handler
    GetErrorHandler(null, e);
    // Close the socket down if it exists
    if (pClientSocket != null)
      if (pClientSocket.Connected)
        pClientSocket.Close();
  }
}


Public Methods Section


We need to add one public method.


The RemoveSocket method is used to remove a reference of a CSocketClient object from the SocketClientList property array.  A reference to a CSocketClient object is passed to the RemoveSocket method.  We first need to check to make sure the reference to the passed CSocketClient object is valid.  We do this by comparing the reference passed into RemoveSocket with the reference located in the SocketClientList property array.  We determine which slot to look at in the SocketClientList array by using the slot the passed CSocketClient object says it’s located in. 


If these references match, we de-reference the object from the SocketClientList property array.  You may have noticed that the CSocketClient class currently does not have a public property called GetSocketListIndex.  We will be adding the public property to the CSocketClient class later.  Again the critical section is used to protect the SocketClientList property array.  The calls to Monitor.Enter and Monitor.Exit are outside the try and catch statements.


//********************************************************************
/// <summary> Funciton to remove a socket from the list of sockets </summary>
/// <param name="iSocketListIndex"> SimType: The index of the socket to remove </param>
public void RemoveSocket(CSocketClient pSocketClient)
{
  Monitor.Enter(GetSocketListCS);
  try
  {
    // Is the supplied CSocketClient object valid
    if (pSocketClient == GetSocketClientList[pSocketClient.GetSocketListIndex])
    {
      // Set the slot to null
      GetSocketClientList[pSocketClient.GetSocketListIndex] = null;
    }
  }
  catch
  {
  }
  Monitor.Exit(GetSocketListCS);
}


CSocketClient Updates


Now we will add some new properties and methods to the CSocketClient class to support the CSocketServer class.


Private Properties Section


We need to add one private property.


This property maintains a reference back to the owning CSocketServer object.  We need this reference to call the CSocketServer RemoveSocket method from the CSocketClient Dispose method.


private CSocketServer m_pSocketServer;
  /// <summary> RefType: The SocketServer for this socket object </summary>
  private CSocketServer GetSocketServer { get { return m_pSocketServer; } set { m_pSocketServer = value; } }


Public Properties Section


We need to add two public properties.


This property maintains the index where this object is located in the CSocketServer SocketClientList array.


private Int32 m_iSocketListIndex;
  /// <summary> SimType: Index into the Socket List Array </summary>
  public Int32 GetSocketListIndex { get { return m_iSocketListIndex; } set { m_iSocketListIndex = value; } }


This property maintains a reference to the socket object created by the TcpListener property when the CSocketServer Accept thread accepts a client socket connection request.  This property will be passed to the NetworkStream property instead of the TcpClient property when the CSocketServer class instantiates a CSocketClient object.


private Socket m_pClientSocket;
  /// <summary> RefType: The socket for the client connection </summary>
  public Socket GetClientSocket { get { return m_pClientSocket; } set { m_pClientSocket = value; } }


Constructor, Finalize, and Dispose Section


We need to add a new constructor method to support the CSocketServer class.  This overloaded constructor method takes 10 arguments.  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 this version of the constructor method, we need to do all of the things we did as in the first version plus a few new things.  First, we need to save the reference to the CSocketServer object that created this CSocketClient object.  Next, we save the reference to the socket for the accepted connection and the index where this object can be located in the CSocketServer SocketClientList array.  Now we need to save the ipaddress and port of the client that connected to us.


In the CSocketClient Connect method we made the following call.


GetNetworkStream = GetTcpClient.GetStream();


This associated the new socket connection we established with the NetworkStream property so we could process socket messages asynchronously.  The Connect method will never be called in this context because the connection is already established.  In this case, we need to associate the socket being passed in with the NetworkStream property.


GetNetworkStream = new NetworkStream(GetClientSocket);


This statement above is used here in the constructor to associate the socket being passed in with the NetworkStream property.


The last thing to add is the socket options.  Since the Connect method will never be called, we need to set the socket options here.  We set all the same options we did in connect.


//********************************************************************
/// <summary> Constructor for SocketServer Suppport </summary>
/// <param name="pSocketServer"> RefType: A Reference to the parent SocketServer </param>
/// <param name="pClientSocket"> RetType: The Socket object we are encapsulating </param>
/// <param name="iSocketListArray"> SimType: The index of the SocketServer Socket List Array </param>
/// <param name="strIpAddress"> RetType: The IpAddress of the remote server </param>
/// <param name="iPort"> SimType: The Port of the remote server </param>
/// <param name="pfnMessageHandler"> DelType: Reference to the user defined message handler function </param>
/// <param name="pfnCloseHandler"> DelType: Reference to the user defined close handler function </param>
/// <param name="pfnErrorHandler"> DelType: Reference to the user defined error handler function </param>
/// <param name="iSizeOfRawBuffer"> SimType: The size of the raw buffer </param>
/// <param name="pUserArg"> RefType: A Reference to the Users arguments </param>
public CSocketClient(CSocketServer pSocketServer, Socket pClientSocket, Int32 iSocketListArray, String strIpAddress, Int16 iPort,
                     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 functions
  GetMessageHandler = pfnMessageHandler;
  GetCloseHandler   = pfnCloseHandler;
  GetErrorHandler   = pfnErrorHandler;
  // Set the async socket function handlers
  GetCallbackReadFunction = new AsyncCallback(ReceiveComplete);
  GetCallbackWriteFunction = new AsyncCallback(SendComplete);
  // Init the dispose flag
  IsDisposed = false;
  // Set reference to SocketServer
  GetSocketServer = pSocketServer;
  // Init the socket references
  GetClientSocket    = pClientSocket;
  GetSocketListIndex = iSocketListArray;
  // Set the Ipaddress and Port
  GetIpAddress = strIpAddress;
  GetPort      = iPort;
  // Init the NetworkStream reference
  GetNetworkStream = new NetworkStream(GetClientSocket);
  // Set these socket options
  GetClientSocket.SetSocketOption(System.Net.Sockets.SocketOptionLevel.Socket, System.Net.Sockets.SocketOptionName.ReceiveBuffer, 1048576);
  GetClientSocket.SetSocketOption(System.Net.Sockets.SocketOptionLevel.Socket, System.Net.Sockets.SocketOptionName.SendBuffer, 1048576);
  GetClientSocket.SetSocketOption(System.Net.Sockets.SocketOptionLevel.Socket, System.Net.Sockets.SocketOptionName.DontLinger, 1);
  GetClientSocket.SetSocketOption(System.Net.Sockets.SocketOptionLevel.Tcp,    System.Net.Sockets.SocketOptionName.NoDelay, 1);
  // Wait for a message
  Receive();
}


We need to make one change to the Dispose method.  We need to add the call to the CSocketServer RemoveSocket method.


// Remove the socket from the list
if (GetSocketServer != null)
  GetSocketServer.RemoveSocket(this);


This is added after the catch statement to guarantee the call is made.  This will remove the reference to this object from the CSocketServer SocketClientList array.


//********************************************************************
/// <summary> Dispose </summary>
public void Dispose()
{
  try
  {
    // Flag that dispose has been called
    IsDisposed = true;
    // Disconnect the client from the server
    Disconnect();
  }
  catch
  {
  }
  // Remove the socket from the list
  if (GetSocketServer != null)
    GetSocketServer.RemoveSocket(this);
}


The last change is to the Disconnect method.  Here we need to add the following code.


if (GetClientSocket != null)
    GetClientSocket.Close();
GetClientSocket = null;


This code will close the socket and remove the reference this class has to the memory so garbage collection can clean up.


//********************************************************************
/// <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();
  if (GetClientSocket != null)
    GetClientSocket.Close();
  // Clean up the connection state
  GetClientSocket  = null;
  GetNetworkStream = null;
  GetTcpClient     = null;
}



Page 3: Sample Application Update

Now it is time to test our CSocketServer class.  Open the class1.cs file and go to the bottom.  Add a new method called MessageHandlerServer.  This method is called when we receive a message from a socket client connection.  In this method we will send back to the client a HTTP HTML command.


//********************************************************************
/// <summary> Called when a message is extracted from the socket </summary>
/// <param name="pSocket"> The SocketClient object the message came from </param>
/// <param name="iNumberOfBytes"> The number of bytes in the RawBuffer inside the SocketClient </param>
static public void MessageHandlerServer(CSocketClient pSocket, Int32 iNumberOfBytes)
{
  try
  {
    // Find a complete message
    String strMessage = System.Text.ASCIIEncoding.ASCII.GetString(pSocket.GetRawBuffer, 0, iNumberOfBytes);
    Console.WriteLine(strMessage);
    // Send the following HTTP command back
    String strServerResponse = "HTTP/1.1 200 OK\n" +
      "Date: Tue, 18 Feb 2003 18:47:39 GMT\n" +
      "Server: Apache/1.3.27 (Unix)  (Red-Hat/Linux) mod_perl/1.24_01 PHP/4.2.2 FrontPage/5.0.2 mod_ssl/2.8.12 OpenSSL/0.9.6b\n" +
      "Last-Modified: Sat, 25 Jan 2003 21:45:30 GMT\n" +
      "ETag: \"2201ab-11cd-3e33057a\"\n" +
      "Accept-Ranges: bytes\n" +
      "Content-Length: 53\n" +
      "Keep-Alive: timeout=15, max=100\n" +
      "Connection: Keep-Alive\n" +
      "Content-Type: text/html\n\n" +
      "<html> <body> <h1> Hello World! </h1> </body> </html>\n";
    pSocket.Send(strServerResponse);       
  }
  catch (Exception pException)
  {
    Console.WriteLine(pException.Message);
  }
}


Add the AcceptHandler next.  This is allow us to be notified when a connection is established to our server.


//********************************************************************
/// <summary> Called when a socket connection is accepted </summary>
/// <param name="pSocket"> The SocketClient object the message came from </param>
static public void AcceptHandler(CSocketClient pSocket)
{
  Console.WriteLine("Accept Handler");
  Console.WriteLine("IpAddress: " + pSocket.GetIpAddress);
}


The TestServer method will be used to start the server.  We will listen for connections on port 9000 using the network card configured under our machine name.
   
//********************************************************************
/// <summary> Function to test the CSocketServer class </summary>
static void TestServer()
{
  try
  {
    // Instantiate a CSocketServer object
    CSocketServer pSocketServer = new CSocketServer();
    // Start listening for connections
    pSocketServer.Start(System.Environment.MachineName, 9000, 1024, 10240, null,
      new CSocketServer.MESSAGE_HANDLER(MessageHandlerServer),
      new CSocketServer.ACCEPT_HANDLER(AcceptHandler),
      new CSocketServer.CLOSE_HANDLER(CloseHandler),
      new CSocketServer.ERROR_HANDLER(ErrorHandler));
    Console.WriteLine("Waiting for a client connection on Machine: {0} Port: {1}", System.Environment.MachineName, 9000);
    // Stay here until you are ready to shutdown the server   
    Console.ReadLine();
    pSocketServer.Dispose();
  }
  catch (Exception pException)
  {
    Console.WriteLine(pException.Message);
  }
}


In the Main method, comment out the call to TestClient and add the call to TestServer.  Now we are ready to start our server and test it. 


//*******************************************************************
/// <summary> Function to test the CSocketClient class </summary>
static void Main(string[] args)
{
  // Test the CSocketClient class
  // TestClient();


// Test the CSocketClient class
  TestServer();
}


Start the server and launch your browser.  Our server is listening for connections on our local machine on port 9000.  You need the machine name or ipaddress of our local machine.  In the address bar type the following: http://machinename:9000 .  This will have the browser connect to our server on port 9000 and our server will send the browser back the HTML statement that will display Hello World on the browser.




Page 4: Review

So now we have the ability to add socket client and server support to our applications easily and quickly.  In the client sample application, all we needed to do was to instantiate a CSocketClient object.  Then we can decide which server we want to connect to and code the handler functions appropriately.  


In the server sample application, all we needed to do was to instantiate a CSocketServer object.  Then we can decide which ipaddress and port to listen for socket client requests and code the handler functions appropriately.




For more great programming articles, please visit http://www.devarticles.com. If you have an opinion or question about this article, then please post it at the devArticles developer forum, which is located at http://www.devarticles.com/forum

本文地址:http://com.8s8s.com/it/it45719.htm