C# 对 TCP 客户端的状态封装
2013-04-14 20:27
387 查看
[b]本文为 Dennis Gao 原创技术文章,发表于博客园博客,未经作者本人允许禁止任何形式的转载。[/b]
TCP客户端连接TCP服务器端有几种应用状态:
与服务器的连接已建立
与服务器的连接已断开
与服务器的连接发生异常
应用程序可按需求合理处理这些逻辑,比如:
连接断开后自动重连
连接断开后选择备用地址重连
所有状态变化上报告警
本文描述的TcpClient实现了状态变化的事件通知机制。
[b]本文为 Dennis Gao 原创技术文章,发表于博客园博客,未经作者本人允许禁止任何形式的转载。[/b]
TCP客户端连接TCP服务器端有几种应用状态:
与服务器的连接已建立
与服务器的连接已断开
与服务器的连接发生异常
应用程序可按需求合理处理这些逻辑,比如:
连接断开后自动重连
连接断开后选择备用地址重连
所有状态变化上报告警
本文描述的TcpClient实现了状态变化的事件通知机制。
/// <summary> /// 异步TCP客户端 /// </summary> public class AsyncTcpClient : IDisposable { #region Fields private TcpClient tcpClient; private bool disposed = false; private int retries = 0; #endregion #region Ctors /// <summary> /// 异步TCP客户端 /// </summary> /// <param name="remoteEP">远端服务器终结点</param> public AsyncTcpClient(IPEndPoint remoteEP) : this(new[] { remoteEP.Address }, remoteEP.Port) { } /// <summary> /// 异步TCP客户端 /// </summary> /// <param name="remoteEP">远端服务器终结点</param> /// <param name="localEP">本地客户端终结点</param> public AsyncTcpClient(IPEndPoint remoteEP, IPEndPoint localEP) : this(new[] { remoteEP.Address }, remoteEP.Port, localEP) { } /// <summary> /// 异步TCP客户端 /// </summary> /// <param name="remoteIPAddress">远端服务器IP地址</param> /// <param name="remotePort">远端服务器端口</param> public AsyncTcpClient(IPAddress remoteIPAddress, int remotePort) : this(new[] { remoteIPAddress }, remotePort) { } /// <summary> /// 异步TCP客户端 /// </summary> /// <param name="remoteIPAddress">远端服务器IP地址</param> /// <param name="remotePort">远端服务器端口</param> /// <param name="localEP">本地客户端终结点</param> public AsyncTcpClient( IPAddress remoteIPAddress, int remotePort, IPEndPoint localEP) : this(new[] { remoteIPAddress }, remotePort, localEP) { } /// <summary> /// 异步TCP客户端 /// </summary> /// <param name="remoteHostName">远端服务器主机名</param> /// <param name="remotePort">远端服务器端口</param> public AsyncTcpClient(string remoteHostName, int remotePort) : this(Dns.GetHostAddresses(remoteHostName), remotePort) { } /// <summary> /// 异步TCP客户端 /// </summary> /// <param name="remoteHostName">远端服务器主机名</param> /// <param name="remotePort">远端服务器端口</param> /// <param name="localEP">本地客户端终结点</param> public AsyncTcpClient( string remoteHostName, int remotePort, IPEndPoint localEP) : this(Dns.GetHostAddresses(remoteHostName), remotePort, localEP) { } /// <summary> /// 异步TCP客户端 /// </summary> /// <param name="remoteIPAddresses">远端服务器IP地址列表</param> /// <param name="remotePort">远端服务器端口</param> public AsyncTcpClient(IPAddress[] remoteIPAddresses, int remotePort) : this(remoteIPAddresses, remotePort, null) { } /// <summary> /// 异步TCP客户端 /// </summary> /// <param name="remoteIPAddresses">远端服务器IP地址列表</param> /// <param name="remotePort">远端服务器端口</param> /// <param name="localEP">本地客户端终结点</param> public AsyncTcpClient( IPAddress[] remoteIPAddresses, int remotePort, IPEndPoint localEP) { this.Addresses = remoteIPAddresses; this.Port = remotePort; this.LocalIPEndPoint = localEP; this.Encoding = Encoding.Default; if (this.LocalIPEndPoint != null) { this.tcpClient = new TcpClient(this.LocalIPEndPoint); } else { this.tcpClient = new TcpClient(); } Retries = 3; RetryInterval = 5; } #endregion #region Properties /// <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; } /// <summary> /// 连接重试次数 /// </summary> public int Retries { get; set; } /// <summary> /// 连接重试间隔 /// </summary> public int RetryInterval { get; set; } /// <summary> /// 远端服务器终结点 /// </summary> public IPEndPoint RemoteIPEndPoint { get { return new IPEndPoint(Addresses[0], Port); } } /// <summary> /// 本地客户端终结点 /// </summary> protected IPEndPoint LocalIPEndPoint { get; private set; } /// <summary> /// 通信所使用的编码 /// </summary> public Encoding Encoding { get; set; } #endregion #region Connect /// <summary> /// 连接到服务器 /// </summary> /// <returns>异步TCP客户端</returns> public AsyncTcpClient Connect() { if (!Connected) { // start the async connect operation tcpClient.BeginConnect( Addresses, Port, HandleTcpServerConnected, tcpClient); } return this; } /// <summary> /// 关闭与服务器的连接 /// </summary> /// <returns>异步TCP客户端</returns> public AsyncTcpClient Close() { if (Connected) { retries = 0; tcpClient.Close(); RaiseServerDisconnected(Addresses, Port); } return this; } #endregion #region Receive private void HandleTcpServerConnected(IAsyncResult ar) { try { tcpClient.EndConnect(ar); RaiseServerConnected(Addresses, Port); 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. RaiseServerExceptionOccurred(Addresses, Port, ex); 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, HandleDatagramReceived, buffer); } private void HandleDatagramReceived(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); RaiseDatagramReceived(tcpClient, receivedBytes); RaisePlaintextReceived(tcpClient, receivedBytes); // then start reading from the network again stream.BeginRead( buffer, 0, buffer.Length, HandleDatagramReceived, buffer); } #endregion #region Events /// <summary> /// 接收到数据报文事件 /// </summary> public event EventHandler<TcpDatagramReceivedEventArgs<byte[]>> DatagramReceived; /// <summary> /// 接收到数据报文明文事件 /// </summary> public event EventHandler<TcpDatagramReceivedEventArgs<string>> PlaintextReceived; private void RaiseDatagramReceived(TcpClient sender, byte[] datagram) { if (DatagramReceived != null) { DatagramReceived(this, new TcpDatagramReceivedEventArgs<byte[]>(sender, datagram)); } } private void RaisePlaintextReceived(TcpClient sender, byte[] datagram) { if (PlaintextReceived != null) { PlaintextReceived(this, new TcpDatagramReceivedEventArgs<string>( sender, this.Encoding.GetString(datagram, 0, datagram.Length))); } } /// <summary> /// 与服务器的连接已建立事件 /// </summary> public event EventHandler<TcpServerConnectedEventArgs> ServerConnected; /// <summary> /// 与服务器的连接已断开事件 /// </summary> public event EventHandler<TcpServerDisconnectedEventArgs> ServerDisconnected; /// <summary> /// 与服务器的连接发生异常事件 /// </summary> public event EventHandler<TcpServerExceptionOccurredEventArgs> ServerExceptionOccurred; private void RaiseServerConnected(IPAddress[] ipAddresses, int port) { if (ServerConnected != null) { ServerConnected(this, new TcpServerConnectedEventArgs(ipAddresses, port)); } } private void RaiseServerDisconnected(IPAddress[] ipAddresses, int port) { if (ServerDisconnected != null) { ServerDisconnected(this, new TcpServerDisconnectedEventArgs(ipAddresses, port)); } } private void RaiseServerExceptionOccurred( IPAddress[] ipAddresses, int port, Exception innerException) { if (ServerExceptionOccurred != null) { ServerExceptionOccurred(this, new TcpServerExceptionOccurredEventArgs( ipAddresses, port, innerException)); } } #endregion #region Send /// <summary> /// 发送报文 /// </summary> /// <param name="datagram">报文</param> public void Send(byte[] datagram) { if (datagram == null) throw new ArgumentNullException("datagram"); if (!Connected) { RaiseServerDisconnected(Addresses, Port); throw new InvalidProgramException( "This client has not connected to server."); } tcpClient.GetStream().BeginWrite( datagram, 0, datagram.Length, HandleDatagramWritten, tcpClient); } private void HandleDatagramWritten(IAsyncResult ar) { ((TcpClient)ar.AsyncState).GetStream().EndWrite(ar); } /// <summary> /// 发送报文 /// </summary> /// <param name="datagram">报文</param> public void Send(string datagram) { Send(this.Encoding.GetBytes(datagram)); } #endregion #region IDisposable Members /// <summary> /// Performs application-defined tasks associated with freeing, /// releasing, or resetting unmanaged resources. /// </summary> public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// <summary> /// Releases unmanaged and - optionally - managed resources /// </summary> /// <param name="disposing"><c>true</c> to release both managed /// and unmanaged resources; <c>false</c> /// to release only unmanaged resources. /// </param> protected virtual void Dispose(bool disposing) { if (!this.disposed) { if (disposing) { try { Close(); if (tcpClient != null) { tcpClient = null; } } catch (SocketException ex) { ExceptionHandler.Handle(ex); } } disposed = true; } } #endregion }
使用举例
class Program { static AsyncTcpClient client; static void Main(string[] args) { LogFactory.Assign(new ConsoleLogFactory()); // 测试用,可以不指定由系统选择端口 IPEndPoint remoteEP = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 9999); IPEndPoint localEP = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 9998); client = new AsyncTcpClient(remoteEP, localEP); client.Encoding = Encoding.UTF8; client.ServerExceptionOccurred += new EventHandler<TcpServerExceptionOccurredEventArgs>(client_ServerExceptionOccurred); client.ServerConnected += new EventHandler<TcpServerConnectedEventArgs>(client_ServerConnected); client.ServerDisconnected += new EventHandler<TcpServerDisconnectedEventArgs>(client_ServerDisconnected); client.PlaintextReceived += new EventHandler<TcpDatagramReceivedEventArgs<string>>(client_PlaintextReceived); client.Connect(); Console.WriteLine("TCP client has connected to server."); Console.WriteLine("Type something to send to server..."); while (true) { try { string text = Console.ReadLine(); client.Send(text); } catch (Exception ex) { Console.WriteLine(ex.Message); } } } static void client_ServerExceptionOccurred( object sender, TcpServerExceptionOccurredEventArgs e) { Logger.Debug(string.Format(CultureInfo.InvariantCulture, "TCP server {0} exception occurred, {1}.", e.ToString(), e.Exception.Message)); } static void client_ServerConnected( object sender, TcpServerConnectedEventArgs e) { Logger.Debug(string.Format(CultureInfo.InvariantCulture, "TCP server {0} has connected.", e.ToString())); } static void client_ServerDisconnected( object sender, TcpServerDisconnectedEventArgs e) { Logger.Debug(string.Format(CultureInfo.InvariantCulture, "TCP server {0} has disconnected.", e.ToString())); } static void client_PlaintextReceived( object sender, TcpDatagramReceivedEventArgs<string> e) { Console.Write(string.Format("Server : {0} --> ", e.TcpClient.Client.RemoteEndPoint.ToString())); Console.WriteLine(string.Format("{0}", e.Datagram)); } }
TCP客户端State
/// <summary> /// Internal class to join the TCP client and buffer together /// for easy management in the server /// </summary> internal class TcpClientState { /// <summary> /// Constructor for a new Client /// </summary> /// <param name="tcpClient">The TCP client</param> /// <param name="buffer">The byte array buffer</param> public TcpClientState(TcpClient tcpClient, byte[] buffer) { if (tcpClient == null) throw new ArgumentNullException("tcpClient"); if (buffer == null) throw new ArgumentNullException("buffer"); this.TcpClient = tcpClient; this.Buffer = buffer; } /// <summary> /// Gets the TCP Client /// </summary> public TcpClient TcpClient { get; private set; } /// <summary> /// Gets the Buffer. /// </summary> public byte[] Buffer { get; private set; } /// <summary> /// Gets the network stream /// </summary> public NetworkStream NetworkStream { get { return TcpClient.GetStream(); } } }
与客户端的连接已建立事件参数
/// <summary> /// 与客户端的连接已建立事件参数 /// </summary> public class TcpClientConnectedEventArgs : EventArgs { /// <summary> /// 与客户端的连接已建立事件参数 /// </summary> /// <param name="tcpClient">客户端</param> public TcpClientConnectedEventArgs(TcpClient tcpClient) { if (tcpClient == null) throw new ArgumentNullException("tcpClient"); this.TcpClient = tcpClient; } /// <summary> /// 客户端 /// </summary> public TcpClient TcpClient { get; private set; } }
与客户端的连接已断开事件参数
/// <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; } }
与服务器的连接发生异常事件参数
/// <summary> /// 与服务器的连接发生异常事件参数 /// </summary> public class TcpServerExceptionOccurredEventArgs : EventArgs { /// <summary> /// 与服务器的连接发生异常事件参数 /// </summary> /// <param name="ipAddresses">服务器IP地址列表</param> /// <param name="port">服务器端口</param> /// <param name="innerException">内部异常</param> public TcpServerExceptionOccurredEventArgs( IPAddress[] ipAddresses, int port, Exception innerException) { if (ipAddresses == null) throw new ArgumentNullException("ipAddresses"); this.Addresses = ipAddresses; this.Port = port; this.Exception = innerException; } /// <summary> /// 服务器IP地址列表 /// </summary> public IPAddress[] Addresses { get; private set; } /// <summary> /// 服务器端口 /// </summary> public int Port { get; private set; } /// <summary> /// 内部异常 /// </summary> public Exception Exception { get; private set; } /// <summary> /// Returns a <see cref="System.String"/> that represents this instance. /// </summary> /// <returns> /// A <see cref="System.String"/> that represents this instance. /// </returns> public override string ToString() { string s = string.Empty; foreach (var item in Addresses) { s = s + item.ToString() + ','; } s = s.TrimEnd(','); s = s + ":" + Port.ToString(CultureInfo.InvariantCulture); return s; } }
接收到数据报文事件参数
/// <summary> /// 接收到数据报文事件参数 /// </summary> /// <typeparam name="T">报文类型</typeparam> public class TcpDatagramReceivedEventArgs<T> : EventArgs { /// <summary> /// 接收到数据报文事件参数 /// </summary> /// <param name="tcpClient">客户端</param> /// <param name="datagram">报文</param> public TcpDatagramReceivedEventArgs(TcpClient tcpClient, T datagram) { TcpClient = tcpClient; Datagram = datagram; } /// <summary> /// 客户端 /// </summary> public TcpClient TcpClient { get; private set; } /// <summary> /// 报文 /// </summary> public T Datagram { get; private set; } }
与服务器的连接已建立事件参数
/// <summary> /// 与服务器的连接已建立事件参数 /// </summary> public class TcpServerConnectedEventArgs : EventArgs { /// <summary> /// 与服务器的连接已建立事件参数 /// </summary> /// <param name="ipAddresses">服务器IP地址列表</param> /// <param name="port">服务器端口</param> public TcpServerConnectedEventArgs(IPAddress[] ipAddresses, int port) { if (ipAddresses == null) throw new ArgumentNullException("ipAddresses"); this.Addresses = ipAddresses; this.Port = port; } /// <summary> /// 服务器IP地址列表 /// </summary> public IPAddress[] Addresses { get; private set; } /// <summary> /// 服务器端口 /// </summary> public int Port { get; private set; } /// <summary> /// Returns a <see cref="System.String"/> that represents this instance. /// </summary> /// <returns> /// A <see cref="System.String"/> that represents this instance. /// </returns> public override string ToString() { string s = string.Empty; foreach (var item in Addresses) { s = s + item.ToString() + ','; } s = s.TrimEnd(','); s = s + ":" + Port.ToString(CultureInfo.InvariantCulture); return s; } }
与服务器的连接已断开事件参数
/// <summary> /// 与服务器的连接已断开事件参数 /// </summary> public class TcpServerDisconnectedEventArgs : EventArgs { /// <summary> /// 与服务器的连接已断开事件参数 /// </summary> /// <param name="ipAddresses">服务器IP地址列表</param> /// <param name="port">服务器端口</param> public TcpServerDisconnectedEventArgs(IPAddress[] ipAddresses, int port) { if (ipAddresses == null) throw new ArgumentNullException("ipAddresses"); this.Addresses = ipAddresses; this.Port = port; } /// <summary> /// 服务器IP地址列表 /// </summary> public IPAddress[] Addresses { get; private set; } /// <summary> /// 服务器端口 /// </summary> public int Port { get; private set; } /// <summary> /// Returns a <see cref="System.String"/> that represents this instance. /// </summary> /// <returns> /// A <see cref="System.String"/> that represents this instance. /// </returns> public override string ToString() { string s = string.Empty; foreach (var item in Addresses) { s = s + item.ToString() + ','; } s = s.TrimEnd(','); s = s + ":" + Port.ToString(CultureInfo.InvariantCulture); return s; } }
[b]本文为 Dennis Gao 原创技术文章,发表于博客园博客,未经作者本人允许禁止任何形式的转载。[/b]
相关文章推荐
- 在C#中对TCP客户端的状态封装详解
- C# 检测TcpListener客户端状态
- C# TCP实现多个客户端与服务端 数据 与 文件的传输
- 【C#食谱】【杭帮菜】菜单2:写一个TCP客户端
- ASP.NET MVC WebApi 返回数据类型序列化控制(json,xml) 用javascript在客户端删除某一个cookie键值对 input点击链接另一个页面,各种操作。 C# 往线程里传参数的方法总结 TCP/IP 协议 用C#+Selenium+ChromeDriver 生成我的咕咚跑步路线地图 (转)值得学习百度开源70+项目
- C#实现的HttpSQS客户端封装类
- C# 海康DVR客户端开发系列(2)—— 封装API (2)
- C#封装的海康DVR客户端SDK
- C#获取网络链接状态的几种方法(己封装为类)
- 【c#源码】基于TCP通信的客户端断线重连
- Untiy中用C#实现TCP通讯(Socket通讯)服务端与客户端皆可
- C# 海康DVR客户端开发系列(2)—— 封装API (3)
- C#SocketAsyncEventArgs实现高效能多并发TCPSocket通信 (客户端实现)
- C# Socket TCP简单例子(服务器与客户端通信)
- C# 异步TCP Socket聊天室(1服务器,N客户端)
- [原创]Coding4Fun检测你的网络,用C#获取本机TCP、UDP状态及连接(一)
- 自己动手写Redis客户端(C#实现)2 - SET请求和状态回复(set)
- C#中TCP实现多个客户端与服务端数据与文件的传输
- C# tcp test 客户端
- C#封装的海康DVR客户端SDK