C#高性能TCP服务的多种实现方式
哎~~ 想想大部分园友应该对"高性能" 字样更感兴趣,为了吸引眼球所以标题中一定要突出,其实我更喜欢的标题是 《猴赛雷,C#编写TCP服务的花样姿势
哎~~ 想想大部分园友应该对 "高性能" 字样更感兴趣,为了吸引眼球所以标题中一定要突出,其实我更喜欢的标题是《猴赛雷,C#编写TCP服务的花样姿势!》。
本篇文章的主旨是使用 .NET/C# 实现 TCP 高性能服务的不同方式,包括但不限于如下内容:
- APM 方式,即 Asynchronous PRogramming Model
- TAP 方式,即 Task-based Asynchronous Pattern
- SAEA 方式,即 SocketAsyncEventArgs
- RIO 方式,即 Registered I/O
在 .NET/C# 中对于 Socket 的支持均是基于 Windows I/O Completion Ports 完成端口技术的封装,通过不同的 Non-Blocking 封装结构来满足不同的编程需求。以上方式均已在 Cowboy.Sockets 中有完整实现,并且 APM 和 TAP 方式已经在实际项目中应用。Cowboy.Sockets 还在不断的进化和完善中,如有任何问题请及时指正。
虽然有这么多种实现方式,但抽象的看,它们是一样一样的,用两个 Loop 即可描述:Accept Loop 和 Read Loop,如下图所示。(这里提及的 "Loop" 指的是一种循环方式,而非特指 while/for 等关键字。)
- 在任何 TCP Server 的实现中,一定存在一个 Accept Socket Loop,用于接收 Client 端的 Connect 请求以建立 TCP Connection。
- 在任何 TCP Server 的实现中,一定存在一个 Read Socket Loop,用于接收 Client 端 Write 过来的数据。
如果 Accept 循环阻塞,则会导致无法快速的建立连接,服务端 Pending Backlog 满,进而导致 Client 端收到 Connect Timeout 的异常。如果 Read 循环阻塞,则显然会导致无法及时收到 Client 端发过来的数据,进而导致 Client 端 Send Buffer 满,无法再发送数据。
从实现细节的角度看,能够导致服务阻塞的位置可能在:
- Accept 到新的 Socket,构建新的 Connection 需要分配各种资源,分配资源慢;
- Accept 到新的 Socket,没有及时触发下一次 Accept;
- Read 到新的 Buffer,判定 Payload 消息长度,判定过程长;
- Read 到新的 Buffer,发现 Payload 还没有收全,继续 Read,则可能会导致一次 Buffer Copy;
- Payload 接收完毕,进行 Serialization 转成可识别的 Protocol Message,序列化慢;
- 由 Business Module 来处理相应的 Protocol Message,处理过程慢;
1-2 涉及到 Accept 过程和 Connection 的建立过程,3-4 涉及到 ReceiveBuffer 的处理过程,5-6 涉及到应用逻辑侧的实现。
java 中著名的 Netty 网络库从 4.0 版本开始对于 Buffer 部分做了全新的尝试,采用了名叫 ByteBuf 的设计,实现 Buffer Zero Copy 以减少高并发条件下 Buffer 拷贝带来的性能损失和 GC 压力。DotNetty,Orleans ,Helios 等项目正在尝试在 C# 中进行类似的 ByteBuf 的实现。
APM 方式:TcpSocketServer
TcpSocketServer 的实现是基于 .NET Framework 自带的 TcpListener 和 TcpClient 的更进一步的封装,采用基于 APM 的 BeginXXX 和 EndXXX 接口实现。
TcpSocketServer 中的 Accept Loop 指的就是,
- BeginAccept -> EndAccept-> BeginAccept -> EndAccept -> BeginAccept -> ...
每一个建立成功的 Connection 由 TcpSocketsession 来处理,所以 TcpSocketSession 中会包含 Read Loop,
- BeginRead -> EndRead -> BeginRead -> EndRead -> BeginRead -> ...
TcpSocketServer 通过暴露 Event 来实现 Connection 的建立与断开和数据接收的通知。
<span style="color: #0000ff;">event</span> EventHandler<tcpclientconnectedeventargs><span style="color: #000000;"> ClientConnected; </span><span style="color: #0000ff;">event</span> EventHandler<tcpclientdisconnectedeventargs><span style="color: #000000;"> ClientDisconnected; </span><span style="color: #0000ff;">event</span> EventHandler<tcpclientdatareceivedeventargs> ClientDataReceived;</tcpclientdatareceivedeventargs></tcpclientdisconnectedeventargs></tcpclientconnectedeventargs>
使用也是简单直接,直接订阅事件通知。
<span style="color: #0000ff;">private</span> <span style="color: #0000ff;">static</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> StartServer() { _server </span>= <span style="color: #0000ff;">new</span> TcpSocketServer(<span style="color: #800080;">22222</span><span style="color: #000000;">); _server.ClientConnected </span>+=<span style="color: #000000;"> server_ClientConnected; _server.ClientDisconnected </span>+=<span style="color: #000000;"> server_ClientDisconnected; _server.ClientDataReceived </span>+=<span style="color: #000000;"> server_ClientDataReceived; _server.Listen(); } </span><span style="color: #0000ff;">static</span> <span style="color: #0000ff;">void</span> server_ClientConnected(<span style="color: #0000ff;">object</span><span style="color: #000000;"> sender, TcpClientConnectedEventArgs e) { Console.WriteLine(</span><span style="color: #0000ff;">string</span>.Format(<span style="color: #800000;">"</span><span style="color: #800000;">TCP client {0} has connected {1}.</span><span style="color: #800000;">"</span><span style="color: #000000;">, e.Session.RemoteEndPoint, e.Session)); } </span><span style="color: #0000ff;">static</span> <span style="color: #0000ff;">void</span> server_ClientDisconnected(<span style="color: #0000ff;">object</span><span style="color: #000000;"> sender, TcpClientDisconnectedEventArgs e) { Console.WriteLine(</span><span style="color: #0000ff;">string</span>.Format(<span style="color: #800000;">"</span><span style="color: #800000;">TCP client {0} has disconnected.</span><span style="color: #800000;">"</span><span style="color: #000000;">, e.Session)); } </span><span style="color: #0000ff;">static</span> <span style="color: #0000ff;">void</span> server_ClientDataReceived(<span style="color: #0000ff;">object</span><span style="color: #000000;"> sender, TcpClientDataReceivedEventArgs e) { </span><span style="color: #0000ff;">var</span> text =<span style="color: #000000;"> Encoding.UTF8.GetString(e.Data, e.DataOffset, e.DataLength); Console.Write(</span><span style="color: #0000ff;">string</span>.Format(<span style="color: #800000;">"</span><span style="color: #800000;">Client : {0} {1} --> </span><span style="color: #800000;">"</span><span style="color: #000000;">, e.Session.RemoteEndPoint, e.Session)); Console.WriteLine(</span><span style="color: #0000ff;">string</span>.Format(<span style="color: #800000;">"</span><span style="color: #800000;">{0}</span><span style="color: #800000;">"</span><span style="color: #000000;">, text)); _server.Broadcast(Encoding.UTF8.GetBytes(text)); }</span>
TAP 方式:AsyncTcpSocketServer
AsyncTcpSocketServer 的实现是基于 .NET Framework 自带的 TcpListener 和 TcpClient 的更进一步的封装,采用基于 TAP 的 async/await 的 XXXAsync 接口实现。
然而,实际上 XXXAsync 并没有创建什么神奇的效果,其内部实现只是将 APM 的方法转换成了 TAP 的调用方式。
<span style="color: #008000;">//</span><span style="color: #008000;">************* Task-based async public methods *************************</span> [HostProtection(ExternalThreading = <span style="color: #0000ff;">true</span><span style="color: #000000;">)] </span><span style="color: #0000ff;">public</span> Task<socket><span style="color: #000000;"> AcceptSocketAsync() { </span><span style="color: #0000ff;">return</span> Task<socket>.Factory.FromAsync(BeginAcceptSocket, EndAcceptSocket, <span style="color: #0000ff;">null</span><span style="color: #000000;">); } [HostProtection(ExternalThreading </span>= <span style="color: #0000ff;">true</span><span style="color: #000000;">)] </span><span style="color: #0000ff;">public</span> Task<tcpclient><span style="color: #000000;"> AcceptTcpClientAsync() { </span><span style="color: #0000ff;">return</span> Task<tcpclient>.Factory.FromAsync(BeginAcceptTcpClient, EndAcceptTcpClient, <span style="color: #0000ff;">null</span><span style="color: #000000;">); }</span></tcpclient></tcpclient></socket></socket>
AsyncTcpSocketServer 中的 Accept Loop 指的就是,
<span style="color: #0000ff;">while</span><span style="color: #000000;"> (IsListening) { </span><span style="color: #0000ff;">var</span> tcpClient = <span style="color: #0000ff;">await</span><span style="color: #000000;"> _listener.AcceptTcpClientAsync(); }</span>
每一个建立成功的 Connection 由 AsyncTcpSocketSession 来处理,所以 AsyncTcpSocketSession 中会包含 Read Loop,
<span style="color: #0000ff;">while</span> (State ==<span style="color: #000000;"> TcpSocketConnectionState.Connected) { </span><span style="color: #0000ff;">int</span> receiveCount = <span style="color: #0000ff;">await</span> _stream.ReadAsync(_receiveBuffer, <span style="color: #800080;">0</span><span style="color: #000000;">, _receiveBuffer.Length); }</span>
为了将 async/await 异步到底,AsyncTcpSocketServer 所暴露的接口也同样是 Awaitable 的。
<span style="color: #0000ff;">public</span> <span style="color: #0000ff;">interface</span><span style="color: #000000;"> IAsyncTcpSocketServerMessageDispatcher { Task OnSessionStarted(AsyncTcpSocketSession session); Task OnSessionDataReceived(AsyncTcpSocketSession session, </span><span style="color: #0000ff;">byte</span>[] data, <span style="color: #0000ff;">int</span> offset, <span style="color: #0000ff;">int</span><span style="color: #000000;"> count); Task OnSessionClosed(AsyncTcpSocketSession session); }</span>
使用时仅需将一个实现了该接口的对象注入到 AsyncTcpSocketServer 的构造函数中即可。
<span style="color: #0000ff;">public</span> <span style="color: #0000ff;">class</span><span style="color: #000000;"> SimpleMessageDispatcher : IAsyncTcpSocketServerMessageDispatcher { </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">async</span><span style="color: #000000;"> Task OnSessionStarted(AsyncTcpSocketSession session) { Console.WriteLine(</span><span style="color: #0000ff;">string</span>.Format(<span style="color: #800000;">"</span><span style="color: #800000;">TCP session {0} has connected {1}.</span><span style="color: #800000;">"</span><span style="color: #000000;">, session.RemoteEndPoint, session)); </span><span style="color: #0000ff;">await</span><span style="color: #000000;"> Task.CompletedTask; } </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">async</span> Task OnSessionDataReceived(AsyncTcpSocketSession session, <span style="color: #0000ff;">byte</span>[] data, <span style="color: #0000ff;">int</span> offset, <span style="color: #0000ff;">int</span><span style="color: #000000;"> count) { </span><span style="color: #0000ff;">var</span> text =<span style="color: #000000;"> Encoding.UTF8.GetString(data, offset, count); Console.Write(</span><span style="color: #0000ff;">string</span>.Format(<span style="color: #800000;">"</span><span style="color: #800000;">Client : {0} --> </span><span style="color: #800000;">"</span><span style="color: #000000;">, session.RemoteEndPoint)); Console.WriteLine(</span><span style="color: #0000ff;">string</span>.Format(<span style="color: #800000;">"</span><span style="color: #800000;">{0}</span><span style="color: #800000;">"</span><span style="color: #000000;">, text)); </span><span style="color: #0000ff;">await</span><span style="color: #000000;"> session.SendAsync(Encoding.UTF8.GetBytes(text)); } </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">async</span><span style="color: #000000;"> Task OnSessionClosed(AsyncTcpSocketSession session) { Console.WriteLine(</span><span style="color: #0000ff;">string</span>.Format(<span style="color: #800000;">"</span><span style="color: #800000;">TCP session {0} has disconnected.</span><span style="color: #800000;">"</span><span style="color: #000000;">, session)); </span><span style="color: #0000ff;">await</span><span style="color: #000000;"> Task.CompletedTask; } }</span>
当然,对于接口的实现也不是强制了,也可以在构造函数中直接注入方法的实现。
<span style="color: #0000ff;">public</span><span style="color: #000000;"> AsyncTcpSocketServer( ipEndPoint listenedEndPoint, Func</span><asynctcpsocketsession style="color: #0000ff;">byte[], <span style="color: #0000ff;">int</span>, <span style="color: #0000ff;">int</span>, Task> onSessionDataReceived = <span style="color: #0000ff;">null</span><span style="color: #000000;">, Func</span><asynctcpsocketsession task> onSessionStarted = <span style="color: #0000ff;">null</span><span style="color: #000000;">, Func</span><asynctcpsocketsession task> onSessionClosed = <span style="color: #0000ff;">null</span><span style="color: #000000;">, AsyncTcpSocketServerConfiguration configuration </span>= <span style="color: #0000ff;">null</span><span style="color: #000000;">) {}</span></asynctcpsocketsession></asynctcpsocketsession></asynctcpsocketsession>
SAEA 方式:TcpSocketSaeaServer
SAEA 是 SocketAsyncEventArgs 的简写。SocketAsyncEventArgs 是 .NET Framework 3.5 开始支持的一种支持高性能 Socket 通信的实现。SocketAsyncEventArgs 相比于 APM 方式的主要优点可以描述如下:
The main feature of these enhancements is the avoidance of the repeated allocation and synchronization of objects during high-volume asynchronous socket I/O. The Begin/End design pattern currently implemented by the Socket class for asynchronous socket I/O requires a System.IAsyncResult object be allocated for each asynchronous socket Operation.
也就是说,优点就是无需为每次调用都生成 IAsyncResult 等对象,向原生 Socket 更靠近一些。
使用 SocketAsyncEventArgs 的推荐步骤如下:
- Allocate a new SocketAsyncEventArgs context object, or get a free one from an application pool.
- Set properties on the context object to the operation about to be performed (the callback delegate method and data buffer, for example).
- Call the appropriate socket method (xxxAsync) to initiate the asynchronous operation.
- If the asynchronous socket method (xxxAsync) returns true in the callback, query the context properties for completion status.
- If the asynchronous socket method (xxxAsync) returns false in the callback, the operation completed synchronously. The context properties may be queried for the operation result.
- Reuse the context for another operation, put it back in the pool, or discard it.
重点在于池化(Pooling),池化的目的就是为了重用和减少运行时分配和垃圾回收的压力。
TcpSocketSaeaServer 即是对 SocketAsyncEventArgs 的应用和封装,并实现了 Pooling 技术。TcpSocketSaeaServer 中的重点是 SaeaAwaitable 类,SaeaAwaitable 中内置了一个 SocketAsyncEventArgs,并通过 GetAwaiter 返回 SaeaAwaiter 来支持 async/await 操作。同时,通过 SaeaExtensions 扩展方法对来扩展 SocketAsyncEventArgs 的 Awaitable 实现。
<span style="color: #0000ff;">public</span> <span style="color: #0000ff;">static</span> SaeaAwaitable AcceptAsync(<span style="color: #0000ff;">this</span><span style="color: #000000;"> Socket socket, SaeaAwaitable awaitable) </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">static</span> SaeaAwaitable ConnectAsync(<span style="color: #0000ff;">this</span><span style="color: #000000;"> Socket socket, SaeaAwaitable awaitable) </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">static</span> SaeaAwaitable DisonnectAsync(<span style="color: #0000ff;">this</span><span style="color: #000000;"> Socket socket, SaeaAwaitable awaitable) </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">static</span> SaeaAwaitable ReceiveAsync(<span style="color: #0000ff;">this</span><span style="color: #000000;"> Socket socket, SaeaAwaitable awaitable) </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">static</span> SaeaAwaitable SendAsync(<span style="color: #0000ff;">this</span> Socket socket, SaeaAwaitable awaitable)
SaeaPool 则是一个 QueuedObjectPool
TcpSocketSaeaServer 中的 Accept Loop 指的就是,
<span style="color: #0000ff;">while</span><span style="color: #000000;"> (IsListening) { </span><span style="color: #0000ff;">var</span> saea =<span style="color: #000000;"> _acceptSaeaPool.Take(); </span><span style="color: #0000ff;">var</span> socketError = <span style="color: #0000ff;">await</span><span style="color: #000000;"> _listener.AcceptAsync(saea); </span><span style="color: #0000ff;">if</span> (socketError ==<span style="color: #000000;"> SocketError.Success) { </span><span style="color: #0000ff;">var</span> acceptedSocket =<span style="color: #000000;"> saea.Saea.AcceptSocket; } _acceptSaeaPool.Return(saea); }</span>
每一个建立成功的 Connection 由 TcpSocketSaeaSession 来处理,所以 TcpSocketSaeaSession 中会包含 Read Loop,
<span style="color: #0000ff;">var</span> saea =<span style="color: #000000;"> _saeaPool.Take(); saea.Saea.SetBuffer(_receiveBuffer, </span><span style="color: #800080;">0</span><span style="color: #000000;">, _receiveBuffer.Length); </span><span style="color: #0000ff;">while</span> (State ==<span style="color: #000000;"> TcpSocketConnectionState.Connected) { saea.Saea.SetBuffer(</span><span style="color: #800080;">0</span><span style="color: #000000;">, _receiveBuffer.Length); </span><span style="color: #0000ff;">var</span> socketError = <span style="color: #0000ff;">await</span><span style="color: #000000;"> _socket.ReceiveAsync(saea); </span><span style="color: #0000ff;">if</span> (socketError !=<span style="color: #000000;"> SocketError.Success) </span><span style="color: #0000ff;">break</span><span style="color: #000000;">; </span><span style="color: #0000ff;">var</span> receiveCount =<span style="color: #000000;"> saea.Saea.BytesTransferred; </span><span style="color: #0000ff;">if</span> (receiveCount == <span style="color: #800080;">0</span><span style="color: #000000;">) </span><span style="color: #0000ff;">break</span><span style="color: #000000;">; }</span>
同样,TcpSocketSaeaServer 对外所暴露的接口也同样是 Awaitable 的。
<span style="color: #0000ff;">public</span> <span style="color: #0000ff;">interface</span><span style="color: #000000;"> ITcpSocketSaeaServerMessageDispatcher { Task OnSessionStarted(TcpSocketSaeaSession session); Task OnSessionDataReceived(TcpSocketSaeaSession session, </span><span style="color: #0000ff;">byte</span>[] data, <span style="color: #0000ff;">int</span> offset, <span style="color: #0000ff;">int</span><span style="color: #000000;"> count); Task OnSessionClosed(TcpSocketSaeaSession session); }</span>
使用起来也是简单直接:
<span style="color: #0000ff;">public</span> <span style="color: #0000ff;">class</span><span style="color: #000000;"> SimpleMessageDispatcher : ITcpSocketSaeaServerMessageDispatcher { </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">async</span><span style="color: #000000;"> Task OnSessionStarted(TcpSocketSaeaSession session) { Console.WriteLine(</span><span style="color: #0000ff;">string</span>.Format(<span style="color: #800000;">"</span><span style="color: #800000;">TCP session {0} has connected {1}.</span><span style="color: #800000;">"</span><span style="color: #000000;">, session.RemoteEndPoint, session)); </span><span style="color: #0000ff;">await</span><span style="color: #000000;"> Task.CompletedTask; } </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">async</span> Task OnSessionDataReceived(TcpSocketSaeaSession session, <span style="color: #0000ff;">byte</span>[] data, <span style="color: #0000ff;">int</span> offset, <span style="color: #0000ff;">int</span><span style="color: #000000;"> count) { </span><span style="color: #0000ff;">var</span> text =<span style="color: #000000;"> Encoding.UTF8.GetString(data, offset, count); Console.Write(</span><span style="color: #0000ff;">string</span>.Format(<span style="color: #800000;">"</span><span style="color: #800000;">Client : {0} --> </span><span style="color: #800000;">"</span><span style="color: #000000;">, session.RemoteEndPoint)); Console.WriteLine(</span><span style="color: #0000ff;">string</span>.Format(<span style="color: #800000;">"</span><span style="color: #800000;">{0}</span><span style="color: #800000;">"</span><span style="color: #000000;">, text)); </span><span style="color: #0000ff;">await</span><span style="color: #000000;"> session.SendAsync(Encoding.UTF8.GetBytes(text)); } </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">async</span><span style="color: #000000;"> Task OnSessionClosed(TcpSocketSaeaSession session) { Console.WriteLine(</span><span style="color: #0000ff;">string</span>.Format(<span style="color: #800000;">"</span><span style="color: #800000;">TCP session {0} has disconnected.</span><span style="color: #800000;">"</span><span style="color: #000000;">, session)); </span><span style="color: #0000ff;">await</span><span style="color: #000000;"> Task.CompletedTask; } }</span>
RIO 方式:TcpSocketRioServer
从 Windows 8.1 / Windows Server 2012 R2 开始,微软推出了 Registered I/O Networking Extensions 来支持高性能 Socket 服务的实现,简称 RIO。
The following functions are supported for Windows Store apps on Windows 8.1, Windows Server 2012 R2, and later. Microsoft Visual Studio 2013 Update 3 or later is required for Windows Store apps.
- RIOCloseCompletionQueue
- RIOCreateCompletionQueue
- RIOCreateRequestQueue
- RIODequeueCompletion
- RIODeregisterBuffer
- RIONotify
- RIOReceive
- RIOReceiveEx
- RIORegisterBuffer
- RIOResizeCompletionQueue
- RIOResizeRequestQueue
- RIOSend
- RIOSendEx
到目前为止,.NET Framework 还没有推出对 RIO 的支持,所以若想在 C# 中实现 RIO 则只能通过 P/Invoke 方式,RioSharp 是开源项目中的一个比较完整的实现。
Cowboy.Sockets 直接引用了 RioSharp 的源代码,放置在 Cowboy.Sockets.Experimental 名空间下,以供实验和测试使用。
同样,通过 TcpSocketRioServer 来实现 Accept Loop,
_listener.OnAccepted = (acceptedSocket) =><span style="color: #000000;"> { Task.Run(</span><span style="color: #0000ff;">async</span> () =><span style="color: #000000;"> { </span><span style="color: #0000ff;">await</span><span style="color: #000000;"> Process(acceptedSocket); }) .Forget(); };</span>
通过 TcpSocketRioSession 来处理 Read Loop,
<span style="color: #0000ff;">while</span> (State ==<span style="color: #000000;"> TcpSocketConnectionState.Connected) { </span><span style="color: #0000ff;">int</span> receiveCount = <span style="color: #0000ff;">await</span> _stream.ReadAsync(_receiveBuffer, <span style="color: #800080;">0</span><span style="color: #000000;">, _receiveBuffer.Length); </span><span style="color: #0000ff;">if</span> (receiveCount == <span style="color: #800080;">0</span><span style="color: #000000;">) </span><span style="color: #0000ff;">break</span><span style="color: #000000;">; }</span>
测试代码一如既往的类似:
<span style="color: #0000ff;">public</span> <span style="color: #0000ff;">class</span><span style="color: #000000;"> SimpleMessageDispatcher : ITcpSocketRioServerMessageDispatcher { </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">async</span><span style="color: #000000;"> Task OnSessionStarted(TcpSocketRioSession session) { </span><span style="color: #008000;">//</span><span style="color: #008000;">Console.WriteLine(string.Format("TCP session {0} has connected {1}.", session.RemoteEndPoint, session));</span> Console.WriteLine(<span style="color: #0000ff;">string</span>.Format(<span style="color: #800000;">"</span><span style="color: #800000;">TCP session has connected {0}.</span><span style="color: #800000;">"</span><span style="color: #000000;">, session)); </span><span style="color: #0000ff;">await</span><span style="color: #000000;"> Task.CompletedTask; } </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">async</span> Task OnSessionDataReceived(TcpSocketRioSession session, <span style="color: #0000ff;">byte</span>[] data, <span style="color: #0000ff;">int</span> offset, <span style="color: #0000ff;">int</span><span style="color: #000000;"> count) { </span><span style="color: #0000ff;">var</span> text =<span style="color: #000000;"> Encoding.UTF8.GetString(data, offset, count); </span><span style="color: #008000;">//</span><span style="color: #008000;">Console.Write(string.Format("Client : {0} --> ", session.RemoteEndPoint));</span> Console.Write(<span style="color: #0000ff;">string</span>.Format(<span style="color: #800000;">"</span><span style="color: #800000;">Client : --> </span><span style="color: #800000;">"</span><span style="color: #000000;">)); Console.WriteLine(</span><span style="color: #0000ff;">string</span>.Format(<span style="color: #800000;">"</span><span style="color: #800000;">{0}</span><span style="color: #800000;">"</span><span style="color: #000000;">, text)); </span><span style="color: #0000ff;">await</span><span style="color: #000000;"> session.SendAsync(Encoding.UTF8.GetBytes(text)); } </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">async</span><span style="color: #000000;"> Task OnSessionClosed(TcpSocketRioSession session) { Console.WriteLine(</span><span style="color: #0000ff;">string</span>.Format(<span style="color: #800000;">"</span><span style="color: #800000;">TCP session {0} has disconnected.</span><span style="color: #800000;">"</span><span style="color: #000000;">, session)); </span><span style="color: #0000ff;">await</span><span style="color: #000000;"> Task.CompletedTask; } }</span>
参考资料
- Asynchronous Programming Model (APM)
- Task-based Asynchronous Pattern (TAP)
- Event-based Asynchronous Pattern (EAP)
- SocketAsyncEventArgs
- Registered I/O
- Netty: Reference counted objects
- Socket Performance Enhancements in Version 3.5
- What's New for Windows Sockets for Windows 8.1 and Windows Server 2012 R2
- RIO_EXTENSION_FUNCTION_TABLE structure
- Windows 8 Registered I/O Networking Extensions
本篇文章《C#高性能TCP服务的多种实现方式》由 Dennis Gao 发表自博客园个人博客,未经作者本人同意禁止以任何的形式转载,任何自动的或人为的爬虫转载行为均为耍流氓。

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

華為手機如何實現雙微信登入?隨著社群媒體的興起,微信已成為人們日常生活中不可或缺的溝通工具之一。然而,許多人可能會遇到一個問題:在同一部手機上同時登入多個微信帳號。對於華為手機用戶來說,實現雙微信登入並不困難,本文將介紹華為手機如何實現雙微信登入的方法。首先,華為手機自帶的EMUI系統提供了一個很方便的功能-應用程式雙開。透過應用程式雙開功能,用戶可以在手機上同

win10如何重置tcp/ip協定?其實方法很簡單的,使用者可以直接的進入到命令提示符,然後按下ctrl shift enter的組合鍵來進行操作就可以了或者是直接的執行重置命令來進行設置,下面就讓本站來為使用者來仔細的介紹一下windows10重置tcp/ip協定棧的方法吧。 windows10重置tcp/ip協定棧的方法一、管理員權限1、我們使用快捷鍵win R直接開啟運行窗口,然後輸入cmd並按住ctrl shift enter的組合鍵。 2、或者我們可以直接在開始選單中搜尋命令提示符,右鍵點

程式語言PHP是一種用於Web開發的強大工具,能夠支援多種不同的程式設計邏輯和演算法。其中,實作斐波那契數列是一個常見且經典的程式設計問題。在這篇文章中,將介紹如何使用PHP程式語言來實作斐波那契數列的方法,並附上具體的程式碼範例。斐波那契數列是一個數學上的序列,其定義如下:數列的第一個和第二個元素為1,從第三個元素開始,每個元素的值等於前兩個元素的和。數列的前幾元

Linux重啟服務的正確方式是什麼?在使用Linux系統時,經常會遇到需要重新啟動某個服務的情況,但是有時我們可能會在重新啟動服務時遇到一些問題,例如服務沒有真正停止或啟動等情況。因此,掌握正確的重啟服務的方式是非常重要的。在Linux中,通常可以使用systemctl指令來管理系統服務。 systemctl指令是systemd系統管理員的一部分

如何在華為手機上實現微信分身功能隨著社群軟體的普及和人們對隱私安全的日益重視,微信分身功能逐漸成為人們關注的焦點。微信分身功能可以幫助使用者在同一台手機上同時登入多個微信帳號,方便管理和使用。在華為手機上實現微信分身功能並不困難,只需要按照以下步驟操作即可。第一步:確保手機系統版本和微信版本符合要求首先,確保你的華為手機系統版本已更新至最新版本,以及微信App

在現今的軟體開發領域中,Golang(Go語言)作為一種高效、簡潔、並發性強的程式語言,越來越受到開發者的青睞。其豐富的標準庫和高效的並發特性使它成為遊戲開發領域的一個備受關注的選擇。本文將探討如何利用Golang來實現遊戲開發,並透過具體的程式碼範例來展示其強大的可能性。 1.Golang在遊戲開發中的優勢作為靜態類型語言,Golang正在建構大型遊戲系統

PHP遊戲需求實現指南隨著網路的普及和發展,網頁遊戲的市場也越來越火爆。許多開發者希望利用PHP語言來開發自己的網頁遊戲,而實現遊戲需求是其中一個關鍵步驟。本文將介紹如何利用PHP語言來實現常見的遊戲需求,並提供具體的程式碼範例。 1.創造遊戲角色在網頁遊戲中,遊戲角色是非常重要的元素。我們需要定義遊戲角色的屬性,例如姓名、等級、經驗值等,並提供方法來操作這些

標題:解決Ubuntu下PHP服務無法正常啟動的方法及具體程式碼範例在使用Ubuntu建立網站或應用程式時,經常會遇到PHP服務無法正常啟動的問題,這會導致網站無法正常存取或應用程式無法正常運行。本文將介紹如何解決Ubuntu下PHP服務無法正常啟動的問題,同時提供具體的程式碼範例幫助讀者快速解決此類故障。一、檢查PHP設定檔首先,我們需要檢查PHP的設定文件
