.NET 3.5 System.Net.Sockets 增強提供高效能異步模式,SocketAsyncEventArgs 專為高效能網路伺服器設計,核心改進:可重用物件避免頻繁 GC,解決傳統異步模式每次 new IAsyncResult 導致 GC CPU 飆高問題。
範例方案三專案:
公共介面極簡:
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 繼續監聽
}
建立即開始接收資料,提供同步發送與斷開方法:
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 解除事件訂閱,避免物件池重用時殘留引用。
參考 MSDN 範例實作,分別管理緩衝區與 SocketAsyncEventArgs 物件的棧出/棧入,幾乎相同邏輯:
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); // 歸還資源池
}
注意:建構函數傳入委託而非訂閱事件,確保首批資料不遺漏。
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 設大值(需重啟),否則端口耗盡。

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