搜尋
首頁後端開發C#.Net教程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 LoopRead Loop ,如下圖所示。 (這裡提及的"Loop" 指的是一種循環方式,而非特指 while/for 等關鍵字。以建立TCP Connection。

在任何 TCP Server 的實作中,一定存在一個 Read Socket Loop,用來接收 Client 端 Write 過來的資料。
  • 如果Accept 循環阻塞,則會導致無法快速的建立連接,服務端P
  • end
  • ing Backlog 滿,進而導致Client 端收到Connect

    Time

    out 的異常。如果 Read 迴圈阻塞,顯然會導致無法及時收到 Client 端發過來的數據,進而導致 Client 端 Send Buffer 滿,無法再傳送資料。
從實作細節的角度來看,能夠導致服務阻塞的位置可能在:

#Accept 到新的 Socket,建構新的Connection 需要分配各種資源,分配資源慢;

Accept 到新的 Socket,沒有及時觸發下一次Accept;
  1. Read 到新的 Buffer,判定 Payload 訊息長度,判定過程長;
  2. Read 到新的 Buffer,發現Payload 還沒有收全,繼續Read,則"可能" 會導致一次Buffer
  3. Copy
  4. Payload 接收完畢,進行De-Serialization 轉成可辨識的Protocol Message,反序列化慢;

    ##由Business

    Module
  5. 來處理對應的Protocol Message,處理過程慢;
  6. 1-2 涉及Accept 過程和Connection 的建立過程,3-4 涉及ReceiveBuffer 的處理過程, 5-6 涉及到應用邏輯側的實作。
  7. Java 中著名的Netty 網路庫從4.0 版本開始對Buffer 部分做了全新的嘗試,採用了名叫ByteBuf 的設計,實現Buffer Zero Copy 以減少高並發條件下 Buffer 拷貝帶來的效能損失和GC 壓力。 DotNetty,Orleans ,Helios 等專案正在嘗試在 C# 中進行類似的 ByteBuf 的實作。 APM 方法:TcpSocketServer

  8. TcpSocketServer 的實作是基於.NET Framework 自帶的 TcpListener 和TcpClient 的更進一步的封裝,採用基於APM 的BeginXXX 和EndXXX
介面

##li早期字##實現。 TcpSocketServer 中的Accept Loop 指的就是,

    BeginAccept -> EndAccept-> BeginAccept -> EndAccept -> BeginginAccept -> ...
  • 每一個建立成功的Connection 由TcpSocket
Session

來處理,所以TcpSocketSession 會包含Read Loop,

TcpSocketServer 透過揭露Event 來實現Connection 的建立與斷開和資料接收的通知。


  event EventHandler<tcpclientconnectedeventargs> ClientConnected;  event EventHandler<tcpclientdisconnectedeventargs> ClientDisconnected;  event EventHandler<tcpclientdatareceivedeventargs> ClientDataReceived;</tcpclientdatareceivedeventargs></tcpclientdisconnectedeventargs></tcpclientconnectedeventargs>

使用也是簡單直接,直接訂閱事件通知。


  private static void StartServer()
  {
      _server = new TcpSocketServer(22222);
      _server.ClientConnected += server_ClientConnected;
      _server.ClientDisconnected += server_ClientDisconnected;
      _server.ClientDataReceived += server_ClientDataReceived;
      _server.Listen();
  }  
  static void server_ClientConnected(object sender, TcpClientConnectedEventArgs e)
  {
      Console.WriteLine(string.Format("TCP client {0} has connected {1}.", e.Session.RemoteEndPoint, e.Session));
  }  
  static void server_ClientDisconnected(object sender, TcpClientDisconnectedEventArgs e)
  {
      Console.WriteLine(string.Format("TCP client {0} has disconnected.", e.Session));
  }  
  static void server_ClientDataReceived(object sender, TcpClientDataReceivedEventArgs e)
  {      var text = Encoding.UTF8.GetString(e.Data, e.DataOffset, e.DataLength);
      Console.Write(string.Format("Client : {0} {1} --> ", e.Session.RemoteEndPoint, e.Session));
      Console.WriteLine(string.Format("{0}", text));
      _server.Broadcast(Encoding.UTF8.GetBytes(text));
  }

TAP 方法:AsyncTcpSocketServer

AsyncTcpSocketServer 的實作是基於.NET Framework 內建的 TcpListener 和 TcpClient 的更進一步的封裝,採用基於TAP 的async/await 的XXXAsync 介面實作。

然而,實際上 XXXAsync 並沒有創造出什麼神奇的效果,其內部實作只是將 APM 的方法轉換成了 TAP 的呼叫方式。


  //************* Task-based async public methods *************************
  [HostProtection(ExternalThreading = true)]  public Task<socket> AcceptSocketAsync()
  {      return Task<socket>.Factory.FromAsync(BeginAcceptSocket, EndAcceptSocket, null);
  }
  
  [HostProtection(ExternalThreading = true)]  public Task<tcpclient> AcceptTcpClientAsync()
  {      return Task<tcpclient>.Factory.FromAsync(BeginAcceptTcpClient, EndAcceptTcpClient, null);
  }</tcpclient></tcpclient></socket></socket>

AsyncTcpSocketServer 中的Accept Loop 指的就是,


  while (IsListening)
  {      var tcpClient = await _listener.AcceptTcpClientAsync();
  }

每一個建立成功的Connection由AsyncTcpSocketSession# 來處理,所以AsyncTcpSocketSession 中會包含Read Loop,


  while (State == TcpSocketConnectionState.Connected)
  {      int receiveCount = await _stream.ReadAsync(_receiveBuffer, 0, _receiveBuffer.Length);
  }

為了將 async/await 非同步到底,AsyncTcpSocketServer 所暴露的介面也同樣是Awaitable 的。


  public interface IAsyncTcpSocketServerMessageDispatcher
  {
      Task OnSessionStarted(AsyncTcpSocketSession session);
      Task OnSessionDataReceived(AsyncTcpSocketSession session, byte[] data, int offset, int count);
      Task OnSessionClosed(AsyncTcpSocketSession session);
  }

使用時只需將一個實作了該介面的物件注入到 AsyncTcpSocketServer 的建構子中即可。


  public class SimpleMessageDispatcher : IAsyncTcpSocketServerMessageDispatcher
  {      public async Task OnSessionStarted(AsyncTcpSocketSession session)
      {
          Console.WriteLine(string.Format("TCP session {0} has connected {1}.", session.RemoteEndPoint, session));          await Task.CompletedTask;
      }  
      public async Task OnSessionDataReceived(AsyncTcpSocketSession session, byte[] data, int offset, int count)
      {          var text = Encoding.UTF8.GetString(data, offset, count);
          Console.Write(string.Format("Client : {0} --> ", session.RemoteEndPoint));
          Console.WriteLine(string.Format("{0}", text));  
          await session.SendAsync(Encoding.UTF8.GetBytes(text));
      }  
      public async Task OnSessionClosed(AsyncTcpSocketSession session)
      {
          Console.WriteLine(string.Format("TCP session {0} has disconnected.", session));          await Task.CompletedTask;
      }
  }

當然,對於介面的實作也不是強制了,也可以在建構函式中直接注入方法的實作。


  public AsyncTcpSocketServer(
      IPEndPoint listenedEndPoint,
      Func<asynctcpsocketsession> onSessionDataReceived = null,
      Func<asynctcpsocketsession> onSessionStarted = null,
      Func<asynctcpsocketsession> onSessionClosed = null,
      AsyncTcpSocketServerConfiguration configuration = null)
  {}</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 all ocation and synchronization of objects during high-volume asynchronous socket I/O. The Begin/End design pattern currently implemented by the Socket class for asynchronous socketly implemented by the Socket class for asynchronous socket I/## s a System.IAsyncResult object be allocated for each asynchronous socket operation.

也就是說,優點就是無需為每次調用生成 IAsyncResult 等對象,向原生Socket 更靠近一些。

使用SocketAsyncEventArgs 的推薦步驟如下:

  1. Allocate a new SocketAsyncEventArgs context object, or get a free one from an application pool.

  2. Set properties on the context object to the operation about to be performed (the callback delegate method and data buffer, for example).

  3. Call the appropriate socket method (

  4. ##Call the appropriate socket method (xxxAsync) to initiate the asynchronous operation.
  5. If
  6. the asynchronous socket method (xxxAsync) returns true in the callback, query the context properties for completion status.
  7. 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.
  8. 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 實作。

  public static SaeaAwaitable AcceptAsync(this Socket socket, SaeaAwaitable awaitable)  public static SaeaAwaitable ConnectAsync(this Socket socket, SaeaAwaitable awaitable)  public static SaeaAwaitable DisonnectAsync(this Socket socket, SaeaAwaitable awaitable)  public static SaeaAwaitable ReceiveAsync(this Socket socket, SaeaAwaitable awaitable)  public static SaeaAwaitable SendAsync(this Socket socket, SaeaAwaitable awaitable)
###SaeaPool 則是一個 QueuedObjectPool 的衍生實現,用於池化 SaeaAwaitable 實例。同時,為了減少 TcpSocketSaeaSession 的建置流程,也實作了 SessionPool 即 QueuedObjectPool。 ###

TcpSocketSaeaServer 中的 Accept Loop 指的就是,


  while (IsListening)
  {      var saea = _acceptSaeaPool.Take();  
      var socketError = await _listener.AcceptAsync(saea);      if (socketError == SocketError.Success)
      {          var acceptedSocket = saea.Saea.AcceptSocket;
      }
  
      _acceptSaeaPool.Return(saea);
  }

每一个建立成功的 Connection 由 TcpSocketSaeaSession 来处理,所以 TcpSocketSaeaSession 中会包含 Read Loop,


  var saea = _saeaPool.Take();
  saea.Saea.SetBuffer(_receiveBuffer, 0, _receiveBuffer.Length);  
  while (State == TcpSocketConnectionState.Connected)
  {
      saea.Saea.SetBuffer(0, _receiveBuffer.Length);  
      var socketError = await _socket.ReceiveAsync(saea);      if (socketError != SocketError.Success)          break;  
      var receiveCount = saea.Saea.BytesTransferred;      if (receiveCount == 0)          break;
  }

同样,TcpSocketSaeaServer 对外所暴露的接口也同样是 Awaitable 的。


  public interface ITcpSocketSaeaServerMessageDispatcher
  {
      Task OnSessionStarted(TcpSocketSaeaSession session);
      Task OnSessionDataReceived(TcpSocketSaeaSession session, byte[] data, int offset, int count);
      Task OnSessionClosed(TcpSocketSaeaSession session);
  }

使用起来也是简单直接:


  public class SimpleMessageDispatcher : ITcpSocketSaeaServerMessageDispatcher
  {      public async Task OnSessionStarted(TcpSocketSaeaSession session)
      {
          Console.WriteLine(string.Format("TCP session {0} has connected {1}.", session.RemoteEndPoint, session));          await Task.CompletedTask;
      }  
      public async Task OnSessionDataReceived(TcpSocketSaeaSession session, byte[] data, int offset, int count)
      {          var text = Encoding.UTF8.GetString(data, offset, count);
          Console.Write(string.Format("Client : {0} --> ", session.RemoteEndPoint));
          Console.WriteLine(string.Format("{0}", text));  
          await session.SendAsync(Encoding.UTF8.GetBytes(text));
      }  
      public async Task OnSessionClosed(TcpSocketSaeaSession session)
      {
          Console.WriteLine(string.Format("TCP session {0} has disconnected.", session));          await Task.CompletedTask;
      }
  }

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) =>{
    Task.Run(async () =>
    {        await Process(acceptedSocket);
    })
    .Forget();
};

通过 TcpSocketRioSession 来处理 Read Loop,


  while (State == TcpSocketConnectionState.Connected)
  {      int receiveCount = await _stream.ReadAsync(_receiveBuffer, 0, _receiveBuffer.Length);      if (receiveCount == 0)          break;
  }

测试代码一如既往的类似:


  public class SimpleMessageDispatcher : ITcpSocketRioServerMessageDispatcher
  {      public async Task OnSessionStarted(TcpSocketRioSession session)
      {          //Console.WriteLine(string.Format("TCP session {0} has connected {1}.", session.RemoteEndPoint, session));
          Console.WriteLine(string.Format("TCP session has connected {0}.", session));          await Task.CompletedTask;
      }  
      public async Task OnSessionDataReceived(TcpSocketRioSession session, byte[] data, int offset, int count)
      {          var text = Encoding.UTF8.GetString(data, offset, count);          //Console.Write(string.Format("Client : {0} --> ", session.RemoteEndPoint));
          Console.Write(string.Format("Client : --> "));
          Console.WriteLine(string.Format("{0}", text));  
          await session.SendAsync(Encoding.UTF8.GetBytes(text));
      }  
      public async Task OnSessionClosed(TcpSocketRioSession session)
      {
          Console.WriteLine(string.Format("TCP session {0} has disconnected.", session));          await Task.CompletedTask;
      }
  }

参考资料

  • 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 发表自博客园个人博客,未经作者本人同意禁止以任何的形式转载,任何自动的或人为的爬虫转载行为均为耍流氓。

以上是C# 高效能 TCP 服務的多種實現的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
C#.NET生態系統:框架,庫和工具C#.NET生態系統:框架,庫和工具Apr 24, 2025 am 12:02 AM

C#.NET生態系統提供了豐富的框架和庫,幫助開發者高效構建應用。 1.ASP.NETCore用於構建高性能Web應用,2.EntityFrameworkCore用於數據庫操作。通過理解這些工具的使用和最佳實踐,開發者可以提高應用的質量和性能。

將C#.NET應用程序部署到Azure/AWS:逐步指南將C#.NET應用程序部署到Azure/AWS:逐步指南Apr 23, 2025 am 12:06 AM

如何將C#.NET應用部署到Azure或AWS?答案是使用AzureAppService和AWSElasticBeanstalk。 1.在Azure上,使用AzureAppService和AzurePipelines自動化部署。 2.在AWS上,使用AmazonElasticBeanstalk和AWSLambda實現部署和無服務器計算。

C#.NET:強大的編程語言簡介C#.NET:強大的編程語言簡介Apr 22, 2025 am 12:04 AM

C#和.NET的結合為開發者提供了強大的編程環境。 1)C#支持多態性和異步編程,2).NET提供跨平台能力和並發處理機制,這使得它們在桌面、Web和移動應用開發中廣泛應用。

.NET框架與C#:解碼術語.NET框架與C#:解碼術語Apr 21, 2025 am 12:05 AM

.NETFramework是一個軟件框架,C#是一種編程語言。 1..NETFramework提供庫和服務,支持桌面、Web和移動應用開發。 2.C#設計用於.NETFramework,支持現代編程功能。 3..NETFramework通過CLR管理代碼執行,C#代碼編譯成IL後由CLR運行。 4.使用.NETFramework可快速開發應用,C#提供如LINQ的高級功能。 5.常見錯誤包括類型轉換和異步編程死鎖,調試需用VisualStudio工具。

揭開c#.net的神秘面紗:初學者的概述揭開c#.net的神秘面紗:初學者的概述Apr 20, 2025 am 12:11 AM

C#是一種由微軟開發的現代、面向對象的編程語言,.NET是微軟提供的開發框架。 C#結合了C 的性能和Java的簡潔性,適用於構建各種應用程序。 .NET框架支持多種語言,提供垃圾回收機制,簡化內存管理。

C#和.NET運行時:它們如何一起工作C#和.NET運行時:它們如何一起工作Apr 19, 2025 am 12:04 AM

C#和.NET運行時緊密合作,賦予開發者高效、強大且跨平台的開發能力。 1)C#是一種類型安全且面向對象的編程語言,旨在與.NET框架無縫集成。 2).NET運行時管理C#代碼的執行,提供垃圾回收、類型安全等服務,確保高效和跨平台運行。

C#.NET開發:入門的初學者指南C#.NET開發:入門的初學者指南Apr 18, 2025 am 12:17 AM

要開始C#.NET開發,你需要:1.了解C#的基礎知識和.NET框架的核心概念;2.掌握變量、數據類型、控制結構、函數和類的基本概念;3.學習C#的高級特性,如LINQ和異步編程;4.熟悉常見錯誤的調試技巧和性能優化方法。通過這些步驟,你可以逐步深入C#.NET的世界,並編寫高效的應用程序。

c#和.net:了解兩者之間的關係c#和.net:了解兩者之間的關係Apr 17, 2025 am 12:07 AM

C#和.NET的關係是密不可分的,但它們不是一回事。 C#是一門編程語言,而.NET是一個開發平台。 C#用於編寫代碼,編譯成.NET的中間語言(IL),由.NET運行時(CLR)執行。

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

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

AI Clothes Remover

AI Clothes Remover

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

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

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

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

Atom編輯器mac版下載

Atom編輯器mac版下載

最受歡迎的的開源編輯器

MinGW - Minimalist GNU for Windows

MinGW - Minimalist GNU for Windows

這個專案正在遷移到osdn.net/projects/mingw的過程中,你可以繼續在那裡關注我們。 MinGW:GNU編譯器集合(GCC)的本機Windows移植版本,可自由分發的導入函式庫和用於建置本機Windows應用程式的頭檔;包括對MSVC執行時間的擴展,以支援C99功能。 MinGW的所有軟體都可以在64位元Windows平台上運作。

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

WebStorm Mac版

WebStorm Mac版

好用的JavaScript開發工具