Asynchronous TCP IP Client socket C#

Post Reply
ajack
Posts: 44
Joined: Wed Feb 22, 2012 12:01 am

Asynchronous TCP IP Client socket C#

Post by ajack »

Dear all,

Thanks for all your help in forum (especially Adam), I can already communicate between my device with HMI through TCP IP using Asynchronous TCP IP Client socket C# using System.Net.Sockets. My Message is also has <EOF> so that the client can know when server send completed Message.

However, I'm facing another challenge: Half-Open (Dropped) Connection due to:
- Network cable unplugged.
- Process crash.
- Computer crash.

you can find more info in this link: https://www.codeproject.com/Articles/37 ... cket-Conne

Therefore, I do want to understand the method to:
- Detect whether the connection is dropped.
- Reconnect to Server once the connection is ready.

I really appreciate for your thought or any contribution.

Thanks and best regards,
Phong Duong

AMitchneck
Posts: 137
Joined: Mon Jun 11, 2012 2:10 pm

Re: Asynchronous TCP IP Client socket C#

Post by AMitchneck »

Hi Phong,

The answer to this question was actually buried in the Modbus Client class I sent you (http://ixtalk.beijerelectronics.com/vie ... 72c489fe97). When a TCP connection is half-dropped the underlying Socket.Connected property still returns true as it has no proof otherwise. The way to determine the socket is no longer connected is to try to send data over the connection. If the connection is broken or breaks, the send function returns 0. To detect this, instead of using the socket's send/receive functions directly, the class uses the functions SendAll and ReceiveAll. For your reference, I've copied the SendAll below:

Code: Select all

private bool SendAll(byte[] buffer)
{
   int i, bytesSent;
   
   if (buffer == null) throw new ArgumentNullException("buffer");
   if (!Connected) return false;
   
   AsyncState asyncState = new AsyncState(sock);
   for (i = 0; i < buffer.Length; i += bytesSent)
   {
      try
      {
         if (sock.Poll(5000000, SelectMode.SelectWrite)) // use send timeout of 5 seconds
         {
            asyncState.Reset();
            sock.BeginSend(buffer, i, buffer.Length - i, SocketFlags.None, ProcessSend, asyncState);
            if (asyncState.WaitForReturn(10000)) // use send timeout of 10 seconds
               bytesSent = asyncState.result;
            else
               bytesSent = 0; // send timed out
         }
         else
         {
            bytesSent = 0; // send timed out
         }
      }
      catch (SocketException) { bytesSent = 0; }
      catch (ObjectDisposedException) { bytesSent = 0; }
      if (bytesSent == 0)
      {
         // no data sent -> connection lost
         Close(); // close socket
         return false;
      }
   }
   
   return true;
}
private void ProcessSend(IAsyncResult asyncResult)
{
   AsyncState asyncState = (AsyncState)(asyncResult.AsyncState);
   try { asyncState.result = asyncState.socket.EndSend(asyncResult); }
   catch (SocketException) { asyncState.result = 0; }
   catch (ArgumentException) { asyncState.result = 0; }
   catch (InvalidOperationException) { asyncState.result = 0; }
   finally { asyncState.Set(); }
}
How this function works is it first checks the Sockets connection status. If not connected, it quits. It then checks the Sockets send buffer to see if it is ready to send data. If not, it waits up to 5 seconds - if still not ready, it closes the Socket as it is assumed to be frozen. Once the send is ready, it goes to send data giving another timeout of 10 seconds. If the socket send function every returns 0 (meaning connection lost), an underlying socket error occurs, or a timeout occurs, then the function closes the socket as the connection has either been lost or there is a problem with the underlying Socket (also, I will note, closes the Socket terminates the asynch operation processing the send in case it did not complete in time). Thus, you can guarantee when this function returns the Socket is connected (and the data was sent) and that when it returns false the Socket is not connected.
The ReceiveAll works in a similar fashion.
Adam M.
Controls Engineer
FlexEnergy

ajack
Posts: 44
Joined: Wed Feb 22, 2012 12:01 am

Re: Asynchronous TCP IP Client socket C#

Post by ajack »

Hi Adam,

Thanks for your support and quick reply as usual!

However, currently, my Server randomly send data to Client (which is my HMI). Therefore, I couldn't rely on 5s or some Polling Method.

I think about using Heartbeat to communicate with my Server and Client so that both device know when the connection is dropped.

Best regards,
Phong Duong

AMitchneck
Posts: 137
Joined: Mon Jun 11, 2012 2:10 pm

Re: Asynchronous TCP IP Client socket C#

Post by AMitchneck »

Hi Phong,

The timeout in the send function is based on the ability of the underlying Socket, not so much what it is being used for so this should not matter. The receive however does. In this case, what I've done in the past is to peek if there is data available before I use the ReceiveAll function.

Both of these methods can be used to check if data is available to read.

Code: Select all

if (sock.Poll(0, SelectMode.SelectRead))
{
   // data is available to be read
}

Code: Select all

if (sock.Available > 0)
{
   // data is available to be read
}
Adam M.
Controls Engineer
FlexEnergy

ajack
Posts: 44
Joined: Wed Feb 22, 2012 12:01 am

Re: Asynchronous TCP IP Client socket C#

Post by ajack »

Hi Adam,

Honestly, I'm not familiar with C# or even HMI. Due to current requirement of our project, I need to do programming and HMI design by myself. Therefore, everything is new to me (including Thread, or Sockets).

Back to the TCP Client Send/Receive, I just wonder why you use Async for Send but Sync for Receive?

Thanks,

ajack
Posts: 44
Joined: Wed Feb 22, 2012 12:01 am

Re: Asynchronous TCP IP Client socket C#

Post by ajack »

For your reference, this is the TCPClient Script I wrote:

Code: Select all

namespace Neo.ApplicationFramework.Generated
{
    using System.Windows.Forms;
    using System;
	using System.Net;
	using System.Net.Sockets;
	using System.Text;
	using System.Threading;
    using System.Drawing;
    using Neo.ApplicationFramework.Tools;
    using Neo.ApplicationFramework.Common.Graphics.Logic;
    using Neo.ApplicationFramework.Controls;
    using Neo.ApplicationFramework.Interfaces;
    
    
    public partial class TCPClient
    {
		public EthernetTCP.AsynchronousClient ethernetClient = new EthernetTCP.AsynchronousClient();		

		public void Start()
		{
			ethernetClient.StartConnection();
		}
		
		public void Stop()
		{
			ethernetClient.CloseConnection();
		}
		
		public void SendData(string msg)
		{
			ethernetClient.SendData(msg);
		}
		
		public string ReceiveData()
		{
			return ethernetClient.ReceiveData();
		}

    }

	public partial class EthernetTCP
	{
		public const int port = 2112;
		public const string ip = "192.168.100.102";
		public const string eof = "|1";
  
		public class StateObject 
		{  
			// Client socket.  
			public Socket workSocket = null;  
			// Size of receive buffer.  
			public const int BufferSize = 256;  
			// Receive buffer.  
			public byte[] buffer = new byte[BufferSize];  
			// Received data string.  
			public StringBuilder sb = new StringBuilder();  
		} 
		
		public class AsynchronousClient
		{
			private static ManualResetEvent connectDone = new ManualResetEvent(false);  
			private static ManualResetEvent sendDone = new ManualResetEvent(false);  
			private static ManualResetEvent receiveDone = new ManualResetEvent(false);
			
			// The response from the remote device.  
			private static string response = String.Empty;
			
			public Socket currentSocket;
						
			public void StartConnection()
			{
				try
				{
					IPEndPoint remoteEP = new IPEndPoint(IPAddress.Parse(ip), port);
					
					currentSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
					// Connect to the remote endpoint Asynchronous
					currentSocket.BeginConnect(remoteEP, new AsyncCallback(ConnectCallback), currentSocket);
					// connectDone.WaitOne();
					currentSocket.SetSocketOption(SocketOptionLevel.Socket,SocketOptionName.ReuseAddress, true);
				} 
				catch (Exception) 
				{
					//ConnectCallBack will throw an exception
				}
			}
			
			public void CloseConnection()
			{
				try
				{
					currentSocket.Shutdown(SocketShutdown.Both);
					currentSocket.Close();
					
					//for debug
					Globals.Tags.ConnectionStatus.Value = "TCP CLOSED";
				} 
				catch (ObjectDisposedException)
				{
					Globals.Tags.ConnectionStatus.Value = "Port has been closed";
				}
				catch(SocketException se)
				{
					if (se.ErrorCode == 10057)
						Globals.Tags.ConnectionStatus.Value = "CLOSE: Socket NOT connected";
					else if (se.ErrorCode == 10058)
						Globals.Tags.ConnectionStatus.Value = "CLOSE: Cannot send after socket shutdown";
					else if (se.ErrorCode == 10060)
						Globals.Tags.ConnectionStatus.Value = "CLOSE: Connection timed out";
					else
						Globals.Tags.ConnectionStatus.Value = "CLOSE: Error Code " + se.ErrorCode.ToString();
				}
			}
			
			public void SendData(String data)
			{
				// Convert the string data to byte data using ASCII encoding.  
				byte[] byteData = System.Text.Encoding.UTF8.GetBytes(data);  

				// Begin sending the data to the remote device.  
				currentSocket.BeginSend(byteData, 0, byteData.Length, 0, new AsyncCallback(SendCallback), currentSocket);
			}
			
			public string ReceiveData()
			{
				try 
				{  
					// Create the state object.  
					StateObject state = new StateObject();  
					state.workSocket = currentSocket;  
					// Begin receiving the data from the remote device.  
					currentSocket.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReceiveCallback), state);
				}
				catch (Exception)
				{
					// ReceiveCallBack will throw an Exception
				}
				
				return response;
			}
			
			private static void ConnectCallback(IAsyncResult ar)
			{  
				try 
				{  
					// Retrieve the socket from the state object.  
					Socket client = (Socket) ar.AsyncState;

					// Complete the connection.  
					client.EndConnect(ar);

					// Signal that the connection has been made.  
					connectDone.Set();
					
					//for debug
					Globals.Tags.ConnectionStatus.Value = "Connection has been made";
				} 
				catch (ObjectDisposedException)
				{
					Globals.Tags.ConnectionStatus.Value = "Connection Failed: No Server";
				}
				catch(SocketException se)
				{
					if (se.ErrorCode == 10057)
						Globals.Tags.ConnectionStatus.Value = "Connection Failed: Socket NOT connected";
					else if (se.ErrorCode == 10058)
						Globals.Tags.ConnectionStatus.Value = "Connection Failed: Cannot send after socket shutdown";
					else if (se.ErrorCode == 10060)
						Globals.Tags.ConnectionStatus.Value = "Connection Failed: Connection timed out";
					else
						Globals.Tags.ConnectionStatus.Value = "Connection Failed: " + se.ErrorCode.ToString();
				}
			}
			
			private static void SendCallback(IAsyncResult ar)
			{
				try
				{
					// Retrieve the socket from the state object.  
					Socket client = (Socket) ar.AsyncState;

					// Complete sending the data to the remote device.  
					int bytesSent = client.EndSend(ar);
					Globals.Tags.ConnectionStatus.Value = "Sent";

					// Signal that all bytes have been sent.  
					sendDone.Set();
				} 
				catch (ObjectDisposedException)
				{
					Globals.Tags.ConnectionStatus.Value = "Send Failed: No Connection";
				}
				catch(SocketException se)
				{
					if (se.ErrorCode == 10057)
						Globals.Tags.ConnectionStatus.Value = "Send Failed: Socket NOT connected";
					else if (se.ErrorCode == 10058)
						Globals.Tags.ConnectionStatus.Value = "Send Failed: Cannot send after socket shutdown";
					else if (se.ErrorCode == 10060)
						Globals.Tags.ConnectionStatus.Value = "Send Failed: Connection timed out";
					else
						Globals.Tags.ConnectionStatus.Value = "Send Failed: " + se.ErrorCode.ToString();
				}
			}
			
			private static void ReceiveCallback( IAsyncResult ar ) 
			{
				string content = String.Empty;
				
				try 
				{  
					// Retrieve the state object and the client socket
					// from the asynchronous state object
					StateObject _state = (StateObject) ar.AsyncState;
					Socket _client = _state.workSocket;

					// Read data from the remote device.  
					int bytesRead = _client.EndReceive(ar);
					
					if (bytesRead > 0)
					{
						_state.sb.Append(Encoding.UTF8.GetString(_state.buffer, 0, bytesRead));
						content = _state.sb.ToString();
						
						// Check for end-of-file tag. If it is not there, read more data
						if (content.IndexOf(eofOK) != -1)
						{
							// All the data has arrived; put it in response
							response = content.Substring(0, content.Length-2);
							_state.sb.Length = 0;
							// Signal that all bytes have been received
							receiveDone.Set();
							Globals.Tags.ConnectionStatus.Value = "Received";
							Globals.Tags.Counter.Value++;
						}
						else
						{
							Globals.Tags.ConnectionStatus.Value = "Long message...";
							_client.BeginReceive(_state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReceiveCallback), _state);
						}
					}
					
				}
				catch (ObjectDisposedException)
				{
					Globals.Tags.ConnectionStatus.Value = "Receive Failed: No Connection";
				}
				catch(SocketException se)
				{
					if (se.ErrorCode == 10057)
						Globals.Tags.ConnectionStatus.Value = "Receive Failed: Socket NOT connected";
					else if (se.ErrorCode == 10058)
						Globals.Tags.ConnectionStatus.Value = "Receive Failed: Cannot send after socket shutdown";
					else if (se.ErrorCode == 10060)
						Globals.Tags.ConnectionStatus.Value = "Receive Failed: Connection timed out";
					else
						Globals.Tags.ConnectionStatus.Value = "Receive Failed: " + se.ErrorCode.ToString();
				}
			}
		}
	}
}
I use Tag "ConnectionStatus" for debug

AMitchneck
Posts: 137
Joined: Mon Jun 11, 2012 2:10 pm

Re: Asynchronous TCP IP Client socket C#

Post by AMitchneck »

Hi Phong,

To answer your question, I used async method for send to enable a send timeout ability. As for receive, I used sync method because I already know the receive method will not block as I check for available data (via poll) before I attempt to read.
Adam M.
Controls Engineer
FlexEnergy

ajack
Posts: 44
Joined: Wed Feb 22, 2012 12:01 am

Re: Asynchronous TCP IP Client socket C#

Post by ajack »

Thanks so much for your help!

Pawarnikhil
Posts: 1
Joined: Thu Nov 14, 2019 2:31 am
Contact:

Re: Asynchronous TCP IP Client socket C#

Post by Pawarnikhil »

Great share. It was helpful for me. Keep sharing!

Post Reply