承接上篇基礎概念,本文深入探討 WinSock 核心 API 函式、位元組序轉換、IP 位址處理、Socket 狀態查詢,以及三種主流 I/O 模型:select、WSAAsyncSelect、WSAEventSelect 的原理與實作細節。
int WSAGetLastError(void);
#define h_errno WSAGetLastError()
每次呼叫 WinSock 函式失敗後,立即呼叫此函式取得錯誤碼,詳細代碼請參考 Winsock2.h。
因不同 CPU 架構 (Little-Endian / Big-Endian) 儲存方式不同,網路輸統一使用 Big-Endian (Network Byte Order)。
| 函式 | 用途 | 範例 |
|---|---|---|
u_long htonl(u_long hostlong) | 32-bit 主機序 → 網路序 | htonl(80) = 1342177280 |
u_long ntohl(u_long netlong) | 32-bit 網路序 → 主機序 | ntohl(1342177280) = 80 |
u_short htons(u_short hostshort) | 16-bit 主機序 → 網路序 | htons(80) = 20480 |
u_short ntohs(u_short netshort) | 16-bit 網路序 → 主機序 | ntohs(20480) = 80 |
// 點分十進位字串 → in_addr (u_long)
unsigned long inet_addr(const char FAR * cp);
// 範例:inet_addr("127.0.0.1") = 16777343
// 失敗回傳 INADDR_NONE
// in_addr → 點分十進位字串
char FAR * inet_ntoa(struct in_addr in);
// 注意:回傳指標指向靜態緩衝區,切勿修改或釋放
// 取得本地綁定位址
int getsockname(SOCKET s, struct sockaddr FAR *name, int FAR *namelen);
// 取得對端位址
int getpeername(SOCKET s, struct sockaddr FAR *name, int FAR *namelen);
// 取得本機主機名
int gethostname(char FAR *name, int namelen);
// 依主機名取得 hostent 結構 (含 IP 列表)
struct hostent FAR * gethostbyname(const char FAR *name);
// 範例:取得第一個 IP
hostent *host = gethostbyname("xiaojin");
if (host->h_addr_list[0]) {
in_addr addr;
memmove(&addr, host->h_addr_list[0], 4);
char *ip = inet_ntoa(addr); // "127.0.0.1"
}
recv/send 完成前不返回,需搭配多執行緒處理多連線。WSAEWOULDBLOCK。邏輯複雜但擴展性強。單一執行緒監控多個 Socket 狀態 (可讀/可寫/例外)。
int select(int nfds, fd_set FAR *readfds, fd_set FAR *writefds,
fd_set FAR *exceptfds, const struct timeval FAR *timeout);
關鍵結構:
fd_set:含 fd_count 與 fd_array[FD_SETSIZE] (預設 64)。timeval:tv_sec 秒 + tv_usec 微秒。設 {0,0} 為立即返回 (輪詢)。常用巨集:FD_ZERO、FD_SET、FD_CLR、FD_ISSET。
fd_set fdread;
FD_ZERO(&fdread);
FD_SET(s, &fdread);
if (select(0, &fdread, NULL, NULL, NULL) > 0) {
if (FD_ISSET(s, &fdread)) { /* 可讀 */ }
}
自動將 Socket 設為非阻塞,向指定視窗發送自訂訊息 (wMsg) 通知網路事件。
int WSAAsyncSelect(SOCKET s, HWND hWnd, u_int wMsg, long lEvent);
// lEvent 事件掩碼:FD_READ | FD_WRITE | FD_ACCEPT | FD_CONNECT | FD_CLOSE ...
// 取消通知:WSAAsyncSelect(s, hWnd, 0, 0);
訊息處理:wParam = Socket,LOWORD(lParam) = 事件,HIWORD(lParam) = 錯誤碼。
與 WSAAsyncSelect 類似,但通知目標為 事件物件 (Event Object) 而非視窗。
WSACreateEvent() 建立事件物件 (預設手動重置、非發送狀態)。WSAEventSelect(s, hEvent, lNetworkEvents) 關聯 Socket 與事件。WSAResetEvent(hEvent) 重置為非發送狀態。WSAWaitForMultipleEvents(...) 等待事件觸發 (支援多事件、超時、Alertable)。WSAEnumNetworkEvents(s, hEvent, &netEvents) 查詢具體事件與錯誤碼。WSACloseEvent(hEvent) 關閉事件物件。int ioctlsocket(SOCKET s, long cmd, u_long FAR *argp);
常用命令:
FIONREAD:取得接收緩衝區資料長度。FIONBIO:啟用/停用非阻塞模式 (非 0 = 啟用)。SIOCATMARK:檢查是否所有帶外資料已讀取。