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

C# 具有连接丢失检测和自动重新连接的TCP异步客户端,c#,asynchronous,sockets,tcp,client,C#,Asynchronous,Sockets,Tcp,Client,这是我多年来多次修改的TCP异步客户端代码。 它能够检测连接丢失(由于使用了保持活动的值) 但现在,我需要在检测到任何连接丢失(任何通信错误或Disconnect()call)后自动将其重新连接到服务器 它适用于一些简单的SW TCP服务器,当我停止它们侦听或断开客户端与它们的连接时。但是,当我连接到一些真实的设备并开始模拟可能的错误时,问题就开始了 例如,我断开客户端PC与网络的连接,然后在“重新连接”一段时间后,应用程序进入连接状态循环并挂起,具体地说,方法OnDataReceived抛出S

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

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

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

例如,我断开客户端PC与网络的连接,然后在“重新连接”一段时间后,应用程序进入连接状态循环并挂起,具体地说,方法
OnDataReceived
抛出
SocketException:ConnectionReset
on
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);
        }
    }
}
使用系统;
Net系统;
使用System.Net.Sockets;
使用系统线程;
使用System.Runtime.InteropServices;
使用System.Threading.Tasks;
名称空间SharpLib.Tcp
{
公共枚举连接状态
{
连接的,断开的
};
公共委托无效ConnectionStateChangedEventHandler(对象发送方,ConnectionStateEventArgs args args);
公共类ConnectionStateEventArgs:EventArgs
{
公共连接状态连接状态{get;set;}
公共连接状态事件参数(连接状态状态)
{
this.ConnectionState=状态;
}
}
/// 
///接收数据的结构
/// 
公共类状态对象
{
公共字节[]数据缓冲{get;set;}
公共套接字{get;set;}
公共状态对象(套接字)
{
this.DataBuffer=新字节[128];
这个.Socket=Socket;
}
}
/// 
///具有异步连接和数据接收的TCP客户端
/// 
公共类TcpAsyncClient
{
受保护的字符串地址;
受保护的int端口;
专用插座;
私有int keepAliveTime;
私密的内部信息;
私有int连接超时;
私人楼宇自动接驳;
私有异步回调;
专用静态手动复位事件连接完成=新手动复位事件(错误);
public event MessageEventHandler DataReceived=委托{};
公共事件例外venthandler例外caught=委托{};
公共事件连接stateChangedEventHandler连接stateChanged=委托{};
公共广播连接
{
得到
{
if(套接字==null)
返回false;
返回插座。已连接;
}
}
公共TcpAsyncClient(字符串地址,int端口,int keepAliveTime=1000,int keepAliveInterval=1000,int connectTimeout=1000,bool autoReconnect=false)
{
this.address=地址;
this.port=端口;
this.keepAliveInterval=keepAliveInterval;
this.keepAliveTime=keepAliveTime;
this.connectTimeout=connectTimeout;
this.autoReconnect=autoReconnect;
ConnectionStateChanged(这是新的ConnectionStateEventArgs(ConnectionState.Disconnected));
}
/// 
///连接到tcp服务器-异步
/// 
公共void Connect()
{
尝试
{
套接字=新套接字(AddressFamily.InterNetwork、SocketType.Stream、ProtocolType.Tcp);
IPEndPoint ipEnd=FindIpEndPoint(地址、端口);
BeginConnect(ipEnd,新的异步回调(ConnectCallback),新的StateObject(socket));
韦通(500);
}
捕获(SocketException例外)
{
OnError(ex);
}
}
/// 
///连接完成回调
/// 
/// 
私有无效连接回调(IAsyncResult ar)
{
尝试
{
//完成连接。
((StateObject)ar.AsyncState).Socket.EndConnect(ar);
//表示已建立连接的信号。
connectDone.Set();
WaitForData();
SetKeepAlive(true,Convert.ToUInt32(keepAliveTime),Convert.ToUInt32(keepAliveInterval));
ConnectionStateChanged(这是新的ConnectionStateEventArgs(ConnectionState.Connected));
}
捕获(SocketException例外)
{
OnError(ex);
}
}
/// 
///断开与tcp服务器的连接
/// 
公共空间断开连接()
{
尝试
{
//MSDN建议在断开()之前关闭()
socket.Shutdown(SocketShutdown.Both);
插座。断开(正确);
}
捕获{}
ConnectionStateChanged(这是新的ConnectionStateEventArgs(ConnectionState.Disconnected));
如果(自动重新连接)
{
Connect();
}
}
/// 
///向tcp服务器发送字符串消息
/// 
/// 
公共无效发送(字符串消息)
{
//因此,我们可以在调用Connect()后立即从客户端发送
DateTime start=DateTime.Now;
如果(!已连接)
{
ConnectionStateChanged(这是新的ConnectionStateEventArgs(ConnectionState.Disconnected));
返回;
}
//在行尾返回
消息+=“\r”;
int sent=0;//已发送多少字节
做
{
尝试
{
sent+=socket.Send(System.Text.Encoding.UTF8.GetBytes(消息)