首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> 關於.NET

.NET 3.5 SocketAsyncEventArgs 高效能伺服器實作:緩衝池與物件池模式深度解析

     編輯:關於.NET

.NET 3.5 System.Net.Sockets 增強提供高效能異步模式,SocketAsyncEventArgs 專為高效能網路伺服器設計,核心改進:可重用物件避免頻繁 GC,解決傳統異步模式每次 new IAsyncResult 導致 GC CPU 飆高問題。

架構總覽

範例方案三專案:

  • FlawlessCode:核心庫(TcpSocketListener、ServerConnection、BufferPool、SocketArgsPool)
  • TestSocketServer:簡易伺服器實作
  • TestLoadGenerator:壓力測試客戶端(15000 連線)

核心元件解析

TcpSocketListener:連線監聽器

公共介面極簡:

public void Start();
public void Stop();
public event EventHandler<SocketEventArgs> SocketConnected;

內部循環使用 AcceptAsync + SocketAsyncEventArgs 重用:

private void ListenForConnection(SocketAsyncEventArgs args) {
    args.AcceptSocket = null;
    listenerSocket.InvokeAsyncMethod(
        new SocketAsyncMethod(listenerSocket.AcceptAsync), SocketAccepted, args);
}
private void SocketAccepted(object sender, SocketAsyncEventArgs e) {
    if (e.SocketError == SocketError.OperationAborted) return; // 停止
    if (e.SocketError == SocketError.Success) {
        OnSocketConnected(e.AcceptSocket);
    }
    lock (this) { ListenForConnection(e); }  // 重用 args 繼續監聽
}

ServerConnection:封裝客戶端連線

建立即開始接收資料,提供同步發送與斷開方法:

public void SendData(byte[] data, int offset, int count) { ... }  // 同步發送
public void Disconnect() { ... }  // 關閉連線並觸發回調

接收循環:

private void ListenForData(SocketAsyncEventArgs args) {
    lock (this) {
        Socket socket = (args.UserToken as State).socket;
        if (socket.Connected)
            socket.InvokeAsyncMethod(socket.ReceiveAsync, ReceivedCompleted, args);
    }
}
private void ReceivedCompleted(object sender, SocketAsyncEventArgs args) {
    if (args.BytesTransferred == 0) { CloseConnection(args); return; }  // 優雅斷開
    if (args.SocketError != SocketError.Success) { CloseConnection(args); return; }
    
    State state = args.UserToken as State;
    byte[] data = new byte[args.BytesTransferred];
    Array.Copy(args.Buffer, args.Offset, data, 0, data.Length);
    OnDataReceived(data, args.RemoteEndPoint as IPEndPoint, state.dataReceived);
    ListenForData(args);  // 重用 args 繼續接收
}

關鍵:斷開時必須 args.Completed -= ReceivedCompleted 解除事件訂閱,避免物件池重用時殘留引用。

BufferPool & SocketArgsPool:資源池化

參考 MSDN 範例實作,分別管理緩衝區與 SocketAsyncEventArgs 物件的棧出/棧入,幾乎相同邏輯:

伺服器實作:TestSocketServer

TcpSocketListener socketListener = new TcpSocketListener(IPAddress.Any, 12345, 10);
socketListener.SocketConnected += socketListener_SocketConnected;
socketListener.Start();

static void socketListener_SocketConnected(object sender, SocketEventArgs e) {
    SocketAsyncEventArgs args = socketArgsPool.CheckOut();
    bufferManager.CheckOut(args);
    ServerConnection connection = new ServerConnection(e.Socket, args,
        new DataReceivedCallback(DataReceived),
        new DisconnectedCallback(Disconnected));
}

static void DataReceived(ServerConnection sender, DataEventArgs e) { /* 處理業務 */ }
static void Disconnected(ServerConnection sender, SocketAsyncEventArgs e) {
    bufferManager.CheckIn(e);
    socketArgsPool.CheckIn(e);  // 歸還資源池
}

注意:建構函數傳入委託而非訂閱事件,確保首批資料不遺漏。

壓力測試:TestLoadGenerator

LoadGenerator generator = new LoadGenerator(15000);
generator.BytesPerDelivery = 2048;
generator.DeliveriesPerSecond = 2;
generator.SocketCount = 15000;
generator.SocketDelay = 5;
generator.SocketsPerDelivery = 3;
generator.Start(IPAddress.Parse("127.0.0.1"), 12345);

註冊表調整:需在 HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters 添加 MaxUserPort DWORD 設大值(需重啟),否則端口耗盡。

效能實測數據

效能監控圖

  • 伺服端初始化 5000 SocketArgsPool + 5000 BufferPool (1024 bytes)
  • 啟動後佔用 ~500MB 記憶體(雙核 32 位)
  • 客戶端 5000 連線,每秒 2 包,每包 2048 bytes
  • 運行 30 分鐘:109MB RAM、1% CPU
  • GC、執行緒爭用極低

裸 Socket 伺服器表現優異,加入業務邏輯後開銷會上升,但基礎架構極其穩健。

原文:FlawlessCode

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved