Asynchronous Socket Utility Classes – Part I

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

Asynchronous Socket Utility Classes – Part I

Author: William Kennedy
Date Added: 28th Mar 2003
Type: Tutorial
Rating: Average visitor rating of 10 out of 10Average visitor rating of 10 out of 10Average visitor rating of 10 out of 10Average visitor rating of 10 out of 10Average visitor rating of 10 out of 10Average visitor rating of 10 out of 10Average visitor rating of 10 out of 10Average visitor rating of 10 out of 10Average visitor rating of 10 out of 10Average visitor rating of 10 out of 10

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


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 client 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.


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 clients.  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

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



Page 3: Sample Application 1

Now that we are done constructing our Socket Client class, we need to test how it works.  The first thing to do is open the Class1.cs file.  Add the SocketSystem namespace so we can access our new class CSocketClient.


using SocketSystem;


Now add the following static public functions to the Class1 class after the static Main function.


//********************************************************************
/// <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 MessageHandlerClient(CSocketClient pSocket, Int32 iNumberOfBytes)
{
  try
  {
    // Convert the message from a byte array to a string
    String strMessage = System.Text.ASCIIEncoding.ASCII.GetString(pSocket.GetRawBuffer, 0, iNumberOfBytes);
       
    // Display the string to the console window
    Console.WriteLine(strMessage);
  }
  catch (Exception pException)
  {
    Console.WriteLine(pException.Message);
  }
}
//********************************************************************
/// <summary> Called when a socket connection is closed </summary>
/// <param name="pSocket"> The SocketClient object the message came from </param>
static public void CloseHandler(CSocketClient pSocket)
{
  Console.WriteLine("Close Handler");
  Console.WriteLine("IpAddress: " + pSocket.GetIpAddress);
}
//********************************************************************
/// <summary> Called when a socket error occurs </summary>
/// <param name="pSocket"> The SocketClient object the message came from </param>
/// <param name="pException"> The reason for the error </param>
static public void ErrorHandler(CSocketClient pSocket, Exception pException)
{
  Console.WriteLine(pException.Message);
}


Implement a function called TestClient.  I thought it would be fun to use the CSocketClient class to connect to a web server and pretend our application was a browser.  So what we will do is connect to the web server hosting www.continuumtechnologycenter.com, issue a HTTP Get command, and see what comes back through the Message Handler.


//*******************************************************************
/// <summary> Function to test the CSocketClient class </summary>
static void TestClient()
{
  try
  {
    // Instantiate a CSocketClient object
    CSocketClient pSocketClient = new CSocketClient(10240, null,
      new CSocketClient.MESSAGE_HANDLER(MessageHandlerClient),
      new CSocketClient.CLOSE_HANDLER(CloseHandler),
      new CSocketClient.ERROR_HANDLER(ErrorHandler));
    // Generate the HTTP Get command to send to the server
    String strBrowserRequest = "GET / HTTP/1.1\n" +
      "Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-excel, application/msword, */*\n" +
      "Accept-Language: en-us\n" +
      "Accept-Encoding: gzip, deflate\n" +
      "User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; .NET CLR 1.0.3705)\n" +
      "Host:
www.continuumtechnologycenter.com:80\n
" +
      "Connection: Keep-Alive\n\n";
    // Establish a connection to the server hosting
www.continuumtechnologycenter.com
    pSocketClient.Connect("www.continuumtechnologycenter.com
", 80);
    // Send the HTTP Get command
    pSocketClient.Send(strBrowserRequest);
    Console.ReadLine();
    pSocketClient.Disconnect();
    Console.ReadLine();
    pSocketClient.Dispose();
  }
  catch (Exception pException)
  {
    Console.WriteLine(pException.Message);
  }
}


Finallly add the following code to the Main function.


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


Build and run the application.  You should get the following




Page 4: Review

So now you have the ability to add socket client support to your applications easily and quickly.  In your sample application all you need to do is decide which server you want to connect to 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://www.devarticles.com/art/1/492



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