Welcome Guest ( Login | Register )



All times are UTC - 7 hours [ DST ]



Post new topic Reply to topic  [ 8 posts ] 
Author Message
 Post subject: Asynchronous TCP IP Client socket C#
PostPosted: Sat Jun 09, 2018 8:23 am 

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

Offline
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/37490/Detection-of-Half-Open-Dropped-TCP-IP-Socket-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


 Profile  
 
 Post subject: Re: Asynchronous TCP IP Client socket C#
PostPosted: Mon Jun 11, 2018 7:09 am 

Joined: Mon Jun 11, 2012 2:10 pm
Posts: 119

Offline
Hi Phong,

The answer to this question was actually buried in the Modbus Client class I sent you (http://beijerinc.com/support/ix/forum/viewtopic.php?f=5&t=2289&sid=bad94095cd38d095f0f2a072c489fe97). 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:
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


 Profile  
 
 Post subject: Re: Asynchronous TCP IP Client socket C#
PostPosted: Mon Jun 11, 2018 11:59 pm 

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

Offline
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


 Profile  
 
 Post subject: Re: Asynchronous TCP IP Client socket C#
PostPosted: Tue Jun 12, 2018 6:45 am 

Joined: Mon Jun 11, 2012 2:10 pm
Posts: 119

Offline
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:
if (sock.Poll(0, SelectMode.SelectRead))
{
   // data is available to be read
}
Code:
if (sock.Available > 0)
{
   // data is available to be read
}

_________________
Adam M.
Controls Engineer
FlexEnergy


 Profile  
 
 Post subject: Re: Asynchronous TCP IP Client socket C#
PostPosted: Tue Jun 12, 2018 7:34 am 

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

Offline
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,


 Profile  
 
 Post subject: Re: Asynchronous TCP IP Client socket C#
PostPosted: Tue Jun 12, 2018 7:38 am 

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

Offline
For your reference, this is the TCPClient Script I wrote:
Code:
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


 Profile  
 
 Post subject: Re: Asynchronous TCP IP Client socket C#
PostPosted: Tue Jun 12, 2018 7:45 am 

Joined: Mon Jun 11, 2012 2:10 pm
Posts: 119

Offline
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


 Profile  
 
 Post subject: Re: Asynchronous TCP IP Client socket C#
PostPosted: Mon Jun 18, 2018 7:33 am 

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

Offline
Thanks so much for your help!


 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 8 posts ] 

All times are UTC - 7 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to: