TCP客户端帮助类

169 阅读4分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路

 public class MyEventArgsBase : EventArgs
    {
        /// <summary>
        /// 服务器IP地址列表
        /// </summary>
        public IPAddress[] Addresses { get; set; }

        /// <summary>
        /// 服务器端口
        /// </summary>
        public int Port { get; set; }

        /// <summary>
        /// ToString方法
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            var res = new StringBuilder();
            foreach (var item in Addresses)
            {
                res.Append($"{item},");
            }
            res.Remove(res.Length - 1, 1);
            res.Append($"Addresses:{res} Port:{Port}");
            return res.ToString();
        }
    }
/// <summary>
    /// TCP客户端连接异常事件参数
    /// </summary>
    public class TcpCnnExceptionEventArgs : MyEventArgsBase
    {
        /// <summary>
        /// 与服务器的连接发生异常事件参数
        /// </summary>
        /// <param name="ipAddresses">服务器IP地址列表</param>
        /// <param name="port">服务器端口</param>
        /// <param name="ex">内部异常</param>
        public TcpCnnExceptionEventArgs(IPAddress[] ipAddresses, int port, Exception ex)
        {
            if (ipAddresses is null)
                throw new ArgumentNullException("ipAddresses is null");
            Addresses = ipAddresses;
            Port = port;
            Ex = ex;
        }

        /// <summary>
        /// 内部异常
        /// </summary>
        public Exception Ex { get; private set; }

        /// <summary>
        /// ToString方法
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return $"{base.ToString()} Exception:{Ex}";
        }
    }
    /// <summary>
    /// TCP客户端与服务器的连接已建立事件参数
    /// </summary>
    public class TcpConnectedEventArgs : MyEventArgsBase
    {
        /// <summary>
        /// 与服务器的连接已建立事件参数
        /// </summary>
        /// <param name="ipAddresses">服务器IP地址列表</param>
        /// <param name="port">服务器端口</param>
        public TcpConnectedEventArgs(IPAddress[] ipAddresses, int port)
        {
            if (ipAddresses is null)
                throw new ArgumentNullException("ipAddresses is null");
            Addresses = ipAddresses;
            Port = port;
        }
    }
  /// <summary>
    /// TCP客户端与服务器的连接已断开事件参数
    /// </summary>
    public class TcpDisconnectedEventArgs : MyEventArgsBase
    {
        /// <summary>
        /// 与服务器的连接已断开事件参数
        /// </summary>
        /// <param name="ipAddresses">服务器IP地址列表</param>
        /// <param name="port">服务器端口</param>
        public TcpDisconnectedEventArgs(IPAddress[] ipAddresses, int port)
        {
            if (ipAddresses is null)
                throw new ArgumentNullException("ipAddresses is null");

            Addresses = ipAddresses;
            Port = port;
        }
    }
    /// <summary>
    /// TCP客户端接收到来自服务器端的报文事件参数
    /// </summary>
    public class TcpMsgReceivedEventArgs<T> : EventArgs
    {
        /// <summary>
        /// 接收到报文事件参数
        /// </summary>
        /// <param name="tcpClient">客户端</param>
        /// <param name="msg">报文</param>
        /// <param name="encoding">报文编码方式</param>
        public TcpMsgReceivedEventArgs(TcpClient tcpClient, T msg, Encoding encoding)
        {
            TcpClient = tcpClient;
            Msg = msg;
            Encoding = encoding;
        }

        /// <summary>
        /// 客户端
        /// </summary>
        public TcpClient TcpClient { get; private set; }

        /// <summary>
        /// 报文
        /// </summary>
        public T Msg { get; private set; }

        /// <summary>
        /// 报文编码方式
        /// </summary>
        public Encoding Encoding { get; private set; }
    }
    /// <summary>
    /// TCP客户端帮助类
    /// </summary>
    public class TcpClientUtil : IDisposable
    {
        #region 事件

        /// <summary>
        /// 接收到报文事件
        /// </summary>
        public event EventHandler<TcpMsgReceivedEventArgs<byte[]>> ReceiveMsg;

        /// <summary>
        /// 与服务器的连接已建立事件
        /// </summary>
        public event EventHandler<TcpConnectedEventArgs> ConnectServer;

        /// <summary>
        /// 与服务器的连接已断开事件
        /// </summary>
        public event EventHandler<TcpDisconnectedEventArgs> DisconnectServer;

        /// <summary>
        /// 与服务器的连接发生异常事件
        /// </summary>
        public event EventHandler<TcpCnnExceptionEventArgs> CnnExceptionOccurred;

        #endregion 事件

        #region 属性、字段

        private readonly TcpClient _tcpClient;
        private bool _disposed = false;

        /// <summary>
        /// 标识是否建立连接
        /// </summary>
        public bool Connected
        { get { return _tcpClient.Client.Connected; } }

        /// <summary>
        /// 远端服务器的IP地址列表
        /// </summary>
        public IPAddress[] Addresses { get; private set; }

        /// <summary>
        /// 远端服务器的端口
        /// </summary>
        public int Port { get; private set; }

        private int _retries = 0;

        /// <summary>
        /// 连接重试次数
        /// </summary>
        public int Retries
        { get { return _retries; } set { _retries = value; } }

        private int _retryInterval;

        /// <summary>
        /// 连接重试间隔
        /// </summary>
        public int RetryInterval
        {
            get { return _retryInterval; }
            set { _retryInterval = value; }
        }

        /// <summary>
        /// 远端服务器终结点
        /// </summary>
        public IPEndPoint ServerIPEndPoint
        {
            get { return new IPEndPoint(Addresses[0], Port); }
        }

        /// <summary>
        /// 本地客户端终结点
        /// </summary>
        protected IPEndPoint LocalIPEndPoint { get; private set; }

        private Encoding _encoding;

        /// <summary>
        /// 通信所使用的编码
        /// </summary>
        public Encoding Encoding
        { get { return _encoding; } set { _encoding = value; } }

        #endregion 属性、字段

        #region 方法

        /// <summary>
        /// 异步TCP客户端
        /// </summary>
        /// <param name="serverEP">远端服务器终结点</param>
        /// <param name="localEP">本地客户端终结点</param>
        /// <param name="retries">重试次数</param>
        /// <param name="retryInterval">重试间隔,单位为秒</param>
        /// <param name="encoding">报文编码方式</param>
        public TcpClientUtil(IPEndPoint serverEP, IPEndPoint localEP = null, byte retries = 3, int retryInterval = 2, Encoding encoding = null)
            : this(new[] { serverEP.Address }, serverEP.Port, localEP, retries, retryInterval, encoding)
        {
        }

        /// <summary>
        /// 异步TCP客户端
        /// </summary>
        /// <param name="serverIPAddress">远端服务器IP地址</param>
        /// <param name="serverPort">远端服务器端口</param>
        /// <param name="localEP">本地客户端终结点</param>
        /// <param name="retries">重试次数</param>
        /// <param name="retryInterval">重试间隔,单位为秒</param>
        /// <param name="encoding">报文编码方式</param>
        public TcpClientUtil(IPAddress serverIPAddress, int serverPort, IPEndPoint localEP = null, byte retries = 3, int retryInterval = 2, Encoding encoding = null)
            : this(new[] { serverIPAddress }, serverPort, localEP, retries, retryInterval, encoding)
        {
        }

        /// <summary>
        /// 异步TCP客户端
        /// </summary>
        /// <param name="serverIPAddresses">远端服务器IP地址列表</param>
        /// <param name="serverPort">远端服务器端口</param>
        /// <param name="retries">重试次数</param>
        /// <param name="retryInterval">重试间隔,单位为秒</param>
        /// <param name="encoding">报文编码方式</param>
        public TcpClientUtil(IPAddress[] serverIPAddresses, int serverPort, byte retries = 3, int retryInterval = 2, Encoding encoding = null)
            : this(serverIPAddresses, serverPort, null, retries, retryInterval, encoding)
        {
        }

        /// <summary>
        /// 异步TCP客户端
        /// </summary>
        /// <param name="serverIPAddresses">远端服务器IP地址列表</param>
        /// <param name="serverPort">远端服务器端口</param>
        /// <param name="localEP">本地客户端终结点</param>
        /// <param name="retries">重试次数</param>
        /// <param name="retryInterval">重试间隔,单位为秒</param>
        /// <param name="encoding">报文编码方式</param>
        public TcpClientUtil(IPAddress[] serverIPAddresses, int serverPort, IPEndPoint localEP, byte retries = 3, int retryInterval = 2, Encoding encoding = null)
        {
            Addresses = serverIPAddresses;
            Port = serverPort;
            LocalIPEndPoint = localEP;
            Encoding = encoding is null ? Encoding.UTF8 : encoding;
            if (LocalIPEndPoint != null)
            {
                _tcpClient = new TcpClient(LocalIPEndPoint);
            }
            else
            {
                _tcpClient = new TcpClient();
            }

            Retries = retries;
            RetryInterval = retryInterval;
        }

        /// <summary>
        /// 连接到服务器
        /// </summary>
        /// <returns>异步TCP客户端</returns>
        public TcpClientUtil Connect()
        {
            if (!Connected)
            {
                _tcpClient.BeginConnect(Addresses, Port, HandleTcpServerConnected, _tcpClient);
            }
            return this;
        }

        /// <summary>
        /// 关闭与服务器的连接
        /// </summary>
        /// <returns>异步TCP客户端</returns>
        public TcpClientUtil Close()
        {
            if (Connected)
            {
                _retries = 0;
                _tcpClient.Close();
                DisconnectServer?.BeginInvoke(this, new TcpDisconnectedEventArgs(Addresses, Port), null, null);
            }
            return this;
        }

        private void HandleTcpServerConnected(IAsyncResult ar)
        {
            try
            {
                _tcpClient.EndConnect(ar);
                ConnectServer?.BeginInvoke(this, new TcpConnectedEventArgs(Addresses, Port), null, null);
                _retries = 0;
            }
            catch (Exception ex)
            {
                //ExceptionHandler.Handle(ex);
                //if (retries > 0)
                //{
                //    Logger.Debug(string.Format(CultureInfo.InvariantCulture,
                //      "Connect to server with retry {0} failed.", retries));
                //}

                _retries++;
                if (_retries > Retries)
                {
                    // we have failed to connect to all the IP Addresses,
                    // connection has failed overall.
                    CnnExceptionOccurred?.BeginInvoke(this, new TcpCnnExceptionEventArgs(Addresses, Port, ex), null, null);
                    return;
                }
                else
                {
                    //Logger.Debug(string.Format(CultureInfo.InvariantCulture,
                    //  "Waiting {0} seconds before retrying to connect to server.",
                    //  RetryInterval));
                    Thread.Sleep(TimeSpan.FromSeconds(RetryInterval));
                    Connect();
                    return;
                }
            }

            // we are connected successfully and start asyn read operation.
            byte[] buffer = new byte[_tcpClient.ReceiveBufferSize];
            _tcpClient.GetStream().BeginRead(buffer, 0, buffer.Length, HandleMsgReceived, buffer);
        }

        private void HandleMsgReceived(IAsyncResult ar)
        {
            NetworkStream stream = _tcpClient.GetStream();
            int numberOfReadBytes = 0;
            try
            {
                numberOfReadBytes = stream.EndRead(ar);
            }
            catch
            {
                numberOfReadBytes = 0;
            }

            if (numberOfReadBytes == 0)
            {
                // connection has been closed
                Close();
                return;
            }
            // received byte and trigger event notification
            byte[] buffer = (byte[])ar.AsyncState;
            byte[] receivedBytes = new byte[numberOfReadBytes];
            Buffer.BlockCopy(buffer, 0, receivedBytes, 0, numberOfReadBytes);
            if (receivedBytes != null)
            {
                ReceiveMsg?.BeginInvoke(this, new TcpMsgReceivedEventArgs<byte[]>(_tcpClient, receivedBytes, _encoding), null, null);
            }
            stream.BeginRead(buffer, 0, buffer.Length, HandleMsgReceived, buffer); // then start reading from the network again
        }

        /// <summary>
        /// 发送报文
        /// </summary>
        /// <param name="msg">报文</param>
        public void Send(string msg)
        {
            Send(Encoding.GetBytes(msg));
        }

        /// <summary>
        /// 发送报文
        /// </summary>
        /// <param name="msg">报文</param>
        public void Send(byte[] msg)
        {
            _tcpClient.GetStream().BeginWrite(msg, 0, msg.Length, (IAsyncResult ar) => ((TcpClient)ar.AsyncState).GetStream().EndWrite(ar), _tcpClient);
        }

        /// <summary>
        /// Dispose方法
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this); //标记gc不在调用析构函数
        }

        /// <summary>
        /// 析构函数
        /// </summary>
        ~TcpClientUtil()
        {
            Dispose(false);
        }

        /// <summary>
        /// Dispose方法
        /// </summary>
        /// <param name="disposing"></param>
        private void Dispose(bool disposing)
        {
            if (_disposed) return; //如果已经被回收,就中断执行
            if (disposing)
            {
                if (_tcpClient.Connected)
                {
                    _tcpClient.Close();
                }
                _tcpClient.Dispose();
            }
            //TODO:释放非托管资源
            _disposed = true;
        }

        #endregion 方法
    }