Click here to Skip to main content
15,896,557 members
Articles / Programming Languages / C#

Solution for TCP/IP client socket message boundary problem

Rate me:
Please Sign up or sign in to vote.
4.64/5 (16 votes)
10 Oct 20054 min read 125.5K   3.6K   46  
Solution for unprotected TCP/IP message boundary problem.
using System;
using System.Net;
using System.Collections;
using System.Net.Sockets;
using System.Threading;
using System.Windows.Forms;
namespace Sockets
{
	/// <summary> 
	/// This class abstracts a socket 
	/// </summary>
	public class SocketClient : SocketBase
	{
		private Queue messageQueue = new Queue();
		private byte[] theRest;
		private bool m_Connected = false;

		#region Private Properties
		/// <summary>
		/// A network stream object 
		/// </summary>
		private NetworkStream networkStream;
		/// <summary>
		/// A TcpClient object for socket connection 
		/// </summary>
		private TcpClient tcpClient;
		/// <summary>
		/// A callback object for processing recieved socket data 
		/// </summary>
		private AsyncCallback callbackReadMethod;
		/// <summary>
		/// A callback object for processing send socket data
		/// </summary>
		private AsyncCallback callbackWriteMethod;
		/// <summary>
		/// Size of the receive buffer. Defaults to 1048576
		/// </summary>
		private int receiveBufferSize = 1048576;
		/// <summary>
		/// Size of the send buffer. Defaults to 1048576
		/// </summary>
		private int sendBufferSize = 1048576;
		/// <summary>
		/// The SocketServer for this socket object
		/// </summary>
		private SocketServer socketServer;

		public SocketServer SocketServer
		{
			get{return socketServer;}
		}

		/// <summary>
		/// The socket for the client connection 
		/// </summary>
		private Socket clientSocket;

		#endregion Private Properties

		#region Constructor & Destructor

		/// <summary> 
		/// Constructor for client support
		/// </summary>
		/// <param name="sizeOfRawBuffer"> The size of the raw buffer </param>
		/// <param name="userArg"> A Reference to the Users arguments </param>
		/// <param name="messageHandler">  Reference to the user defined message handler method </param>
		/// <param name="closeHandler">  Reference to the user defined close handler method </param>
		/// <param name="errorHandler">  Reference to the user defined error handler method </param>
		public SocketClient(int sizeOfRawBuffer, object userArg, 
			MessageHandler messageHandler, CloseHandler closeHandler, 
			ErrorHandler errorHandler)
		{
			// Create the raw buffer
			this.SizeOfRawBuffer = sizeOfRawBuffer;
			this.RawBuffer  = new Byte[this.SizeOfRawBuffer];

			// Save the user argument
			this.userArg = userArg;

			// Set the handler methods
			this.messageHandler = messageHandler;
			this.closeHandler = closeHandler;
			this.errorHandler = errorHandler;

			// Set the async socket method handlers
			this.callbackReadMethod  = new AsyncCallback(ReceiveComplete);
			this.callbackWriteMethod = new AsyncCallback(SendComplete);

			this.m_Connected = true;
			// Init the dispose flag
			this.disposed = false;
		}

		/// <summary> Constructor for SocketServer Suppport </summary>
		/// <param name="socketServer"> A Reference to the parent SocketServer </param>
		/// <param name="clientSocket"> The Socket object we are encapsulating </param>
		/// <param name="socketListArray"> The index of the SocketServer Socket List Array </param>
		/// <param name="ipAddress"> The IpAddress of the remote server </param>
		/// <param name="port"> The Port of the remote server </param>
		/// <param name="messageHandler"> Reference to the user defined message handler function </param>
		/// <param name="closeHandler"> Reference to the user defined close handler function </param>
		/// <param name="errorHandler"> Reference to the user defined error handler function </param>
		/// <param name="sizeOfRawBuffer"> The size of the raw buffer </param>
		/// <param name="userArg"> A Reference to the Users arguments </param>
		public SocketClient(SocketServer socketServer, Socket clientSocket, 
			string ipAddress, int port, int sizeOfRawBuffer, 
			object userArg, MessageHandler messageHandler, 	CloseHandler closeHandler, 
			ErrorHandler errorHandler)
			:this(sizeOfRawBuffer,userArg, messageHandler,closeHandler,errorHandler)
		{

			// Set reference to SocketServer
			this.socketServer = socketServer;

			// Init the socket references
			this.clientSocket    = clientSocket;

			// Set the Ipaddress and Port
			this.ipAddress = ipAddress;
			this.port = port;

			// Init the NetworkStream reference
			this.networkStream = new NetworkStream(this.clientSocket);

			// Set these socket options
			this.clientSocket.SetSocketOption(System.Net.Sockets.SocketOptionLevel.Socket, 
				System.Net.Sockets.SocketOptionName.ReceiveBuffer, this.receiveBufferSize);
			this.clientSocket.SetSocketOption(System.Net.Sockets.SocketOptionLevel.Socket, 
				System.Net.Sockets.SocketOptionName.SendBuffer, this.sendBufferSize);
			this.clientSocket.SetSocketOption(System.Net.Sockets.SocketOptionLevel.Socket, 
				System.Net.Sockets.SocketOptionName.DontLinger, 1);
			this.clientSocket.SetSocketOption(System.Net.Sockets.SocketOptionLevel.Tcp,    
				System.Net.Sockets.SocketOptionName.NoDelay, 1);
			
			// Wait for a message
			Receive();
		}

		/// <summary> 
		/// Overloaded constructor for client support
		/// </summary>
		/// <param name="sendBufferSize"></param>
		/// <param name="receiveBufferSize"></param>
		/// <param name="sizeOfRawBuffer"> The size of the raw buffer </param>
		/// <param name="userArg"> A Reference to the Users arguments </param>
		/// <param name="messageHandler">  Reference to the user defined message handler method </param>
		/// <param name="closeHandler">  Reference to the user defined close handler method </param>
		/// <param name="errorHandler">  Reference to the user defined error handler method </param>
		public SocketClient(int sendBufferSize, int receiveBufferSize, 
			int sizeOfRawBuffer, 
			object userArg, 
			MessageHandler messageHandler,	
			CloseHandler closeHandler, 
			ErrorHandler errorHandler
			) : this(sizeOfRawBuffer,userArg, messageHandler,closeHandler,errorHandler)
		{
			//Set the size of the send/receive buffers
			this.sendBufferSize = sendBufferSize;
			this.receiveBufferSize = receiveBufferSize;
		}

		/// <summary> 
		/// Finialize 
		/// </summary>
		
		~SocketClient()
		{
			if (!this.disposed)
				Dispose();
		}

		/// <summary>
		/// Disposes of internal objects
		/// </summary>
		public override void Dispose()
		{
			try
			{
				// Flag that dispose has been called
				this.disposed = true;
				// Disconnect the client from the server
				Disconnect();
			}
			catch
			{
			}
			// Remove the socket from the list
			if (this.socketServer != null)
				this.socketServer.RemoveSocket(this);

			base.Dispose ();
		}


		#endregion Constructor & Destructor

		private void SaveTheRestofTheStream(byte[] src, int ptr, int TotalLen)
		{
			theRest = new byte[ TotalLen - ptr ];
			for(int i = 0; ptr< TotalLen; i++)
				theRest[i] = RawBuffer[ptr++];

			return;
		}
		private void ParseMessage(int TotalLength)
		{
			int ptr = 0;

			try 
			{
				while( ptr < TotalLength )
				{
					if( (TotalLength - ptr) <= 6)
					{
						SaveTheRestofTheStream(RawBuffer, ptr, TotalLength);
						return;
					}

					string len = System.Text.ASCIIEncoding.ASCII.GetString(RawBuffer, ptr, 6 );
					int iLen   = Int32.Parse( len );
					byte[] msg;

					if( iLen+6 > (TotalLength - ptr))
					{
						SaveTheRestofTheStream(RawBuffer, ptr, TotalLength);
						return;
					}

					ptr += 6;

					msg = new byte[ iLen ];
					for(int i = 0; i< iLen; i++)
						msg[i] = RawBuffer[ptr++];

					messageQueue.Enqueue( msg );
				}
			}
			catch(Exception e)
			{
				Console.WriteLine("Error:SocketClient: Got Exception while ParseMessage");
			}
		}

		/// <summary> 
		/// Called when a message arrives
		///  </summary>
		/// <param name="ar"> An async result interface </param>
		private void ReceiveComplete(IAsyncResult ar)
		{
			try
			{
				// Is the Network Stream object valid
				if (this.networkStream.CanRead)
				{
					// Read the current bytes from the stream buffer
					int bytesRecieved = this.networkStream.EndRead(ar);
					// If there are bytes to process else the connection is lost
					if (bytesRecieved > 0)
					{
						try
						{
							if(theRest != null)
							{
								int i;
								byte[] tmp = new byte[bytesRecieved + theRest.Length];

								for(i = 0; i<theRest.Length; i++)
									tmp[i] = theRest[i];

								for(int j = 0; j< bytesRecieved; j++)
									tmp[i++] = RawBuffer[j];

								RawBuffer = tmp;

								bytesRecieved = bytesRecieved + theRest.Length;

								theRest = null;
							}

							ParseMessage( bytesRecieved );

							if(messageQueue.Count > 0)
							{
								RawBuffer = (byte[])messageQueue.Dequeue();
								// A message came in send it to the MessageHandler
								this.messageHandler(this, RawBuffer.Length);
							}
						}
						catch( Exception e )
						{
							throw e;
						}
						// Wait for a new message

						if(this.m_Connected == true)
							Receive();
					}

				}
			}
			catch (Exception e)
			{
				Console.WriteLine("Error:SocketClient: Got Exception while ReceiveComplite");

				try
				{
					System.Diagnostics.Debugger.Break();
					// The connection must have dropped call the CloseHandler
					this.closeHandler(this);
				}
				catch
				{
				}
			}
		}

		/// <summary> 
		/// Called when a message is sent
		///  </summary>
		/// <param name="ar"> An async result interface </param>
		private void SendComplete(IAsyncResult ar)
		{
			try
			{
				// Is the Network Stream object valid
				if (this.networkStream.CanWrite)
					this.networkStream.EndWrite(ar);
			}
			catch (Exception)
			{
				Console.WriteLine("Error:SocketClient: Got Exception while SendComplete");
			}
		}

		/// <summary> 
		/// The socket for the client connection 
		/// </summary>
		public Socket ClientSocket
		{ 
			get 
			{ 
				return this.clientSocket; 
			} 
			set 
			{ 
				this.clientSocket = value; 
			} 
		}

		/// <summary> 
		/// Function used to connect to a server 
		/// </summary>
		/// <param name="strIpAddress"> The address to connect to </param>
		/// <param name="iPort"> The Port to connect to </param>
		public void Connect(String ipAddress, int port)
		{
			try
			{
				if (this.networkStream == null)
				{
					// Set the Ipaddress and Port
					this.IpAddress = ipAddress;
					this.Port = port;

					// Attempt to establish a connection
					this.tcpClient = new TcpClient(this.IpAddress, this.Port);
					this.networkStream = this.tcpClient.GetStream();

					// Set these socket options
					this.tcpClient.ReceiveBufferSize = this.receiveBufferSize;
					this.tcpClient.SendBufferSize = this.sendBufferSize;
					this.tcpClient.NoDelay = true;
					this.tcpClient.LingerState = new System.Net.Sockets.LingerOption(false,0);
					
					this.m_Connected = true;
					// Start to receive messages
					Receive();
				}
			}
			catch (System.Net.Sockets.SocketException e)
			{
				Console.WriteLine("Error:SocketClient: Got Exception while Connect:"+ e.Message);
				throw new Exception(e.Message, e.InnerException);
			}
		}

		/// <summary> 
		/// Function used to disconnect from the server 
		/// </summary>
		public virtual void Disconnect()
		{
			if(m_Connected == true)
			{
				// Close down the connection
				if (this.networkStream != null)
					this.networkStream.Close();
				if (this.tcpClient != null)
					this.tcpClient.Close();
				if (this.clientSocket != null)
					this.clientSocket.Close();
			
				// Clean up the connection state
				this.networkStream = null;
				this.tcpClient     = null;
				this.clientSocket  = null;

				this.m_Connected = false;
			}
		}

		/// <summary>
		///  Function to send a string to the server 
		///  </summary>
		/// <param name="message"> A string to send </param>
		public bool Send(string message)
		{
			Byte[] rawBuffer = System.Text.Encoding.ASCII.GetBytes(message.Length.ToString("000000") + message);
			return this.Send(rawBuffer);
		}

		/// <summary>
		///  Function to send a string to the server 
		///  </summary>
		/// <param name="message"> A string to send </param>
		virtual public bool SendNotification(string message)
		{
			return this.Send( message );
		}

		/// <summary> 
		/// Function to send a raw buffer to the server 
		/// </summary>
		/// <param name="rawBuffer"> A Raw buffer of bytes to send </param>
		public bool Send(Byte[] rawBuffer)
		{
			if ((this.networkStream != null) && (this.networkStream.CanWrite))
				//&& 
				//(clientSocket != null) && (this.clientSocket.Connected == true))
			{
				// Issue an asynchronus write
				this.networkStream.BeginWrite(rawBuffer, 0, rawBuffer.GetLength(0), this.callbackWriteMethod, null);
				return true;
			}
			else
				return false;
		}

		/// <summary> 
		/// Wait for a message to arrive
		/// </summary>
		public void Receive()
		{
			while(messageQueue.Count != 0)
			{
				RawBuffer = (byte[])messageQueue.Dequeue();
				
				this.messageHandler(this, RawBuffer.Length);
			}

			if ((this.networkStream != null) && (this.networkStream.CanRead))
			{
				this.RawBuffer  = new Byte[this.SizeOfRawBuffer];
				// Issue an asynchronous read
				this.networkStream.BeginRead(this.RawBuffer, 0, this.SizeOfRawBuffer, this.callbackReadMethod, null);
			}
			else
				throw new Exception("Socket Closed");
		}

	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Technical Lead Gaisman Ltd
Canada Canada
Over 10 years professional experience in software design and development with strong OOD and OOP skills. Extensive experience with C# and C/C++ programming. Through understanding of multi-tier, client-server technology. Full project cycle experience related to: user requirements formulation, system architecture design, implementation and support. Strong knowledge of algorithms, programming languages, operation systems, API’s, telecommunication protocols, distributed computing, Internet technologies.

Comments and Discussions