首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> Delphi

Delphi Socket I/O 模型全解析:Select、WSAAsyncSelect、WSAEventSelect、Overlapped I/O、IOCP

     編輯:Delphi

前言

Windows Socket 提供多種 I/O 模型,由淺入深:Select → WSAAsyncSelect → WSAEventSelect → Overlapped I/O (事件通知/完成例程) → IOCP (I/O Completion Port)。本文以「老陳收信」比喻逐一說明原理、程式碼骨架與適用場景。

1. Select 模型 (輪詢)

比喻:老陳每 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 連線限制、程式碼複雜。

2. WSAAsyncSelect 模型 (視窗訊息)

比喻:微軟信箱有信時打電話通知老陳。

特點:將網路事件 (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;

限制:單視窗處理所有連線,高負載時訊息佇列擁塞。

3. WSAEventSelect 模型 (事件物件 + 執行緒)

比喻:信箱裝感測器,有信響鈴,老陳聽到再去拿。

特點:建立 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;

4. Overlapped I/O 事件通知模型

比喻:郵差直接把信送到老陳家門口,放好後通知老陳。

特點:提交 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;

5. Overlapped I/O 完成例程 (APC)

比喻:老陳告訴郵差「收到信幫我拆開、讀完、回覆」,郵差全自動處理。

特點:提供回呼函數 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

6. IOCP (I/O Completion Port) 模型 — 高效能伺服器標準

比喻:微軟派超級機器人處理所有信件,老陳完全不用管。

核心機制:

  • 建立 Completion Port:CreateIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 0)。
  • 每個 Accept 的 Socket 綁定到 Port:CreateIoCompletionPort(Socket, Port, 0, 0)。
  • 啟動 CPU核心數 * 2 條工作者執行緒,全部呼叫 GetQueuedCompletionStatus(Port, ...) 等待。
  • 任何 Socket 完成 I/O,系統將完成封包排入 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**大型伺服器、萬級連線****無實際上限**高**最高**

現代 Delphi 開發建議

  • 直接使用 Indy (TIdTCPServer):內部已實作 IOCP (Windows) / epoll (Linux),開發者只需撰寫 OnExecute 邏輯。
  • 或使用 ICS (Francois Piette)、sgcWebSockets、DataSnap 等成熟框架。
  • 僅在極度底層控制、嵌入式、或教學研究時手寫 IOCP。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved