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   }
复制代码
目录
相关文章
|
3月前
|
域名解析 网络协议 数据库
TCP/IP服务器
【10月更文挑战第20天】TCP/IP服务器
143 65
|
1月前
|
缓存 网络协议 Java
【JavaEE】——TCP回显服务器(万字长文超详细)
ServerSocket类,Socket类,PrintWriter缓冲区问题,Socket文件释放问题,多线程问题
|
2月前
|
设计模式 API C#
C# 一分钟浅谈:GraphQL 服务器端实现
本文通过C#语言从零开始构建一个简单的GraphQL服务器端实现,介绍了环境准备、项目创建、定义Schema、配置GraphQL等步骤。同时,探讨了常见问题如数据源问题、类型定义不一致、性能问题和权限控制,提供了相应的解决方法。希望帮助读者更好地理解和应用GraphQL。
48 3
|
2月前
|
C# UED SEO
C# 异步方法async / await任务超时处理
通过使用 `Task.WhenAny`和 `Task.Delay`方法,您可以在C#中有效地实现异步任务的超时处理机制。这种方法允许您在指定时间内等待任务完成,并在任务超时时采取适当的措施,如抛出异常或执行备用操作。希望本文提供的详细解释和代码示例能帮助您在实际项目中更好地处理异步任务超时问题,提升应用程序的可靠性和用户体验。
106 3
|
3月前
|
网络协议 网络性能优化 C#
C# 一分钟浅谈:UDP 与 TCP 协议区别
【10月更文挑战第8天】在网络编程中,传输层协议的选择对应用程序的性能和可靠性至关重要。本文介绍了 TCP 和 UDP 两种常用协议的基础概念、区别及应用场景,并通过 C# 代码示例详细说明了如何处理常见的问题和易错点。TCP 适用于需要可靠传输和顺序保证的场景,而 UDP 适用于对延迟敏感且可以容忍一定数据丢失的实时应用。
70 1
|
3月前
|
网络协议 Java API
【网络】TCP回显服务器和客户端的构造,以及相关bug解决方法
【网络】TCP回显服务器和客户端的构造,以及相关bug解决方法
84 2
|
3月前
|
存储 网络协议 Java
【网络】UDP和TCP之间的差别和回显服务器
【网络】UDP和TCP之间的差别和回显服务器
94 1
|
3月前
|
网络协议 Python
Python创建一个TCP服务器
Python创建一个TCP服务器
34 0
|
15天前
|
弹性计算 数据挖掘 应用服务中间件
阿里云轻量应用服务器68元与云服务器99元和199元区别及选择参考
目前阿里云有三款特惠云服务器,第一款轻量云服务器2核2G68元一年,第二款经济型云服务器2核2G3M带宽99元1年,第三款通用算力型2核4G5M带宽199元一年。有的新手用户并不是很清楚他们之间的区别,因此不知道如何选择。本文来介绍一下它们之间的区别以及选择参考。
294 87
|
8天前
|
存储 弹性计算 应用服务中间件
阿里云轻量应用服务器出新品通用型实例了,全球26个地域可选
近日,阿里云再度发力,推出了首款全新升级的轻量应用服务器——通用型实例。这款服务器实例不仅标配了200Mbps峰值公网带宽,更在计算、存储、网络等基础资源上进行了全面优化,旨在为中小企业和开发者提供更加轻量、易用、普惠的云计算服务,满足其对于通用计算小算力的迫切需求。目前,这款新品已在全球26个地域正式上线,为全球用户提供了更加便捷、高效的上云选择。

热门文章

最新文章