具有连接丢失检测和自动连接的TCP异步客户端

时间:2017-03-14 22:20:44

标签: c# asynchronous sockets tcp client

这是我多年来修改过的TCP异步客户端代码。 它能够检测连接丢失(感谢使用保持活动值)。

但现在,我需要在检测到任何连接丢失(任何通信错误或Disconnect()电话)后自动将其重新连接到服务器。

当我停止监听或断开客户端与它们的连接时,它可以与一些简单的SW TCP服务器一起使用。但是,当我连接到一些真实的设备并开始模拟可能的错误时,问题就开始了。

例如,我断开客户端PC与网络的连接,然后经过一段时间后重新连接",应用程序进入连接状态循环并挂起,特别是方法OnDataReceived抛出SocketException: ConnectionReset iRx = stateObject.Socket.EndReceive(asyn);致电。

由于我不擅长异步代码,我相信我做的事情很糟糕

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

0 个答案:

没有答案