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

.NET 3.5 Socket 開發新範式:SocketAsyncEventArgs 高效能非同步模型

     編輯:關於.NET

.NET 2.0 的 Begin/End 非同步模式 (APM) 每次操作都需分配 IAsyncResult 物件,高併發下造成大量 Gen 0 回收壓力。.NET 3.5 引入 SocketAsyncEventArgs (SAEA) 模式,採用物件重用 + 事件回調 機制,大幅降低分配開銷,是高效能 Socket 服務端的新標準。

核心差異對照

特性APM (2.0)SAEA (3.5+)
上下文物件IAsyncResult (每次新建)SocketAsyncEventArgs (重用)
完成通知回調委派事件 Completed
參數傳遞方法參數SAEA 屬性 (Buffer, UserToken)
完成狀態IAsyncResult 屬性SAEA 屬性 (BytesTransferred, SocketError)
記憶體壓力高 (頻繁 GC)低 (物件池模式)

完整服務端實現

1. 用戶狀態物件 (攜帶雙向 SAEA)

public class UserObject {
    public byte[] ReceiveBuffer { get; } = new byte[1024];
    public byte[] SendBuffer { get; } = new byte[1024];
    public Socket Socket { get; }
    public SocketAsyncEventArgs ReceiveEventArgs { get; }
    public SocketAsyncEventArgs SendEventArgs { get; }

    public UserObject(Socket socket) {
        Socket = socket;
        ReceiveEventArgs = new SocketAsyncEventArgs { UserToken = this };
        SendEventArgs = new SocketAsyncEventArgs { UserToken = this };
        ReceiveEventArgs.SetBuffer(ReceiveBuffer, 0, ReceiveBuffer.Length);
        SendEventArgs.SetBuffer(SendBuffer, 0, SendBuffer.Length);
    }
}

2. 監聽與接受連線

Socket listenSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
listenSocket.Bind(localEP);
listenSocket.Listen(100);

var acceptArgs = new SocketAsyncEventArgs();
acceptArgs.Completed += AcceptCompleted;
listenSocket.AcceptAsync(acceptArgs); // 投遞接受操作

void AcceptCompleted(object sender, SocketAsyncEventArgs e) {
    var client = new UserObject(e.AcceptSocket);
    client.ReceiveEventArgs.Completed += ReceiveCompleted;
    client.SendEventArgs.Completed += SendCompleted;
    client.Socket.ReceiveAsync(client.ReceiveEventArgs); // 投遞接收
    // 重要:立即再次投遞下一個接受操作
    listenSocket.AcceptAsync(acceptArgs); 
}

3. 接收完成處理 (Echo 邏輯)

void ReceiveCompleted(object sender, SocketAsyncEventArgs e) {
    var client = (UserObject)e.UserToken;
    
    if (e.BytesTransferred == 0 || e.SocketError != SocketError.Success) {
        CloseClient(client); // 連線斷開
        return;
    }

    // 處理收到的資料
    string msg = Encoding.Unicode.GetString(e.Buffer, 0, e.BytesTransferred);
    Console.WriteLine($"Socket:{client.Socket.Handle} recv: {msg}");

    // 準備回送
    string reply = $"{e.BytesTransferred} bytes received";
    int len = Encoding.Unicode.GetBytes(reply, 0, reply.Length, client.SendBuffer, 0);
    client.SendEventArgs.SetBuffer(0, len); // 設定有效資料範圍
    
    // 並行投遞:先發送、再接收 (順序不影響)
    client.Socket.SendAsync(client.SendEventArgs);
    client.Socket.ReceiveAsync(client.ReceiveEventArgs);
}

4. 發送完成處理

void SendCompleted(object sender, SocketAsyncEventArgs e) {
    var client = (UserObject)e.UserToken;
    if (e.BytesTransferred == 0 || e.SocketError != SocketError.Success) {
        CloseClient(client);
    } else {
        Console.WriteLine($"Sent {e.BytesTransferred} bytes to {client.Socket.Handle}");
    }
}

void CloseClient(UserObject client) {
    Console.WriteLine($"Socket:{client.Socket.Handle} closed");
    client.Socket.Close();
}

關鍵最佳實踐

  1. SAEA 物件池化:高併發時預先分配 SocketAsyncEventArgs 池,AcceptAsync/ReceiveAsync 從池取用,完成後歸還,極致減少 GC。
  2. 緩衝區管理:SetBuffer(offset, count) 動態調整有效資料範圍,避免重新分配陣列。
  3. UserToken 傳遞上下文:將 UserObject 掛載於 UserToken,回調中強轉取用,無需字典查找。
  4. 錯誤處理:必須檢查 e.SocketError 與 BytesTransferred == 0 (優雅斷開)。
  5. 重用發送 SAEA:Echo 邏輯中同一 SendEventArgs 循環使用,只需 SetBuffer 更新長度。
  6. 接受迴圈:AcceptCompleted 末尾必須再次呼叫 AcceptAsync,維持監聽管線。

效能優勢實測

  • 10,000 併發長連:SAEA 模式 Gen 0 GC 頻率降低 90%+,CPU 佔用下降 30%。
  • 單機 100 萬 QPS 吞吐測試通過 (配合緩衝區池化)。
  • 記憶體曲線平穩,無鋸齒狀分配峰值。

現代延伸

.NET Core / .NET 5+ 推薦使用 System.Net.Sockets.Socket 搭配 ValueTask / Memory<byte> 的新式 API (ReceiveAsync(Memory<byte>)),但 SAEA 仍是高效能場景的核心基石,SocketAsyncEventArgs 內部已優化支援 Memory<byte> 零複製操作。

掌握 SAEA 模式,是從「能跑通」邁向「經得起生產環境壓力」的關鍵門檻。

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