TCP客户端连接TCP服务器端有几种应用状态:
与服务器的连接已建立
与服务器的连接已断开
与服务器的连接发生异常
应用程序可按需求合理处理这些逻辑,比如:
连接断开后自动重连
连接断开后选择备用地址重连
所有状态变化上报告警
本文描述的TcpClient实现了状态变化的事件通知机制。
1 ///2 /// 异步TCP客户端 3 /// 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 ///17 /// 异步TCP客户端 18 /// 19 /// 远端服务器终结点 20 public AsyncTcpClient(IPEndPoint remoteEP) 21 : this(new[] { remoteEP.Address }, remoteEP.Port) 22 { 23 } 24 25 ///26 /// 异步TCP客户端 27 /// 28 /// 远端服务器终结点 29 /// 本地客户端终结点 30 public AsyncTcpClient(IPEndPoint remoteEP, IPEndPoint localEP) 31 : this(new[] { remoteEP.Address }, remoteEP.Port, localEP) 32 { 33 } 34 35 ///36 /// 异步TCP客户端 37 /// 38 /// 远端服务器IP地址 39 /// 远端服务器端口 40 public AsyncTcpClient(IPAddress remoteIPAddress, int remotePort) 41 : this(new[] { remoteIPAddress }, remotePort) 42 { 43 } 44 45 ///46 /// 异步TCP客户端 47 /// 48 /// 远端服务器IP地址 49 /// 远端服务器端口 50 /// 本地客户端终结点 51 public AsyncTcpClient( 52 IPAddress remoteIPAddress, int remotePort, IPEndPoint localEP) 53 : this(new[] { remoteIPAddress }, remotePort, localEP) 54 { 55 } 56 57 ///58 /// 异步TCP客户端 59 /// 60 /// 远端服务器主机名 61 /// 远端服务器端口 62 public AsyncTcpClient(string remoteHostName, int remotePort) 63 : this(Dns.GetHostAddresses(remoteHostName), remotePort) 64 { 65 } 66 67 ///68 /// 异步TCP客户端 69 /// 70 /// 远端服务器主机名 71 /// 远端服务器端口 72 /// 本地客户端终结点 73 public AsyncTcpClient( 74 string remoteHostName, int remotePort, IPEndPoint localEP) 75 : this(Dns.GetHostAddresses(remoteHostName), remotePort, localEP) 76 { 77 } 78 79 ///80 /// 异步TCP客户端 81 /// 82 /// 远端服务器IP地址列表 83 /// 远端服务器端口 84 public AsyncTcpClient(IPAddress[] remoteIPAddresses, int remotePort) 85 : this(remoteIPAddresses, remotePort, null) 86 { 87 } 88 89 ///90 /// 异步TCP客户端 91 /// 92 /// 远端服务器IP地址列表 93 /// 远端服务器端口 94 /// 本地客户端终结点 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 ///121 /// 是否已与服务器建立连接122 /// 123 public bool Connected { get { return tcpClient.Client.Connected; } }124 ///125 /// 远端服务器的IP地址列表126 /// 127 public IPAddress[] Addresses { get; private set; }128 ///129 /// 远端服务器的端口130 /// 131 public int Port { get; private set; }132 ///133 /// 连接重试次数134 /// 135 public int Retries { get; set; }136 ///137 /// 连接重试间隔138 /// 139 public int RetryInterval { get; set; }140 ///141 /// 远端服务器终结点142 /// 143 public IPEndPoint RemoteIPEndPoint 144 { 145 get { return new IPEndPoint(Addresses[0], Port); } 146 }147 ///148 /// 本地客户端终结点149 /// 150 protected IPEndPoint LocalIPEndPoint { get; private set; }151 ///152 /// 通信所使用的编码153 /// 154 public Encoding Encoding { get; set; }155 156 #endregion157 158 #region Connect159 160 ///161 /// 连接到服务器162 /// 163 ///异步TCP客户端 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 ///177 /// 关闭与服务器的连接178 /// 179 ///异步TCP客户端 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 ///276 /// 接收到数据报文事件277 /// 278 public event EventHandler> DatagramReceived;279 /// 280 /// 接收到数据报文明文事件281 /// 282 public event EventHandler> PlaintextReceived;283 284 private void RaiseDatagramReceived(TcpClient sender, byte[] datagram)285 {286 if (DatagramReceived != null)287 {288 DatagramReceived(this, 289 new TcpDatagramReceivedEventArgs (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 (299 sender, this.Encoding.GetString(datagram, 0, datagram.Length)));300 }301 }302 303 /// 304 /// 与服务器的连接已建立事件305 /// 306 public event EventHandlerServerConnected;307 /// 308 /// 与服务器的连接已断开事件309 /// 310 public event EventHandlerServerDisconnected;311 /// 312 /// 与服务器的连接发生异常事件313 /// 314 public event EventHandlerServerExceptionOccurred;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 /// 350 /// 发送报文351 /// 352 /// 报文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 ///375 /// 发送报文376 /// 377 /// 报文378 public void Send(string datagram)379 {380 Send(this.Encoding.GetBytes(datagram));381 }382 383 #endregion384 385 #region IDisposable Members386 387 ///388 /// Performs application-defined tasks associated with freeing, 389 /// releasing, or resetting unmanaged resources.390 /// 391 public void Dispose()392 {393 Dispose(true);394 GC.SuppressFinalize(this);395 }396 397 ///398 /// Releases unmanaged and - optionally - managed resources399 /// 400 ///true to release both managed 401 /// and unmanaged resources;false 402 /// to release only unmanaged resources.403 /// 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(client_ServerExceptionOccurred);16 client.ServerConnected +=
17 new EventHandler(client_ServerConnected);18 client.ServerDisconnected +=
19 new EventHandler(client_ServerDisconnected);20 client.PlaintextReceived +=
21 new EventHandler>(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 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 } 1 ///2 /// Internal class to join the TCP client and buffer together 3 /// for easy management in the server 4 /// 5 internal class TcpClientState 6 { 7 ///8 /// Constructor for a new Client 9 /// 10 /// The TCP client11 /// The byte array buffer12 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 ///24 /// Gets the TCP Client25 /// 26 public TcpClient TcpClient { get; private set; }27 28 ///29 /// Gets the Buffer.30 /// 31 public byte[] Buffer { get; private set; }32 33 ///34 /// Gets the network stream35 /// 36 public NetworkStream NetworkStream37 {38 get { return TcpClient.GetStream(); }39 }40 }
1 ///2 /// 与客户端的连接已建立事件参数 3 /// 4 public class TcpClientConnectedEventArgs : EventArgs 5 { 6 ///7 /// 与客户端的连接已建立事件参数 8 /// 9 /// 客户端10 public TcpClientConnectedEventArgs(TcpClient tcpClient)11 {12 if (tcpClient == null)13 throw new ArgumentNullException("tcpClient");14 15 this.TcpClient = tcpClient;16 }17 18 ///19 /// 客户端20 /// 21 public TcpClient TcpClient { get; private set; }22 }
////// 与客户端的连接已断开事件参数 /// public class TcpClientDisconnectedEventArgs : EventArgs { ////// 与客户端的连接已断开事件参数 /// /// 客户端 public TcpClientDisconnectedEventArgs(TcpClient tcpClient) { if (tcpClient == null) throw new ArgumentNullException("tcpClient"); this.TcpClient = tcpClient; } ////// 客户端 /// public TcpClient TcpClient { get; private set; } }
1 ///2 /// 与服务器的连接发生异常事件参数 3 /// 4 public class TcpServerExceptionOccurredEventArgs : EventArgs 5 { 6 ///7 /// 与服务器的连接发生异常事件参数 8 /// 9 /// 服务器IP地址列表10 /// 服务器端口11 /// 内部异常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 ///24 /// 服务器IP地址列表25 /// 26 public IPAddress[] Addresses { get; private set; }27 ///28 /// 服务器端口29 /// 30 public int Port { get; private set; }31 ///32 /// 内部异常33 /// 34 public Exception Exception { get; private set; }35 36 ///37 /// Returns a 39 ///that represents this instance.38 /// 40 /// A 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 }that represents this instance.41 ///
1 ///2 /// 接收到数据报文事件参数 3 /// 4 ///报文类型 5 public class TcpDatagramReceivedEventArgs: EventArgs 6 { 7 /// 8 /// 接收到数据报文事件参数 9 /// 10 /// 客户端11 /// 报文12 public TcpDatagramReceivedEventArgs(TcpClient tcpClient, T datagram)13 {14 TcpClient = tcpClient;15 Datagram = datagram;16 }17 18 ///19 /// 客户端20 /// 21 public TcpClient TcpClient { get; private set; }22 ///23 /// 报文24 /// 25 public T Datagram { get; private set; }26 }
1 ///2 /// 与服务器的连接已建立事件参数 3 /// 4 public class TcpServerConnectedEventArgs : EventArgs 5 { 6 ///7 /// 与服务器的连接已建立事件参数 8 /// 9 /// 服务器IP地址列表10 /// 服务器端口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 ///21 /// 服务器IP地址列表22 /// 23 public IPAddress[] Addresses { get; private set; }24 ///25 /// 服务器端口26 /// 27 public int Port { get; private set; }28 29 ///30 /// Returns a 32 ///that represents this instance.31 /// 33 /// A 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 }that represents this instance.34 ///
1 ///2 /// 与服务器的连接已断开事件参数 3 /// 4 public class TcpServerDisconnectedEventArgs : EventArgs 5 { 6 ///7 /// 与服务器的连接已断开事件参数 8 /// 9 /// 服务器IP地址列表10 /// 服务器端口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 ///21 /// 服务器IP地址列表22 /// 23 public IPAddress[] Addresses { get; private set; }24 ///25 /// 服务器端口26 /// 27 public int Port { get; private set; }28 29 ///30 /// Returns a 32 ///that represents this instance.31 /// 33 /// A 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 }that represents this instance.34 ///



