Windows Socket 提供多種 I/O 模型,由淺入深:Select → WSAAsyncSelect → WSAEventSelect → Overlapped I/O (事件通知/完成例程) → IOCP (I/O Completion Port)。本文以「老陳收信」比喻逐一說明原理、程式碼骨架與適用場景。
比喻:老陳每 10 分鐘下樓檢查信箱。
特點:單執行緒輪詢多個 Socket,fd_set 最大 64 個 (Windows),需自行管理陣列。
procedure TListenThread.Execute;
var
ReadSet: TFDSet; TimeVal: TTimeVal; i: Integer; NewSock: TSocket;
begin
// ... bind, listen ...
while not Terminated do
begin
FD_ZERO(ReadSet); FD_SET(ListenSock, ReadSet);
TimeVal.tv_sec := 0; TimeVal.tv_usec := 500;
if select(0, @ReadSet, nil, nil, @TimeVal) > 0 then
if FD_ISSET(ListenSock, ReadSet) then
for i := 0 to ReadSet.fd_count - 1 do
begin
NewSock := accept(ListenSock, ...);
// 為新連線建立執行緒並再次 select
end;
end;
end;
缺點:忙碌等待浪費 CPU、64 連線限制、程式碼複雜。
比喻:微軟信箱有信時打電話通知老陳。
特點:將網路事件 (FD_READ、FD_ACCEPT、FD_CLOSE 等) 封裝為 WM_USER+xxx 視窗訊息,在主視窗程序處理,適合單執行緒 GUI 應用。
const WM_SOCK = WM_USER + 1;
WSAAsyncSelect(Sock, Handle, WM_SOCK, FD_READ or FD_ACCEPT or FD_CLOSE);
procedure TForm1.WMSock(var Msg: TMessage); message WM_SOCK;
var
NewSock: TSocket; Addr: TSockAddrIn; Len: Integer; Buf: array[0..4095] of Char;
begin
case WSAGetSelectEvent(Msg.LParam) of
FD_ACCEPT:
begin
Len := SizeOf(Addr);
NewSock := accept(Msg.WParam, @Addr, @Len);
WSAAsyncSelect(NewSock, Handle, WM_SOCK, FD_READ or FD_WRITE or FD_CLOSE);
end;
FD_READ: recv(Msg.WParam, @Buf, SizeOf(Buf), 0);
FD_CLOSE: closesocket(Msg.WParam);
end;
end;
限制:單視窗處理所有連線,高負載時訊息佇列擁塞。
比喻:信箱裝感測器,有信響鈴,老陳聽到再去拿。
特點:建立 WSAEvent 綁定 Socket 事件,專用執行緒 WSAWaitForMultipleEvents 等待,支援 64 事件 (同 WSA_MAXIMUM_WAIT_EVENTS)。
procedure TListenThread.Execute;
var
Events: array[0..63] of WSAEvent; Socks: array[0..63] of TSocket;
Count: Integer; Index: DWORD; NetEvents: TWSANetworkEvents;
begin
// ... listen socket 綁定 Events[0] ...
while not Terminated do
begin
Index := WSAWaitForMultipleEvents(Count, @Events, False, WSA_INFINITE, False);
Index := Index - WSA_WAIT_EVENT_0;
WSAEnumNetworkEvents(Socks[Index], Events[Index], @NetEvents);
if (NetEvents.lNetworkEvents and FD_ACCEPT) <> 0 then
// accept 新連線,建立 Event 綁定 FD_READ/FD_WRITE/FD_CLOSE,加入陣列
if (NetEvents.lNetworkEvents and FD_READ) <> 0 then
// recv 處理
end;
end;
比喻:郵差直接把信送到老陳家門口,放好後通知老陳。
特點:提交 WSARecv/WSASend 時傳入 WSAOVERLAPPED (含 Event Handle),作業完成由系統觸發 Event,執行緒 WSAWaitForMultipleEvents 等待,完成後呼叫 WSAGetOverlappedResult 取得結果,再重新投遞下一個接收請求。
procedure TOverlapThread.Execute;
var
Index: DWORD; Bytes: DWORD; Flags: DWORD;
begin
while not Terminated do
begin
Index := WSAWaitForMultipleEvents(Count, @Events, False, TIMEOUT, False);
WSAResetEvent(Events[Index]);
WSAGetOverlappedResult(Socks[Index], @Overlaps[Index], @Bytes, False, @Flags);
if Bytes = 0 then // 連線關閉
Cleanup(Index)
else
ProcessData(Buffers[Index], Bytes);
// 重新投遞接收
FillChar(Overlaps[Index], SizeOf(WSAOVERLAPPED), 0);
Overlaps[Index].hEvent := Events[Index];
FillChar(Buffers[Index], SizeOf(Buffers[Index]), 0);
WSARecv(Socks[Index], @WSABuf, 1, @Bytes, @Flags, @Overlaps[Index], nil);
end;
end;
比喻:老陳告訴郵差「收到信幫我拆開、讀完、回覆」,郵差全自動處理。
特點:提供回呼函數 WorkerRoutine,系統在執行緒處於可提醒狀態 (SleepEx(..., True)) 時自動排程執行,執行緒幾乎無需主動等待。
procedure WorkerRoutine(Error, Transferred: DWORD; Overlapped: LPWSAOVERLAPPED; Flags: DWORD); stdcall;
begin
// 處理接收資料,再投遞下一個 WSARecv
end;
WSARecv(Sock, @WSABuf, 1, @Bytes, @Flags, @Overlap, @WorkerRoutine);
while not Terminated do
if SleepEx(TIMEOUT, True) = WAIT_IO_COMPLETION then ; // 執行 APC
比喻:微軟派超級機器人處理所有信件,老陳完全不用管。
核心機制:
CreateIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 0)。CreateIoCompletionPort(Socket, Port, 0, 0)。CPU核心數 * 2 條工作者執行緒,全部呼叫 GetQueuedCompletionStatus(Port, ...) 等待。procedure TWorkerThread.Execute;
var
Bytes: DWORD; Key: ULONG_PTR; Overlap: POverlapped;
begin
while not Terminated do
begin
if GetQueuedCompletionStatus(Port, Bytes, Key, Overlap, INFINITE) then
begin
if Bytes = 0 then CloseConnection(Key)
else ProcessData(Key, Overlap, Bytes);
// 重新投遞
WSARecv(Key, ...);
end;
end;
end;
| 模型 | 適用場景 | 最大連線 | 複雜度 | 效能 |
|---|---|---|---|---|
| Select | 教學、極少量連線 | 64 | 低 | 低 |
| WSAAsyncSelect | 單執行緒 GUI 小工具 | 視窗訊息限制 | 低 | 低 |
| WSAEventSelect | 中等連線、需自訂執行緒 | 64/執行緒 | 中 | 中 |
| Overlapped Event | 高效能、自訂執行緒池 | 64/執行緒 | 高 | 高 |
| Overlapped APC | 極簡執行緒邏輯 | 64/執行緒 | 高 | 高 |
| IOCP | **大型伺服器、萬級連線** | **無實際上限** | 高 | **最高** |
OnExecute 邏輯。