异步 TCP 通信 ---- AsyncTcpClient
1 using System; 2 using System.Diagnostics; 3 using System.Globalization; 4 using System.Net; 5 using System.Net.Sockets; 6 using System.Text; 7 using System.Threading; 8 9 namespace JCommon.Network 10 { 11 /// <summary> 12 /// 异步 TCP 客户端 13 /// </summary> 14 public class AsyncTcpClient : IDisposable 15 { 16 public event EventHandler<TcpDatagramReceivedEventArgs<byte[]>> DatagramReceived; // 接收到数据报文事件 17 public event EventHandler<TcpDatagramReceivedEventArgs<string>> PlaintextReceived; // 接收到数据报文明文事件 18 public event EventHandler<TcpServerConnectedEventArgs> ServerConnected; // 与服务器的连接已建立事件 19 public event EventHandler<TcpServerDisconnectedEventArgs> ServerDisconnected; // 与服务器的连接已断开事件 20 public event EventHandler<TcpServerExceptionOccurredEventArgs> ServerExceptionOccurred; // 与服务器的连接发生异常事件 21 22 private TcpClient tcpClient; 23 private bool disposed = false; 24 private int retries = 0; // 重连计数 25 26 public AsyncTcpClient(IPAddress remoteIPAddress, int remotePort) 27 { 28 this.Addresses = remoteIPAddress; 29 this.Port = remotePort; 30 this.Encoding = Encoding.Default; 31 this.Retries = 3; 32 this.RetryInterval = 5; 33 } 34 35 /// <summary> 36 /// 是否已与服务器建立连接 37 /// </summary> 38 public bool Connected 39 { 40 get 41 { 42 return this.tcpClient.Client.Connected; 43 } 44 } 45 46 /// <summary> 47 /// 远端服务器的IP地址列表 48 /// </summary> 49 public IPAddress Addresses { get; private set; } 50 51 /// <summary> 52 /// 远端服务器的端口 53 /// </summary> 54 public int Port { get; private set; } 55 56 /// <summary> 57 /// 连接重试次数 58 /// </summary> 59 public int Retries { get; set; } 60 61 /// <summary> 62 /// 连接重试间隔 63 /// </summary> 64 public int RetryInterval { get; set; } 65 66 /// <summary> 67 /// 远端服务器终结点 68 /// </summary> 69 public IPEndPoint RemoteIPEndPoint 70 { 71 get 72 { 73 return new IPEndPoint(this.Addresses, this.Port); 74 } 75 } 76 77 /// <summary> 78 /// 通信所使用的编码 79 /// </summary> 80 public Encoding Encoding { get; set; } 81 82 /// <summary> 83 /// 连接到服务器 84 /// </summary> 85 /// <returns></returns> 86 public AsyncTcpClient Connect() 87 { 88 this.tcpClient = new TcpClient(); 89 if (!this.Connected) 90 { 91 this.tcpClient.BeginConnect(this.Addresses, this.Port, new AsyncCallback(this.HandleTcpServerConnected), this.tcpClient); 92 } 93 return this; 94 } 95 96 /// <summary> 97 /// 关闭与服务器的连接 98 /// </summary> 99 /// <returns>异步TCP客户端</returns> 100 public AsyncTcpClient Close() 101 { 102 if (this.Connected) 103 { 104 this.retries = 0; 105 this.tcpClient.Close(); 106 this.RaiseServerDisconnected(this.Addresses, this.Port); 107 } 108 return this; 109 } 110 111 private void HandleTcpServerConnected(IAsyncResult ar) 112 { 113 try 114 { 115 this.tcpClient.EndConnect(ar); 116 this.RaiseServerConnected(this.Addresses, this.Port); 117 this.retries = 0; 118 } 119 catch (Exception ex) 120 { 121 // 限制重连次数 122 if (this.Retries > 0) 123 { 124 if (this.retries > 0) 125 { 126 Trace.WriteLine(string.Format(CultureInfo.InvariantCulture, "Connect to server with retry {0} failed.", retries)); 127 } 128 129 this.retries++; 130 if (this.retries > this.Retries) 131 { 132 this.RaiseServerExceptionOccurred(this.Addresses, this.Port, ex); 133 return; 134 } 135 } 136 137 Thread.Sleep(TimeSpan.FromSeconds((double)this.RetryInterval)); 138 this.Connect(); 139 return; 140 } 141 byte[] buffer = new byte[this.tcpClient.ReceiveBufferSize]; 142 this.tcpClient.GetStream().BeginRead(buffer, 0, buffer.Length, new AsyncCallback(this.HandleDatagramReceived), buffer); 143 } 144 145 private void HandleDatagramReceived(IAsyncResult ar) 146 { 147 try 148 { 149 NetworkStream stream = this.tcpClient.GetStream(); 150 int numberOfReadBytes = 0; 151 try 152 { 153 numberOfReadBytes = stream.EndRead(ar); 154 } 155 catch 156 { 157 numberOfReadBytes = 0; 158 } 159 if (numberOfReadBytes == 0) 160 { 161 this.Close(); 162 163 // 断开重连可写在这里 164 Trace.WriteLine("连接断开,尝试重连..."); 165 this.Connect(); 166 } 167 else 168 { 169 byte[] buffer = (byte[])ar.AsyncState; 170 byte[] receivedBytes = new byte[numberOfReadBytes]; 171 Buffer.BlockCopy(buffer, 0, receivedBytes, 0, numberOfReadBytes); 172 this.RaiseDatagramReceived(this.tcpClient, receivedBytes); 173 this.RaisePlaintextReceived(this.tcpClient, receivedBytes); 174 175 // then start reading from the network again 176 stream.BeginRead(buffer, 0, buffer.Length, new AsyncCallback(this.HandleDatagramReceived), buffer); 177 } 178 } 179 catch(Exception ex) 180 { 181 Trace.WriteLine(ex.Message); 182 } 183 } 184 185 private void RaiseDatagramReceived(TcpClient sender, byte[] datagram) 186 { 187 if (this.DatagramReceived != null) 188 { 189 this.DatagramReceived(this, new TcpDatagramReceivedEventArgs<byte[]>(sender, datagram)); 190 } 191 } 192 193 private void RaisePlaintextReceived(TcpClient sender, byte[] datagram) 194 { 195 if (this.PlaintextReceived != null) 196 { 197 this.PlaintextReceived(this, new TcpDatagramReceivedEventArgs<string>(sender, this.Encoding.GetString(datagram, 0, datagram.Length))); 198 } 199 } 200 201 private void RaiseServerConnected(IPAddress ipAddresses, int port) 202 { 203 if (this.ServerConnected != null) 204 { 205 this.ServerConnected(this, new TcpServerConnectedEventArgs(ipAddresses, port)); 206 } 207 } 208 209 private void RaiseServerDisconnected(IPAddress ipAddresses, int port) 210 { 211 if (this.ServerDisconnected != null) 212 { 213 this.ServerDisconnected(this, new TcpServerDisconnectedEventArgs(ipAddresses, port)); 214 } 215 } 216 217 private void RaiseServerExceptionOccurred(IPAddress ipAddresses, int port, Exception innerException) 218 { 219 if (this.ServerExceptionOccurred != null) 220 { 221 this.ServerExceptionOccurred(this, new TcpServerExceptionOccurredEventArgs(ipAddresses, port, innerException)); 222 } 223 } 224 225 /// <summary> 226 /// 发送报文 227 /// </summary> 228 /// <param name="datagram"></param> 229 public void Send(byte[] datagram) 230 { 231 if (datagram == null) 232 { 233 throw new ArgumentNullException("datagram"); 234 } 235 if (!this.Connected) 236 { 237 this.RaiseServerDisconnected(this.Addresses, this.Port); 238 throw new InvalidProgramException("This client has not connected to server."); 239 } 240 this.tcpClient.GetStream().BeginWrite(datagram, 0, datagram.Length, new AsyncCallback(this.HandleDatagramWritten), this.tcpClient); 241 } 242 243 private void HandleDatagramWritten(IAsyncResult ar) 244 { 245 ((TcpClient)ar.AsyncState).GetStream().EndWrite(ar); 246 } 247 248 public void Send(string datagram) 249 { 250 this.Send(this.Encoding.GetBytes(datagram)); 251 } 252 253 /// <summary> 254 /// 释放非托管资源 255 /// </summary> 256 public void Dispose() 257 { 258 this.Dispose(true); 259 GC.SuppressFinalize(this); 260 } 261 262 protected virtual void Dispose(bool disposing) 263 { 264 if (!this.disposed) 265 { 266 if (disposing) 267 { 268 try 269 { 270 this.Close(); 271 if (this.tcpClient != null) 272 { 273 this.tcpClient = null; 274 } 275 } 276 catch// (SocketException ex) 277 { 278 } 279 } 280 this.disposed = true; 281 } 282 } 283 } 284 }
1 using System; 2 using System.Net.Sockets; 3 4 namespace JCommon.Network 5 { 6 /// <summary> 7 /// 接收到数据报文事件 8 /// </summary> 9 /// <typeparam name="T"></typeparam> 10 public class TcpDatagramReceivedEventArgs<T> : EventArgs 11 { 12 public TcpDatagramReceivedEventArgs(TcpClient tcpClient, T datagram) 13 { 14 this.TcpClient = tcpClient; 15 this.Datagram = datagram; 16 } 17 18 public TcpClient TcpClient { get; private set; } 19 20 public T Datagram { get; private set; } 21 } 22 }
1 using System; 2 using System.Net.Sockets; 3 4 namespace JCommon.Network 5 { 6 /// <summary> 7 /// 接收到数据报文事件 8 /// </summary> 9 /// <typeparam name="T"></typeparam> 10 public class TcpDatagramReceivedEventArgs<T> : EventArgs 11 { 12 public TcpDatagramReceivedEventArgs(TcpClient tcpClient, T datagram) 13 { 14 this.TcpClient = tcpClient; 15 this.Datagram = datagram; 16 } 17 18 public TcpClient TcpClient { get; private set; } 19 20 public T Datagram { get; private set; } 21 } 22 }
1 using System; 2 using System.Globalization; 3 using System.Net; 4 5 namespace JCommon.Network 6 { 7 /// <summary> 8 /// 与服务器的连接已建立事件 9 /// </summary> 10 public class TcpServerConnectedEventArgs : EventArgs 11 { 12 public TcpServerConnectedEventArgs(IPAddress ipAddress, int port) 13 { 14 if (ipAddress == null) 15 { 16 throw new ArgumentNullException("ipAddress"); 17 } 18 this.Address = ipAddress; 19 this.Port = port; 20 } 21 22 public IPAddress Address { get; private set; } 23 24 public int Port { get; private set; } 25 26 public override string ToString() 27 { 28 return this.Address + ":" + this.Port.ToString(CultureInfo.InvariantCulture); 29 } 30 } 31 }
1 using System; 2 using System.Globalization; 3 using System.Net; 4 5 namespace JCommon.Network 6 { 7 /// <summary> 8 /// 与服务器的连接已断开事件 9 /// </summary> 10 public class TcpServerDisconnectedEventArgs : EventArgs 11 { 12 public TcpServerDisconnectedEventArgs(IPAddress ipAddress, int port) 13 { 14 if (ipAddress == null) 15 { 16 throw new ArgumentNullException("ipAddress"); 17 } 18 this.Address = ipAddress; 19 this.Port = port; 20 } 21 22 public IPAddress Address { get; private set; } 23 24 public int Port { get; private set; } 25 26 public override string ToString() 27 { 28 return this.Address + ":" + this.Port.ToString(CultureInfo.InvariantCulture); 29 } 30 } 31 }
1 using System; 2 using System.Globalization; 3 using System.Net; 4 5 namespace JCommon.Network 6 { 7 /// <summary> 8 /// 与服务器的连接发生异常事件 9 /// </summary> 10 public class TcpServerExceptionOccurredEventArgs : EventArgs 11 { 12 public TcpServerExceptionOccurredEventArgs(IPAddress ipAddresses, int port, Exception innerException) 13 { 14 if (ipAddresses == null) 15 { 16 throw new ArgumentNullException("ipAddress"); 17 } 18 this.Address = ipAddresses; 19 this.Port = port; 20 this.Exception = innerException; 21 } 22 23 public IPAddress Address { get; private set; } 24 25 public int Port { get; private set; } 26 27 public Exception Exception { get; private set; } 28 29 public override string ToString() 30 { 31 return this.Address + ":" + this.Port.ToString(CultureInfo.InvariantCulture); 32 } 33 } 34 }
1 using System; 2 using System.Collections.Generic; 3 using System.ComponentModel; 4 using System.Data; 5 using System.Drawing; 6 using System.Linq; 7 using System.Text; 8 using System.Threading.Tasks; 9 using System.Windows.Forms; 10 using JCommon.Network; 11 using System.Net; 12 13 namespace JCommon.Test 14 { 15 public partial class FrmTcpClient_aync : Form 16 { 17 Network.AsyncTcpClient client = null; 18 19 20 DelMsgShow msgShow; 21 22 void MsgShow(string str) 23 { 24 if (richTextBox1.InvokeRequired) 25 { 26 richTextBox1.Invoke(new Action<string>(MsgShow), new object[] { str }); 27 } 28 else 29 { 30 richTextBox1.AppendText(str); 31 richTextBox1.AppendText("\r\n"); 32 richTextBox1.ScrollToCaret(); 33 } 34 } 35 36 37 public FrmTcpClient_aync() 38 { 39 InitializeComponent(); 40 } 41 42 private void button1_Click(object sender, EventArgs e) 43 { 44 string strServerIP = textBox1.Text; 45 int nPort = textBox2.Text.ToInt(); 46 47 button1.Enabled = false; 48 if(this.client == null) 49 { 50 IPEndPoint ip = new IPEndPoint(IPAddress.Parse(strServerIP), nPort); 51 this.client = new AsyncTcpClient(IPAddress.Parse(strServerIP), nPort); 52 53 this.client.PlaintextReceived += this.onPlaintextReceived; 54 this.client.ServerConnected += this.onServerConnected; 55 this.client.ServerDisconnected += this.onServerDisconnected; 56 } 57 58 string str = button1.Text; 59 if (str == "连接") 60 { 61 this.client.Connect(); 62 button1.Text = "断开连接"; 63 } 64 else 65 { 66 this.client.Close(); 67 button1.Text = "连接"; 68 } 69 70 button1.Enabled = true; 71 } 72 73 private void button2_Click(object sender, EventArgs e) 74 { 75 string str = richTextBox2.Text; 76 if (this.client.Connected) 77 this.client.Send(str); 78 } 79 80 /// <summary> 81 /// 82 /// </summary> 83 /// <param name="sender"></param> 84 /// <param name="e"></param> 85 public void onPlaintextReceived(object sender, TcpDatagramReceivedEventArgs<string> e) 86 { 87 string data = e.Datagram; 88 msgShow("收到:" + data); 89 } 90 91 public void onServerConnected(object sender, TcpServerConnectedEventArgs e) 92 { 93 msgShow(this.Name + "连接成功"); 94 } 95 96 public void onServerDisconnected(object sender, TcpServerDisconnectedEventArgs e) 97 { 98 msgShow(this.Name + "连接断开"); 99 } 100 101 private void FrmTcpClient_aync_Load(object sender, EventArgs e) 102 { 103 msgShow = this.MsgShow; 104 this.textBox1.Text = "127.0.0.1"; 105 this.textBox2.Text = "20000"; 106 } 107 } 108 }
1 namespace JCommon.Test 2 { 3 partial class FrmTcpClient_aync 4 { 5 /// <summary> 6 /// Required designer variable. 7 /// </summary> 8 private System.ComponentModel.IContainer components = null; 9 10 /// <summary> 11 /// Clean up any resources being used. 12 /// </summary> 13 /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param> 14 protected override void Dispose(bool disposing) 15 { 16 if (disposing && (components != null)) 17 { 18 components.Dispose(); 19 } 20 base.Dispose(disposing); 21 } 22 23 #region Windows Form Designer generated code 24 25 /// <summary> 26 /// Required method for Designer support - do not modify 27 /// the contents of this method with the code editor. 28 /// </summary> 29 private void InitializeComponent() 30 { 31 this.richTextBox2 = new System.Windows.Forms.RichTextBox(); 32 this.richTextBox1 = new System.Windows.Forms.RichTextBox(); 33 this.button2 = new System.Windows.Forms.Button(); 34 this.button1 = new System.Windows.Forms.Button(); 35 this.textBox2 = new System.Windows.Forms.TextBox(); 36 this.textBox1 = new System.Windows.Forms.TextBox(); 37 this.label2 = new System.Windows.Forms.Label(); 38 this.label1 = new System.Windows.Forms.Label(); 39 this.SuspendLayout(); 40 // 41 // richTextBox2 42 // 43 this.richTextBox2.Location = new System.Drawing.Point(32, 234); 44 this.richTextBox2.Name = "richTextBox2"; 45 this.richTextBox2.Size = new System.Drawing.Size(306, 51); 46 this.richTextBox2.TabIndex = 4; 47 this.richTextBox2.Text = ""; 48 // 49 // richTextBox1 50 // 51 this.richTextBox1.Location = new System.Drawing.Point(29, 65); 52 this.richTextBox1.Name = "richTextBox1"; 53 this.richTextBox1.Size = new System.Drawing.Size(385, 155); 54 this.richTextBox1.TabIndex = 3; 55 this.richTextBox1.Text = ""; 56 // 57 // button2 58 // 59 this.button2.Location = new System.Drawing.Point(344, 234); 60 this.button2.Name = "button2"; 61 this.button2.Size = new System.Drawing.Size(70, 51); 62 this.button2.TabIndex = 5; 63 this.button2.Text = "发送"; 64 this.button2.UseVisualStyleBackColor = true; 65 this.button2.Click += new System.EventHandler(this.button2_Click); 66 // 67 // button1 68 // 69 this.button1.Location = new System.Drawing.Point(320, 19); 70 this.button1.Name = "button1"; 71 this.button1.Size = new System.Drawing.Size(75, 23); 72 this.button1.TabIndex = 2; 73 this.button1.Text = "连接"; 74 this.button1.UseVisualStyleBackColor = true; 75 this.button1.Click += new System.EventHandler(this.button1_Click); 76 // 77 // textBox2 78 // 79 this.textBox2.Location = new System.Drawing.Point(214, 21); 80 this.textBox2.Name = "textBox2"; 81 this.textBox2.Size = new System.Drawing.Size(84, 21); 82 this.textBox2.TabIndex = 1; 83 // 84 // textBox1 85 // 86 this.textBox1.Location = new System.Drawing.Point(71, 21); 87 this.textBox1.Name = "textBox1"; 88 this.textBox1.Size = new System.Drawing.Size(84, 21); 89 this.textBox1.TabIndex = 0; 90 // 91 // label2 92 // 93 this.label2.AutoSize = true; 94 this.label2.Location = new System.Drawing.Point(179, 24); 95 this.label2.Name = "label2"; 96 this.label2.Size = new System.Drawing.Size(29, 12); 97 this.label2.TabIndex = 7; 98 this.label2.Text = "Port"; 99 // 100 // label1 101 // 102 this.label1.AutoSize = true; 103 this.label1.Location = new System.Drawing.Point(37, 24); 104 this.label1.Name = "label1"; 105 this.label1.Size = new System.Drawing.Size(17, 12); 106 this.label1.TabIndex = 6; 107 this.label1.Text = "IP"; 108 // 109 // FrmTcpClient_aync 110 // 111 this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F); 112 this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 113 this.ClientSize = new System.Drawing.Size(456, 311); 114 this.Controls.Add(this.richTextBox2); 115 this.Controls.Add(this.richTextBox1); 116 this.Controls.Add(this.button2); 117 this.Controls.Add(this.button1); 118 this.Controls.Add(this.textBox2); 119 this.Controls.Add(this.textBox1); 120 this.Controls.Add(this.label2); 121 this.Controls.Add(this.label1); 122 this.Name = "FrmTcpClient_aync"; 123 this.Text = "FrmTcpClient_aync"; 124 this.Load += new System.EventHandler(this.FrmTcpClient_aync_Load); 125 this.ResumeLayout(false); 126 this.PerformLayout(); 127 128 } 129 130 #endregion 131 132 private System.Windows.Forms.RichTextBox richTextBox2; 133 private System.Windows.Forms.RichTextBox richTextBox1; 134 private System.Windows.Forms.Button button2; 135 private System.Windows.Forms.Button button1; 136 private System.Windows.Forms.TextBox textBox2; 137 private System.Windows.Forms.TextBox textBox1; 138 private System.Windows.Forms.Label label2; 139 private System.Windows.Forms.Label label1; 140 } 141 }