TCP客户端连接TCP服务器端有几种应用状态:
与服务器的连接已建立
与服务器的连接已断开
与服务器的连接发生异常
应用程序可按需求合理处理这些逻辑,比如:
连接断开后自动重连
连接断开后选择备用地址重连
所有状态变化上报告警
本文描述的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 点赞
评论
共同学习,写下你的评论
评论加载中...
作者其他优质文章
正在加载中
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦