C# 异步 TCP 服务器完整实现

简介:

TCP异步Socket模型

C#的TCP异步Socket模型是通过Begin-End模式实现的。例如提供 BeginConnect、BeginAccept、BeginSend 和 BeginReceive等。

IAsyncResult BeginAccept(AsyncCallback callback, object state);

AsyncCallback 回调在函数执行完毕后执行。state 对象被用于在执行函数和回调函数间传输信息。

复制代码
Socket socket = new Socket(
                  AddressFamily.InterNetwork, 
                  SocketType.Stream, 
                  ProtocolType.Tcp);
IPEndPoint iep = new IPEndPoint(IPAddress.Any, 8888);
socket.Bind(iep);
socket.Listen(5);
socket.BeginAccept (new AsyncCallback(CallbackAccept), socket);

private void CallbackAccept(IAsyncResult iar)
{
  Socket server = (Socket)iar.AsyncState;
  Socket client = server.EndAccept(iar);
}
复制代码

则在Accept一个TcpClient,需要维护TcpClient列表。

private List<TcpClientState> clients;

异步TCP服务器完整实现

复制代码
  1   /// <summary>
  2   /// 异步TCP服务器
  3   /// </summary>
  4   public class AsyncTcpServer : IDisposable
  5   {
  6     #region Fields
  7 
  8     private TcpListener listener;
  9     private List<TcpClientState> clients;
 10     private bool disposed = false;
 11 
 12     #endregion
 13 
 14     #region Ctors
 15 
 16     /// <summary>
 17     /// 异步TCP服务器
 18     /// </summary>
 19     /// <param name="listenPort">监听的端口</param>
 20     public AsyncTcpServer(int listenPort)
 21       : this(IPAddress.Any, listenPort)
 22     {
 23     }
 24 
 25     /// <summary>
 26     /// 异步TCP服务器
 27     /// </summary>
 28     /// <param name="localEP">监听的终结点</param>
 29     public AsyncTcpServer(IPEndPoint localEP)
 30       : this(localEP.Address, localEP.Port)
 31     {
 32     }
 33 
 34     /// <summary>
 35     /// 异步TCP服务器
 36     /// </summary>
 37     /// <param name="localIPAddress">监听的IP地址</param>
 38     /// <param name="listenPort">监听的端口</param>
 39     public AsyncTcpServer(IPAddress localIPAddress, int listenPort)
 40     {
 41       Address = localIPAddress;
 42       Port = listenPort;
 43       this.Encoding = Encoding.Default;
 44 
 45       clients = new List<TcpClientState>();
 46 
 47       listener = new TcpListener(Address, Port);
 48       listener.AllowNatTraversal(true);
 49     }
 50 
 51     #endregion
 52 
 53     #region Properties
 54 
 55     /// <summary>
 56     /// 服务器是否正在运行
 57     /// </summary>
 58     public bool IsRunning { get; private set; }
 59     /// <summary>
 60     /// 监听的IP地址
 61     /// </summary>
 62     public IPAddress Address { get; private set; }
 63     /// <summary>
 64     /// 监听的端口
 65     /// </summary>
 66     public int Port { get; private set; }
 67     /// <summary>
 68     /// 通信使用的编码
 69     /// </summary>
 70     public Encoding Encoding { get; set; }
 71 
 72     #endregion
 73 
 74     #region Server
 75 
 76     /// <summary>
 77     /// 启动服务器
 78     /// </summary>
 79     /// <returns>异步TCP服务器</returns>
 80     public AsyncTcpServer Start()
 81     {
 82       if (!IsRunning)
 83       {
 84         IsRunning = true;
 85         listener.Start();
 86         listener.BeginAcceptTcpClient(
 87           new AsyncCallback(HandleTcpClientAccepted), listener);
 88       }
 89       return this;
 90     }
 91 
 92     /// <summary>
 93     /// 启动服务器
 94     /// </summary>
 95     /// <param name="backlog">
 96     /// 服务器所允许的挂起连接序列的最大长度
 97     /// </param>
 98     /// <returns>异步TCP服务器</returns>
 99     public AsyncTcpServer Start(int backlog)
100     {
101       if (!IsRunning)
102       {
103         IsRunning = true;
104         listener.Start(backlog);
105         listener.BeginAcceptTcpClient(
106           new AsyncCallback(HandleTcpClientAccepted), listener);
107       }
108       return this;
109     }
110 
111     /// <summary>
112     /// 停止服务器
113     /// </summary>
114     /// <returns>异步TCP服务器</returns>
115     public AsyncTcpServer Stop()
116     {
117       if (IsRunning)
118       {
119         IsRunning = false;
120         listener.Stop();
121 
122         lock (this.clients)
123         {
124           for (int i = 0; i < this.clients.Count; i++)
125           {
126             this.clients[i].TcpClient.Client.Disconnect(false);
127           }
128           this.clients.Clear();
129         }
130 
131       }
132       return this;
133     }
134 
135     #endregion
136 
137     #region Receive
138 
139     private void HandleTcpClientAccepted(IAsyncResult ar)
140     {
141       if (IsRunning)
142       {
143         TcpListener tcpListener = (TcpListener)ar.AsyncState;
144 
145         TcpClient tcpClient = tcpListener.EndAcceptTcpClient(ar);
146         byte[] buffer = new byte[tcpClient.ReceiveBufferSize];
147 
148         TcpClientState internalClient 
149           = new TcpClientState(tcpClient, buffer);
150         lock (this.clients)
151         {
152           this.clients.Add(internalClient);
153           RaiseClientConnected(tcpClient);
154         }
155 
156         NetworkStream networkStream = internalClient.NetworkStream;
157         networkStream.BeginRead(
158           internalClient.Buffer, 
159           0, 
160           internalClient.Buffer.Length, 
161           HandleDatagramReceived, 
162           internalClient);
163 
164         tcpListener.BeginAcceptTcpClient(
165           new AsyncCallback(HandleTcpClientAccepted), ar.AsyncState);
166       }
167     }
168 
169     private void HandleDatagramReceived(IAsyncResult ar)
170     {
171       if (IsRunning)
172       {
173         TcpClientState internalClient = (TcpClientState)ar.AsyncState;
174         NetworkStream networkStream = internalClient.NetworkStream;
175 
176         int numberOfReadBytes = 0;
177         try
178         {
179           numberOfReadBytes = networkStream.EndRead(ar);
180         }
181         catch
182         {
183           numberOfReadBytes = 0;
184         }
185 
186         if (numberOfReadBytes == 0)
187         {
188           // connection has been closed
189           lock (this.clients)
190           {
191             this.clients.Remove(internalClient);
192             RaiseClientDisconnected(internalClient.TcpClient);
193             return;
194           }
195         }
196 
197         // received byte and trigger event notification
198         byte[] receivedBytes = new byte[numberOfReadBytes];
199         Buffer.BlockCopy(
200           internalClient.Buffer, 0, 
201           receivedBytes, 0, numberOfReadBytes);
202         RaiseDatagramReceived(internalClient.TcpClient, receivedBytes);
203         RaisePlaintextReceived(internalClient.TcpClient, receivedBytes);
204 
205         // continue listening for tcp datagram packets
206         networkStream.BeginRead(
207           internalClient.Buffer, 
208           0, 
209           internalClient.Buffer.Length, 
210           HandleDatagramReceived, 
211           internalClient);
212       }
213     }
214 
215     #endregion
216 
217     #region Events
218 
219     /// <summary>
220     /// 接收到数据报文事件
221     /// </summary>
222     public event EventHandler<TcpDatagramReceivedEventArgs<byte[]>> DatagramReceived;
223     /// <summary>
224     /// 接收到数据报文明文事件
225     /// </summary>
226     public event EventHandler<TcpDatagramReceivedEventArgs<string>> PlaintextReceived;
227 
228     private void RaiseDatagramReceived(TcpClient sender, byte[] datagram)
229     {
230       if (DatagramReceived != null)
231       {
232         DatagramReceived(this, new TcpDatagramReceivedEventArgs<byte[]>(sender, datagram));
233       }
234     }
235 
236     private void RaisePlaintextReceived(TcpClient sender, byte[] datagram)
237     {
238       if (PlaintextReceived != null)
239       {
240         PlaintextReceived(this, new TcpDatagramReceivedEventArgs<string>(
241           sender, this.Encoding.GetString(datagram, 0, datagram.Length)));
242       }
243     }
244 
245     /// <summary>
246     /// 与客户端的连接已建立事件
247     /// </summary>
248     public event EventHandler<TcpClientConnectedEventArgs> ClientConnected;
249     /// <summary>
250     /// 与客户端的连接已断开事件
251     /// </summary>
252     public event EventHandler<TcpClientDisconnectedEventArgs> ClientDisconnected;
253 
254     private void RaiseClientConnected(TcpClient tcpClient)
255     {
256       if (ClientConnected != null)
257       {
258         ClientConnected(this, new TcpClientConnectedEventArgs(tcpClient));
259       }
260     }
261 
262     private void RaiseClientDisconnected(TcpClient tcpClient)
263     {
264       if (ClientDisconnected != null)
265       {
266         ClientDisconnected(this, new TcpClientDisconnectedEventArgs(tcpClient));
267       }
268     }
269 
270     #endregion
271 
272     #region Send
273 
274     /// <summary>
275     /// 发送报文至指定的客户端
276     /// </summary>
277     /// <param name="tcpClient">客户端</param>
278     /// <param name="datagram">报文</param>
279     public void Send(TcpClient tcpClient, byte[] datagram)
280     {
281       if (!IsRunning)
282         throw new InvalidProgramException("This TCP server has not been started.");
283 
284       if (tcpClient == null)
285         throw new ArgumentNullException("tcpClient");
286 
287       if (datagram == null)
288         throw new ArgumentNullException("datagram");
289 
290       tcpClient.GetStream().BeginWrite(
291         datagram, 0, datagram.Length, HandleDatagramWritten, tcpClient);
292     }
293 
294     private void HandleDatagramWritten(IAsyncResult ar)
295     {
296       ((TcpClient)ar.AsyncState).GetStream().EndWrite(ar);
297     }
298 
299     /// <summary>
300     /// 发送报文至指定的客户端
301     /// </summary>
302     /// <param name="tcpClient">客户端</param>
303     /// <param name="datagram">报文</param>
304     public void Send(TcpClient tcpClient, string datagram)
305     {
306       Send(tcpClient, this.Encoding.GetBytes(datagram));
307     }
308 
309     /// <summary>
310     /// 发送报文至所有客户端
311     /// </summary>
312     /// <param name="datagram">报文</param>
313     public void SendAll(byte[] datagram)
314     {
315       if (!IsRunning)
316         throw new InvalidProgramException("This TCP server has not been started.");
317 
318       for (int i = 0; i < this.clients.Count; i++)
319       {
320         Send(this.clients[i].TcpClient, datagram);
321       }
322     }
323 
324     /// <summary>
325     /// 发送报文至所有客户端
326     /// </summary>
327     /// <param name="datagram">报文</param>
328     public void SendAll(string datagram)
329     {
330       if (!IsRunning)
331         throw new InvalidProgramException("This TCP server has not been started.");
332 
333       SendAll(this.Encoding.GetBytes(datagram));
334     }
335 
336     #endregion
337 
338     #region IDisposable Members
339 
340     /// <summary>
341     /// Performs application-defined tasks associated with freeing, 
342     /// releasing, or resetting unmanaged resources.
343     /// </summary>
344     public void Dispose()
345     {
346       Dispose(true);
347       GC.SuppressFinalize(this);
348     }
349 
350     /// <summary>
351     /// Releases unmanaged and - optionally - managed resources
352     /// </summary>
353     /// <param name="disposing"><c>true</c> to release 
354     /// both managed and unmanaged resources; <c>false</c> 
355     /// to release only unmanaged resources.</param>
356     protected virtual void Dispose(bool disposing)
357     {
358       if (!this.disposed)
359       {
360         if (disposing)
361         {
362           try
363           {
364             Stop();
365 
366             if (listener != null)
367             {
368               listener = null;
369             }
370           }
371           catch (SocketException ex)
372           {
373             ExceptionHandler.Handle(ex);
374           }
375         }
376 
377         disposed = true;
378       }
379     }
380 
381     #endregion
382   }
复制代码

使用举例

复制代码
 1   class Program
 2   {
 3     static AsyncTcpServer server;
 4 
 5     static void Main(string[] args)
 6     {
 7       LogFactory.Assign(new ConsoleLogFactory());
 8 
 9       server = new AsyncTcpServer(9999);
10       server.Encoding = Encoding.UTF8;
11       server.ClientConnected += 
12         new EventHandler<TcpClientConnectedEventArgs>(server_ClientConnected);
13       server.ClientDisconnected += 
14         new EventHandler<TcpClientDisconnectedEventArgs>(server_ClientDisconnected);
15       server.PlaintextReceived += 
16         new EventHandler<TcpDatagramReceivedEventArgs<string>>(server_PlaintextReceived);
17       server.Start();
18 
19       Console.WriteLine("TCP server has been started.");
20       Console.WriteLine("Type something to send to client...");
21       while (true)
22       {
23         string text = Console.ReadLine();
24         server.SendAll(text);
25       }
26     }
27 
28     static void server_ClientConnected(object sender, TcpClientConnectedEventArgs e)
29     {
30       Logger.Debug(string.Format(CultureInfo.InvariantCulture, 
31         "TCP client {0} has connected.", 
32         e.TcpClient.Client.RemoteEndPoint.ToString()));
33     }
34 
35     static void server_ClientDisconnected(object sender, TcpClientDisconnectedEventArgs e)
36     {
37       Logger.Debug(string.Format(CultureInfo.InvariantCulture, 
38         "TCP client {0} has disconnected.", 
39         e.TcpClient.Client.RemoteEndPoint.ToString()));
40     }
41 
42     static void server_PlaintextReceived(object sender, TcpDatagramReceivedEventArgs<string> e)
43     {
44       if (e.Datagram != "Received")
45       {
46         Console.Write(string.Format("Client : {0} --> ", 
47           e.TcpClient.Client.RemoteEndPoint.ToString()));
48         Console.WriteLine(string.Format("{0}", e.Datagram));
49         server.Send(e.TcpClient, "Server has received you text : " + e.Datagram);
50       }
51     }
52   }
复制代码








目录
相关文章
|
2月前
|
网络协议 安全 测试技术
手撕测试tcp服务器效率工具——以epoll和io_uring对比为例
手撕测试tcp服务器效率工具——以epoll和io_uring对比为例
40 2
|
25天前
|
安全 数据处理 C#
C# Post数据或文件到指定的服务器进行接收
C# Post数据或文件到指定的服务器进行接收
|
2月前
|
网络协议 Java API
【JavaEE初阶】 TCP服务器与客户端的搭建
【JavaEE初阶】 TCP服务器与客户端的搭建
|
2月前
|
前端开发 Java API
构建异步高并发服务器:Netty与Spring Boot的完美结合
构建异步高并发服务器:Netty与Spring Boot的完美结合
|
2月前
|
数据采集 网络协议 搜索推荐
网络编程【TCP单向通信、TCP双向通信、一对多应用、一对多聊天服务器】(二)-全面详解(学习总结---从入门到深化)
网络编程【TCP单向通信、TCP双向通信、一对多应用、一对多聊天服务器】(二)-全面详解(学习总结---从入门到深化)
52 0
|
3月前
|
网络协议 安全 Linux
腾讯三面:一台服务器,最大支持的TCP连接数是多少?
一个 TCP 对象占用的大小,等于它所包含的一些数据结构占用大小的总和,也是就把上面这些数据结构的大小累加起来,就是一个 TCP 连接占用的大小了。实际过程中的 TCP 连接,肯定不是静止状态的,还会进行发送数据和接收数据了,那么这些过程还是会额外消耗更多的内存资源的,8 GB 物理内存的服务器实际并发很难达到百万级别。
55 2
|
3月前
|
网络协议 安全 Go
TCP基础详解:Telnet回音服务器搭建
TCP基础详解:Telnet回音服务器搭建
30 0
|
1月前
|
C#
24. C# 编程:用户设定敌人初始血值的实现
24. C# 编程:用户设定敌人初始血值的实现
18 0
|
2月前
|
SQL 数据库连接 应用服务中间件
C#WinForm基础编程(三)
C#WinForm基础编程
71 0
|
2月前
C#WinForm基础编程(二)
C#WinForm基础编程
55 0