为了账号安全,请及时绑定邮箱和手机立即绑定

C# 对 TCP 客户端的状态封装

标签:
C#

TCP客户端连接TCP服务器端有几种应用状态:

  1. 与服务器的连接已建立

  2. 与服务器的连接已断开

  3. 与服务器的连接发生异常

应用程序可按需求合理处理这些逻辑,比如:

  1. 连接断开后自动重连

  2. 连接断开后选择备用地址重连

  3. 所有状态变化上报告警

本文描述的TcpClient实现了状态变化的事件通知机制。

  1   /// <summary>  2   /// 异步TCP客户端  3   /// </summary>  4   public class AsyncTcpClient : IDisposable  5   {  6     #region Fields  7   8     private TcpClient tcpClient;  9     private bool disposed = false; 10     private int retries = 0; 11  12     #endregion 13  14     #region Ctors 15  16     /// <summary> 17     /// 异步TCP客户端 18     /// </summary> 19     /// <param name="remoteEP">远端服务器终结点</param> 20     public AsyncTcpClient(IPEndPoint remoteEP) 21       : this(new[] { remoteEP.Address }, remoteEP.Port) 22     { 23     } 24  25     /// <summary> 26     /// 异步TCP客户端 27     /// </summary> 28     /// <param name="remoteEP">远端服务器终结点</param> 29     /// <param name="localEP">本地客户端终结点</param> 30     public AsyncTcpClient(IPEndPoint remoteEP, IPEndPoint localEP) 31       : this(new[] { remoteEP.Address }, remoteEP.Port, localEP) 32     { 33     } 34  35     /// <summary> 36     /// 异步TCP客户端 37     /// </summary> 38     /// <param name="remoteIPAddress">远端服务器IP地址</param> 39     /// <param name="remotePort">远端服务器端口</param> 40     public AsyncTcpClient(IPAddress remoteIPAddress, int remotePort) 41       : this(new[] { remoteIPAddress }, remotePort) 42     { 43     } 44  45     /// <summary> 46     /// 异步TCP客户端 47     /// </summary> 48     /// <param name="remoteIPAddress">远端服务器IP地址</param> 49     /// <param name="remotePort">远端服务器端口</param> 50     /// <param name="localEP">本地客户端终结点</param> 51     public AsyncTcpClient( 52       IPAddress remoteIPAddress, int remotePort, IPEndPoint localEP) 53       : this(new[] { remoteIPAddress }, remotePort, localEP) 54     { 55     } 56  57     /// <summary> 58     /// 异步TCP客户端 59     /// </summary> 60     /// <param name="remoteHostName">远端服务器主机名</param> 61     /// <param name="remotePort">远端服务器端口</param> 62     public AsyncTcpClient(string remoteHostName, int remotePort) 63       : this(Dns.GetHostAddresses(remoteHostName), remotePort) 64     { 65     } 66  67     /// <summary> 68     /// 异步TCP客户端 69     /// </summary> 70     /// <param name="remoteHostName">远端服务器主机名</param> 71     /// <param name="remotePort">远端服务器端口</param> 72     /// <param name="localEP">本地客户端终结点</param> 73     public AsyncTcpClient( 74       string remoteHostName, int remotePort, IPEndPoint localEP) 75       : this(Dns.GetHostAddresses(remoteHostName), remotePort, localEP) 76     { 77     } 78  79     /// <summary> 80     /// 异步TCP客户端 81     /// </summary> 82     /// <param name="remoteIPAddresses">远端服务器IP地址列表</param> 83     /// <param name="remotePort">远端服务器端口</param> 84     public AsyncTcpClient(IPAddress[] remoteIPAddresses, int remotePort) 85       : this(remoteIPAddresses, remotePort, null) 86     { 87     } 88  89     /// <summary> 90     /// 异步TCP客户端 91     /// </summary> 92     /// <param name="remoteIPAddresses">远端服务器IP地址列表</param> 93     /// <param name="remotePort">远端服务器端口</param> 94     /// <param name="localEP">本地客户端终结点</param> 95     public AsyncTcpClient( 96       IPAddress[] remoteIPAddresses, int remotePort, IPEndPoint localEP) 97     { 98       this.Addresses = remoteIPAddresses; 99       this.Port = remotePort;100       this.LocalIPEndPoint = localEP;101       this.Encoding = Encoding.Default;102 103       if (this.LocalIPEndPoint != null)104       {105         this.tcpClient = new TcpClient(this.LocalIPEndPoint);106       }107       else108       {109         this.tcpClient = new TcpClient();110       }111 112       Retries = 3;113       RetryInterval = 5;114     }115 116     #endregion117 118     #region Properties119 120     /// <summary>121     /// 是否已与服务器建立连接122     /// </summary>123     public bool Connected { get { return tcpClient.Client.Connected; } }124     /// <summary>125     /// 远端服务器的IP地址列表126     /// </summary>127     public IPAddress[] Addresses { get; private set; }128     /// <summary>129     /// 远端服务器的端口130     /// </summary>131     public int Port { get; private set; }132     /// <summary>133     /// 连接重试次数134     /// </summary>135     public int Retries { get; set; }136     /// <summary>137     /// 连接重试间隔138     /// </summary>139     public int RetryInterval { get; set; }140     /// <summary>141     /// 远端服务器终结点142     /// </summary>143     public IPEndPoint RemoteIPEndPoint 
144     { 
145       get { return new IPEndPoint(Addresses[0], Port); } 
146     }147     /// <summary>148     /// 本地客户端终结点149     /// </summary>150     protected IPEndPoint LocalIPEndPoint { get; private set; }151     /// <summary>152     /// 通信所使用的编码153     /// </summary>154     public Encoding Encoding { get; set; }155 156     #endregion157 158     #region Connect159 160     /// <summary>161     /// 连接到服务器162     /// </summary>163     /// <returns>异步TCP客户端</returns>164     public AsyncTcpClient Connect()165     {166       if (!Connected)167       {168         // start the async connect operation169         tcpClient.BeginConnect(170           Addresses, Port, HandleTcpServerConnected, tcpClient);171       }172 173       return this;174     }175 176     /// <summary>177     /// 关闭与服务器的连接178     /// </summary>179     /// <returns>异步TCP客户端</returns>180     public AsyncTcpClient Close()181     {182       if (Connected)183       {184         retries = 0;185         tcpClient.Close();186         RaiseServerDisconnected(Addresses, Port);187       }188 189       return this;190     }191 192     #endregion193 194     #region Receive195 196     private void HandleTcpServerConnected(IAsyncResult ar)197     {198       try199       {200         tcpClient.EndConnect(ar);201         RaiseServerConnected(Addresses, Port);202         retries = 0;203       }204       catch (Exception ex)205       {206         ExceptionHandler.Handle(ex);207         if (retries > 0)208         {209           Logger.Debug(string.Format(CultureInfo.InvariantCulture, 
210             "Connect to server with retry {0} failed.", retries));211         }212 213         retries++;214         if (retries > Retries)215         {216           // we have failed to connect to all the IP Addresses, 
217           // connection has failed overall.218           RaiseServerExceptionOccurred(Addresses, Port, ex);219           return;220         }221         else222         {223           Logger.Debug(string.Format(CultureInfo.InvariantCulture, 
224             "Waiting {0} seconds before retrying to connect to server.", 
225             RetryInterval));226           Thread.Sleep(TimeSpan.FromSeconds(RetryInterval));227           Connect();228           return;229         }230       }231 232       // we are connected successfully and start asyn read operation.233       byte[] buffer = new byte[tcpClient.ReceiveBufferSize];234       tcpClient.GetStream().BeginRead(235         buffer, 0, buffer.Length, HandleDatagramReceived, buffer);236     }237 238     private void HandleDatagramReceived(IAsyncResult ar)239     {240       NetworkStream stream = tcpClient.GetStream();241 242       int numberOfReadBytes = 0;243       try244       {245         numberOfReadBytes = stream.EndRead(ar);246       }247       catch248       {249         numberOfReadBytes = 0;250       }251 252       if (numberOfReadBytes == 0)253       {254         // connection has been closed255         Close();256         return;257       }258 259       // received byte and trigger event notification260       byte[] buffer = (byte[])ar.AsyncState;261       byte[] receivedBytes = new byte[numberOfReadBytes];262       Buffer.BlockCopy(buffer, 0, receivedBytes, 0, numberOfReadBytes);263       RaiseDatagramReceived(tcpClient, receivedBytes);264       RaisePlaintextReceived(tcpClient, receivedBytes);265 266       // then start reading from the network again267       stream.BeginRead(268         buffer, 0, buffer.Length, HandleDatagramReceived, buffer);269     }270 271     #endregion272 273     #region Events274 275     /// <summary>276     /// 接收到数据报文事件277     /// </summary>278     public event EventHandler<TcpDatagramReceivedEventArgs<byte[]>> DatagramReceived;279     /// <summary>280     /// 接收到数据报文明文事件281     /// </summary>282     public event EventHandler<TcpDatagramReceivedEventArgs<string>> PlaintextReceived;283 284     private void RaiseDatagramReceived(TcpClient sender, byte[] datagram)285     {286       if (DatagramReceived != null)287       {288         DatagramReceived(this, 
289           new TcpDatagramReceivedEventArgs<byte[]>(sender, datagram));290       }291     }292 293     private void RaisePlaintextReceived(TcpClient sender, byte[] datagram)294     {295       if (PlaintextReceived != null)296       {297         PlaintextReceived(this, 
298           new TcpDatagramReceivedEventArgs<string>(299             sender, this.Encoding.GetString(datagram, 0, datagram.Length)));300       }301     }302 303     /// <summary>304     /// 与服务器的连接已建立事件305     /// </summary>306     public event EventHandler<TcpServerConnectedEventArgs> ServerConnected;307     /// <summary>308     /// 与服务器的连接已断开事件309     /// </summary>310     public event EventHandler<TcpServerDisconnectedEventArgs> ServerDisconnected;311     /// <summary>312     /// 与服务器的连接发生异常事件313     /// </summary>314     public event EventHandler<TcpServerExceptionOccurredEventArgs> ServerExceptionOccurred;315 316     private void RaiseServerConnected(IPAddress[] ipAddresses, int port)317     {318       if (ServerConnected != null)319       {320         ServerConnected(this, 
321           new TcpServerConnectedEventArgs(ipAddresses, port));322       }323     }324 325     private void RaiseServerDisconnected(IPAddress[] ipAddresses, int port)326     {327       if (ServerDisconnected != null)328       {329         ServerDisconnected(this, 
330           new TcpServerDisconnectedEventArgs(ipAddresses, port));331       }332     }333 334     private void RaiseServerExceptionOccurred(335       IPAddress[] ipAddresses, int port, Exception innerException)336     {337       if (ServerExceptionOccurred != null)338       {339         ServerExceptionOccurred(this, 
340           new TcpServerExceptionOccurredEventArgs(341             ipAddresses, port, innerException));342       }343     }344 345     #endregion346 347     #region Send348 349     /// <summary>350     /// 发送报文351     /// </summary>352     /// <param name="datagram">报文</param>353     public void Send(byte[] datagram)354     {355       if (datagram == null)356         throw new ArgumentNullException("datagram");357 358       if (!Connected)359       {360         RaiseServerDisconnected(Addresses, Port);361         throw new InvalidProgramException(362           "This client has not connected to server.");363       }364 365       tcpClient.GetStream().BeginWrite(366         datagram, 0, datagram.Length, HandleDatagramWritten, tcpClient);367     }368 369     private void HandleDatagramWritten(IAsyncResult ar)370     {371       ((TcpClient)ar.AsyncState).GetStream().EndWrite(ar);372     }373 374     /// <summary>375     /// 发送报文376     /// </summary>377     /// <param name="datagram">报文</param>378     public void Send(string datagram)379     {380       Send(this.Encoding.GetBytes(datagram));381     }382 383     #endregion384 385     #region IDisposable Members386 387     /// <summary>388     /// Performs application-defined tasks associated with freeing, 
389     /// releasing, or resetting unmanaged resources.390     /// </summary>391     public void Dispose()392     {393       Dispose(true);394       GC.SuppressFinalize(this);395     }396 397     /// <summary>398     /// Releases unmanaged and - optionally - managed resources399     /// </summary>400     /// <param name="disposing"><c>true</c> to release both managed 
401     /// and unmanaged resources; <c>false</c> 402     /// to release only unmanaged resources.403     /// </param>404     protected virtual void Dispose(bool disposing)405     {406       if (!this.disposed)407       {408         if (disposing)409         {410           try411           {412             Close();413 414             if (tcpClient != null)415             {416               tcpClient = null;417             }418           }419           catch (SocketException ex)420           {421             ExceptionHandler.Handle(ex);422           }423         }424 425         disposed = true;426       }427     }428 429     #endregion430   }


使用举例


 1   class Program 2   { 3     static AsyncTcpClient client; 4  5     static void Main(string[] args) 6     { 7       LogFactory.Assign(new ConsoleLogFactory()); 8  9       // 测试用,可以不指定由系统选择端口10       IPEndPoint remoteEP = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 9999);11       IPEndPoint localEP = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 9998); 
12       client = new AsyncTcpClient(remoteEP, localEP);13       client.Encoding = Encoding.UTF8;14       client.ServerExceptionOccurred += 
15         new EventHandler<TcpServerExceptionOccurredEventArgs>(client_ServerExceptionOccurred);16       client.ServerConnected += 
17         new EventHandler<TcpServerConnectedEventArgs>(client_ServerConnected);18       client.ServerDisconnected += 
19         new EventHandler<TcpServerDisconnectedEventArgs>(client_ServerDisconnected);20       client.PlaintextReceived += 
21         new EventHandler<TcpDatagramReceivedEventArgs<string>>(client_PlaintextReceived);22       client.Connect();23 24       Console.WriteLine("TCP client has connected to server.");25       Console.WriteLine("Type something to send to server...");26       while (true)27       {28         try29         {30           string text = Console.ReadLine();31           client.Send(text);32         }33         catch (Exception ex)34         {35           Console.WriteLine(ex.Message);36         }37       }38     }39 40     static void client_ServerExceptionOccurred(41       object sender, TcpServerExceptionOccurredEventArgs e)42     {43       Logger.Debug(string.Format(CultureInfo.InvariantCulture, 
44         "TCP server {0} exception occurred, {1}.", 
45         e.ToString(), e.Exception.Message));46     }47 48     static void client_ServerConnected(49       object sender, TcpServerConnectedEventArgs e)50     {51       Logger.Debug(string.Format(CultureInfo.InvariantCulture, 
52         "TCP server {0} has connected.", e.ToString()));53     }54 55     static void client_ServerDisconnected(56       object sender, TcpServerDisconnectedEventArgs e)57     {58       Logger.Debug(string.Format(CultureInfo.InvariantCulture, 
59         "TCP server {0} has disconnected.", e.ToString()));60     }61 62     static void client_PlaintextReceived(63       object sender, TcpDatagramReceivedEventArgs<string> e)64     {65       Console.Write(string.Format("Server : {0} --> ", 
66         e.TcpClient.Client.RemoteEndPoint.ToString()));67       Console.WriteLine(string.Format("{0}", e.Datagram));68     }69   }


TCP客户端State


 1   /// <summary> 2   /// Internal class to join the TCP client and buffer together 3   /// for easy management in the server 4   /// </summary> 5   internal class TcpClientState 6   { 7     /// <summary> 8     /// Constructor for a new Client 9     /// </summary>10     /// <param name="tcpClient">The TCP client</param>11     /// <param name="buffer">The byte array buffer</param>12     public TcpClientState(TcpClient tcpClient, byte[] buffer)13     {14       if (tcpClient == null)15         throw new ArgumentNullException("tcpClient");16       if (buffer == null)17         throw new ArgumentNullException("buffer");18 19       this.TcpClient = tcpClient;20       this.Buffer = buffer;21     }22 23     /// <summary>24     /// Gets the TCP Client25     /// </summary>26     public TcpClient TcpClient { get; private set; }27 28     /// <summary>29     /// Gets the Buffer.30     /// </summary>31     public byte[] Buffer { get; private set; }32 33     /// <summary>34     /// Gets the network stream35     /// </summary>36     public NetworkStream NetworkStream37     {38       get { return TcpClient.GetStream(); }39     }40   }


与客户端的连接已建立事件参数


 1   /// <summary> 2   /// 与客户端的连接已建立事件参数 3   /// </summary> 4   public class TcpClientConnectedEventArgs : EventArgs 5   { 6     /// <summary> 7     /// 与客户端的连接已建立事件参数 8     /// </summary> 9     /// <param name="tcpClient">客户端</param>10     public TcpClientConnectedEventArgs(TcpClient tcpClient)11     {12       if (tcpClient == null)13         throw new ArgumentNullException("tcpClient");14 15       this.TcpClient = tcpClient;16     }17 18     /// <summary>19     /// 客户端20     /// </summary>21     public TcpClient TcpClient { get; private set; }22   }


与客户端的连接已断开事件参数


  /// <summary>
  /// 与客户端的连接已断开事件参数  /// </summary>
  public class TcpClientDisconnectedEventArgs : EventArgs
  {    /// <summary>
    /// 与客户端的连接已断开事件参数    /// </summary>
    /// <param name="tcpClient">客户端</param>
    public TcpClientDisconnectedEventArgs(TcpClient tcpClient)
    {      if (tcpClient == null)        throw new ArgumentNullException("tcpClient");      this.TcpClient = tcpClient;
    }    /// <summary>
    /// 客户端    /// </summary>
    public TcpClient TcpClient { get; private set; }
  }


与服务器的连接发生异常事件参数


 1   /// <summary> 2   /// 与服务器的连接发生异常事件参数 3   /// </summary> 4   public class TcpServerExceptionOccurredEventArgs : EventArgs 5   { 6     /// <summary> 7     /// 与服务器的连接发生异常事件参数 8     /// </summary> 9     /// <param name="ipAddresses">服务器IP地址列表</param>10     /// <param name="port">服务器端口</param>11     /// <param name="innerException">内部异常</param>12     public TcpServerExceptionOccurredEventArgs(13       IPAddress[] ipAddresses, int port, Exception innerException)14     {15       if (ipAddresses == null)16         throw new ArgumentNullException("ipAddresses");17 18       this.Addresses = ipAddresses;19       this.Port = port;20       this.Exception = innerException;21     }22 23     /// <summary>24     /// 服务器IP地址列表25     /// </summary>26     public IPAddress[] Addresses { get; private set; }27     /// <summary>28     /// 服务器端口29     /// </summary>30     public int Port { get; private set; }31     /// <summary>32     /// 内部异常33     /// </summary>34     public Exception Exception { get; private set; }35 36     /// <summary>37     /// Returns a <see cref="System.String"/> that represents this instance.38     /// </summary>39     /// <returns>40     /// A <see cref="System.String"/> that represents this instance.41     /// </returns>42     public override string ToString()43     {44       string s = string.Empty;45       foreach (var item in Addresses)46       {47         s = s + item.ToString() + ',';48       }49       s = s.TrimEnd(',');50       s = s + ":" + Port.ToString(CultureInfo.InvariantCulture);51 52       return s;53     }54   }


接收到数据报文事件参数


 1   /// <summary> 2   /// 接收到数据报文事件参数 3   /// </summary> 4   /// <typeparam name="T">报文类型</typeparam> 5   public class TcpDatagramReceivedEventArgs<T> : EventArgs 6   { 7     /// <summary> 8     /// 接收到数据报文事件参数 9     /// </summary>10     /// <param name="tcpClient">客户端</param>11     /// <param name="datagram">报文</param>12     public TcpDatagramReceivedEventArgs(TcpClient tcpClient, T datagram)13     {14       TcpClient = tcpClient;15       Datagram = datagram;16     }17 18     /// <summary>19     /// 客户端20     /// </summary>21     public TcpClient TcpClient { get; private set; }22     /// <summary>23     /// 报文24     /// </summary>25     public T Datagram { get; private set; }26   }


与服务器的连接已建立事件参数


 1   /// <summary> 2   /// 与服务器的连接已建立事件参数 3   /// </summary> 4   public class TcpServerConnectedEventArgs : EventArgs 5   { 6     /// <summary> 7     /// 与服务器的连接已建立事件参数 8     /// </summary> 9     /// <param name="ipAddresses">服务器IP地址列表</param>10     /// <param name="port">服务器端口</param>11     public TcpServerConnectedEventArgs(IPAddress[] ipAddresses, int port)12     {13       if (ipAddresses == null)14         throw new ArgumentNullException("ipAddresses");15 16       this.Addresses = ipAddresses;17       this.Port = port;18     }19 20     /// <summary>21     /// 服务器IP地址列表22     /// </summary>23     public IPAddress[] Addresses { get; private set; }24     /// <summary>25     /// 服务器端口26     /// </summary>27     public int Port { get; private set; }28 29     /// <summary>30     /// Returns a <see cref="System.String"/> that represents this instance.31     /// </summary>32     /// <returns>33     /// A <see cref="System.String"/> that represents this instance.34     /// </returns>35     public override string ToString()36     {37       string s = string.Empty;38       foreach (var item in Addresses)39       {40         s = s + item.ToString() + ',';41       }42       s = s.TrimEnd(',');43       s = s + ":" + Port.ToString(CultureInfo.InvariantCulture);44 45       return s;46     }47   }


与服务器的连接已断开事件参数


 1   /// <summary> 2   /// 与服务器的连接已断开事件参数 3   /// </summary> 4   public class TcpServerDisconnectedEventArgs : EventArgs 5   { 6     /// <summary> 7     /// 与服务器的连接已断开事件参数 8     /// </summary> 9     /// <param name="ipAddresses">服务器IP地址列表</param>10     /// <param name="port">服务器端口</param>11     public TcpServerDisconnectedEventArgs(IPAddress[] ipAddresses, int port)12     {13       if (ipAddresses == null)14         throw new ArgumentNullException("ipAddresses");15 16       this.Addresses = ipAddresses;17       this.Port = port;18     }19 20     /// <summary>21     /// 服务器IP地址列表22     /// </summary>23     public IPAddress[] Addresses { get; private set; }24     /// <summary>25     /// 服务器端口26     /// </summary>27     public int Port { get; private set; }28 29     /// <summary>30     /// Returns a <see cref="System.String"/> that represents this instance.31     /// </summary>32     /// <returns>33     /// A <see cref="System.String"/> that represents this instance.34     /// </returns>35     public override string ToString()36     {37       string s = string.Empty;38       foreach (var item in Addresses)39       {40         s = s + item.ToString() + ',';41       }42       s = s.TrimEnd(',');43       s = s + ":" + Port.ToString(CultureInfo.InvariantCulture);44 45       return s;46     }47   }


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消