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

本文为 Dennis Gao 原创技术文章,发表于博客园博客,未经作者本人允许禁止任何形式的转载。

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       else
108       {
109         this.tcpClient = new TcpClient();
110       }
111 
112       Retries = 3;
113       RetryInterval = 5;
114     }
115 
116     #endregion
117 
118     #region Properties
119 
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     #endregion
157 
158     #region Connect
159 
160     /// <summary>
161     /// 连接到服务器
162     /// </summary>
163     /// <returns>异步TCP客户端</returns>
164     public AsyncTcpClient Connect()
165     {
166       if (!Connected)
167       {
168         // start the async connect operation
169         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     #endregion
193 
194     #region Receive
195 
196     private void HandleTcpServerConnected(IAsyncResult ar)
197     {
198       try
199       {
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         else
222         {
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       try
244       {
245         numberOfReadBytes = stream.EndRead(ar);
246       }
247       catch
248       {
249         numberOfReadBytes = 0;
250       }
251 
252       if (numberOfReadBytes == 0)
253       {
254         // connection has been closed
255         Close();
256         return;
257       }
258 
259       // received byte and trigger event notification
260       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 again
267       stream.BeginRead(
268         buffer, 0, buffer.Length, HandleDatagramReceived, buffer);
269     }
270 
271     #endregion
272 
273     #region Events
274 
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     #endregion
346 
347     #region Send
348 
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     #endregion
384 
385     #region IDisposable Members
386 
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 resources
399     /// </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           try
411           {
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     #endregion
430   }

使用举例

 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         try
29         {
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 Client
25     /// </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 stream
35     /// </summary>
36     public NetworkStream NetworkStream
37     {
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   }

本文为 Dennis Gao 原创技术文章,发表于博客园博客,未经作者本人允许禁止任何形式的转载。

posted @ 2013-04-14 20:27  sangmado  阅读(16808)  评论(31编辑  收藏  举报