在 90 年代初期,為了統一 Windows 平台下的網絡程式開發介面,Microsoft 聯合多家廠商共同制定了 Windows Sockets(Winsock)規範。Winsock 並非一種網絡協議,而是一套開放的、支援多種協議的程式設計介面。現今的 Winsock 已實現與協議無關,開發者可透過它調用 TCP/IP、UDP 等多種協議功能,其中 TCP/IP 為最常用。Socket 在電腦中提供一個通訊端口,應用程式在網絡上收發資料皆須經由此介面。
使用 Visual C++ 進行 Winsock API 開發時,專案必須包含以下三個文件,否則會出現編譯錯誤:
呼叫 WSAStartup() 初始化 Windows Sockets DLL,只有此函數成功後,才能調用其他 Winsock API。典型調用方式:
WSAStartup(MAKEWORD(1,1), &WSAData);
其中 MAKEWORD(1,1) 表示使用 Winsock 1.1 版本,WSAData 結構用於接收系統回傳的資訊。
呼叫 socket() 建立 Socket 並指定通訊協議:
SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
af:地址家族,通常為 AF_INET。type:Socket 類型,SOCK_STREAM 為 TCP 流式,SOCK_DGRAM 為 UDP 資料報。protocol:協議類型,TCP 為 IPPROTO_TCP。成功返回 Socket 描述符,失敗返回 INVALID_SOCKET,可透過 WSAGetLastError() 取得錯誤代碼。
使用 bind() 為 Socket 指定本機 IP 與埠號,讓客戶端知曉連接目標:
bind(s, (sockaddr*)&addr, sizeof(addr));
若不指定具體 IP,可設為 INADDR_ANY;埠號設為 0 則由系統自動分配(1024~5000)。
呼叫 listen() 建立連接請求佇列,參數 backlog 指定最大佇列長度(上限 5):
listen(s, 5);
為了讓系統主動通知客戶端連接請求,需使用 WSAAsyncSelect() 註冊網絡事件:
WSAAsyncSelect(s, hWnd, WM_USER+1, FD_ACCEPT | FD_READ | FD_CLOSE);
當客戶端發起連接,視窗會收到自訂訊息,在訊息處理函數中根據 lParam 判斷事件類型(FD_ACCEPT、FD_READ 等),並呼叫 accept() 建立新的通訊 Socket:
SOCKET client = accept(s, (sockaddr*)&clientAddr, &len);
原監聽 Socket 繼續監聽,新 Socket 用於與該客戶端通訊。
通訊結束時呼叫 closesocket() 關閉 Socket,程式退出前必須呼叫 WSACleanup() 釋放 Winsock 佔用的資源。
客戶端同樣需先呼叫 WSAStartup() 與 socket()。不同之處在於:
bind() 指定本地 IP/埠,或由系統自動分配。connect() 主動向伺服器發起連接:connect(s, (sockaddr*)&serverAddr, sizeof(serverAddr));
| 特性 | TCP (SOCK_STREAM) | UDP (SOCK_DGRAM) |
|---|---|---|
| 連接方式 | 面向連接 | 無連接 |
| 可靠性 | 可靠、有序、不重複 | 不可靠、可能亂序、重複、丟包 |
| 發送/接收函數 | send() / recv() | sendto() / recvfrom() |
絕大多數應用為保證資料正確性,均採用 TCP。TCP 的 send() 在阻塞模式下會等待緩衝區可用;非阻塞模式則發送盡可能多的資料即返回。
為封裝底層 API 並簡化錯誤處理,文中實作了一個 CMySocket 類別,主要成員函數如下:
Create():建立 TCP Socket。Bind() / Listen() / Accept():伺服器端綁定、監聽、接受連接。Connect():客戶端發起連接,支援主機名或 IP 解析。Send() / Receive():資料收發。asyncSelect():註冊非同步事件通知。Close():關閉 Socket 並重置內部結構。WSAStartup(),解構函數自動呼叫 WSACleanup()。使用時,伺服器端需建立兩個 CMySocket 物件(一個監聽、一個通訊),客戶端建立一個即可。若需支援多連接,可增加物件,但注意單進程監聽佇列上限為 5。
本文系統闡述了 Winsock API 的核心流程與關鍵函數,並提供了可復用的 CMySocket 類別原始碼。開發者可在此基礎上擴充更多功能(如獲取遠端 IP、主機名等),快速構建穩健的網絡應用程式。