2

Here is my many-years many times modified code for TCP asynchronous client. It is able to detect connection loss (thanks to using keep alive values).

But now, I need to reconnect it to server automatically after any connection loss detection (any communication error or Disconnect() call).

It works fine with some simple SW TCP servers, when I stop them listening or disconnect my client from them. But, when I connect to some real devices and start to simulate possible errors, problems start.

For example, I disconnect client PC from network, then after some time of "reconnecting", application goes to connection state loop and hangs, specifically method OnDataReceived throws SocketException: ConnectionReset on iRx = stateObject.Socket.EndReceive(asyn); call.

As I am not adept at async code, I believe I do something very bad

using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Runtime.InteropServices;
using System.Threading.Tasks;

namespace SharpLib.Tcp
{
    public enum ConnectionState
    {
        Connected, Disconnected
    };

    public delegate void ConnectionStateChangedEventHandler(object sender, ConnectionStateEventArgs args);

    public class ConnectionStateEventArgs : EventArgs
    {
        public ConnectionState ConnectionState { get; set; }

        public ConnectionStateEventArgs(ConnectionState state)
        {
            this.ConnectionState = state;
        }
    }

    /// <summary>
    /// Structure of received data
    /// </summary>
    public class StateObject
    {
        public byte[] DataBuffer { get; set; }
        public Socket Socket { get; set; }

        public StateObject(Socket socket)
        {
            this.DataBuffer = new byte[128];
            this.Socket = socket;
        }
    }


    /// <summary>
    /// TCP client with asynchronous connecting and data receiving
    /// </summary>
    public class TcpAsyncClient
    {
        protected string address;
        protected int port;
        private Socket socket;
        private int keepAliveTime;
        private int keepAliveInterval;
        private int connectTimeout;
        private bool autoReconnect;

        private AsyncCallback callback;

        private static ManualResetEvent connectDone = new ManualResetEvent(false);

        public event MessageEventHandler DataReceived = delegate { };
        public event ExceptionEventHandler ExceptionCaught = delegate { };
        public event ConnectionStateChangedEventHandler ConnectionStateChanged = delegate { };

        public bool Connected
        {
            get
            {
                if (socket == null)
                    return false;
                return socket.Connected;
            }
        }

        public TcpAsyncClient(string address, int port, int keepAliveTime = 1000, int keepAliveInterval = 1000, int connectTimeout = 1000, bool autoReconnect = false)
        {
            this.address = address;
            this.port = port;
            this.keepAliveInterval = keepAliveInterval;
            this.keepAliveTime = keepAliveTime;
            this.connectTimeout = connectTimeout;
            this.autoReconnect = autoReconnect;

            ConnectionStateChanged(this, new ConnectionStateEventArgs(ConnectionState.Disconnected));
        }

        /// <summary>
        /// Connect to tcp server - async
        /// </summary>
        public void Connect()
        {
            try
            {
                socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

                IPEndPoint ipEnd = FindIpEndPoint(address, port);

                socket.BeginConnect(ipEnd, new AsyncCallback(ConnectCallback), new StateObject(socket));
                connectDone.WaitOne(500);
            }
            catch (SocketException ex)
            {
                OnError(ex);
            }
        }

        /// <summary>
        /// Connect done callback
        /// </summary>
        /// <param name="ar"></param>
        private void ConnectCallback(IAsyncResult ar)
        {
            try
            {
                // Complete the connection.
                ((StateObject)ar.AsyncState).Socket.EndConnect(ar);

                // Signal that the connection has been made.
                connectDone.Set();
                WaitForData();

                SetKeepAlive(true, Convert.ToUInt32(keepAliveTime), Convert.ToUInt32(keepAliveInterval));

                ConnectionStateChanged(this, new ConnectionStateEventArgs(ConnectionState.Connected));
            }
            catch (SocketException ex)
            {
                OnError(ex);
            }
        }

        /// <summary>
        /// Disconnect from tcp server
        /// </summary>
        public void Disconnect()
        {
            try
            {
                // MSDN recommends to Shutdown() before Disconnect()
                socket.Shutdown(SocketShutdown.Both);
                socket.Disconnect(true);
            }
            catch { }

            ConnectionStateChanged(this, new ConnectionStateEventArgs(ConnectionState.Disconnected));

            if (autoReconnect)
            {
                Connect();
            }
        }

        /// <summary>
        /// Send string message to tcp server
        /// </summary>
        /// <param name="message"></param>
        public void Send(string message)
        {
            // because of this, we can Send from client imidiately after Connect() call
            DateTime start = DateTime.Now;

            if (!Connected)
            {
                ConnectionStateChanged(this, new ConnectionStateEventArgs(ConnectionState.Disconnected));
                return;
            }

            // make return on the end of line
            message += "\r";

            int sent = 0;  // how many bytes is already sent
            do
            {
                try
                {
                    sent += socket.Send(System.Text.Encoding.UTF8.GetBytes(message), sent, message.Length - sent, SocketFlags.None);
                }
                catch (SocketException ex)
                {
                    if (ex.SocketErrorCode == SocketError.WouldBlock ||
                        ex.SocketErrorCode == SocketError.IOPending ||
                        ex.SocketErrorCode == SocketError.NoBufferSpaceAvailable)
                    {
                        // socket buffer is probably full, wait and try again
                        Thread.Sleep(30);
                    }
                    else
                    {
                        OnError(ex);
                        break;
                    }
                }
            }
            while (sent < message.Length);
        }

        /// <summary>
        /// Start receiving data from tcp server
        /// </summary>
        public void WaitForData()
        {
            try
            {
                StateObject stateObject = new StateObject(socket);

                IAsyncResult result = socket.BeginReceive(stateObject.DataBuffer, 0, 128, SocketFlags.None, new AsyncCallback(OnDataReceived), stateObject);
            }
            catch (SocketException ex)
            {
                OnError(ex);
            }
        }

        /// <summary>
        /// Data received callback
        /// </summary>
        /// <param name="asyn"></param>
        public void OnDataReceived(IAsyncResult asyn)
        {
            try
            {
                StateObject stateObject = (StateObject)asyn.AsyncState;
                if (!stateObject.Socket.Connected)
                    return;

                int iRx = stateObject.Socket.EndReceive(asyn);

                // Server probably stopped listening
                if (iRx == 0)
                {
                    Disconnect();
                    ConnectionStateChanged(this, new ConnectionStateEventArgs(ConnectionState.Disconnected));
                    return;
                }

                char[] chars = new char[iRx];
                System.Text.Decoder d = System.Text.Encoding.UTF8.GetDecoder();
                int charLen = d.GetChars(stateObject.DataBuffer, 0, iRx, chars, 0);
                string szData = new string(chars);

                DataReceived(this, new MessageEventArgs(szData));

                WaitForData();
            }
            catch (SocketException ex)
            {
                OnError(ex);
            }
        }

        /// <summary>
        /// Socket exception during connecting or communication with server
        /// </summary>
        /// <param name="ex"></param>
        private void OnError(Exception ex)
        {
            ExceptionCaught(this, new ExceptionEventArgs(ex));
            Disconnect();
        }

        /// <summary>
        /// Set KeepAlive timer for socket
        /// </summary>
        /// <param name="on"></param>
        /// <param name="time"></param>
        /// <param name="interval"></param>
        private void SetKeepAlive(bool on, uint time, uint interval)
        {
            int size = Marshal.SizeOf(new uint());

            var inOptionValues = new byte[size * 3];

            BitConverter.GetBytes((uint)(on ? 1 : 0)).CopyTo(inOptionValues, 0);
            BitConverter.GetBytes((uint)time).CopyTo(inOptionValues, size);
            BitConverter.GetBytes((uint)interval).CopyTo(inOptionValues, size * 2);

            socket.IOControl(IOControlCode.KeepAliveValues, inOptionValues, null);
        }

        /// <summary>
        /// Create ip address from known host name 
        /// </summary>
        /// <param name="hostName"></param>
        /// <param name="port"></param>
        /// <returns></returns>
        private IPEndPoint FindIpEndPoint(string hostName, int port)
        {
            var addresses = System.Net.Dns.GetHostAddresses(hostName);
            if (addresses.Length == 0)
            {
                throw new ArgumentException(
                    "Unable to retrieve address from specified host name.",
                    "hostName"
                );
            }
            return new IPEndPoint(addresses[0], port);
        }
    }
}
2
  • 1
    Your question sounds like your code is currently not working as intended. This makes it off-topic for Code Review. Have a look at the help center to see what is on-topic here. Commented Mar 16, 2017 at 15:06
  • 1
    @Graipher. You are right, I flagged it to close and move to StackOverflow. Commented Mar 16, 2017 at 15:20

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.