Windows 平台調用 Socket API 前,必須調用 WSAStartup 載入 Winsock DLL 並協商版本:
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
// 錯誤處理
return -1;
}
// 檢查實際版本
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
WSACleanup();
return -1;
}
MAKEWORD(2,2) 請求 2.2 版本(主版本 2,副版本 2),比舊教程常見的 1.1 更現代。LOBYTE/HIBYTE 宏從 wVersion 提取主/副版本號。WSACleanup() 釋放資源。SOCKET tcp_socket = socket(AF_INET, SOCK_STREAM, 0);
| 參數 | 值 | 含義 |
|---|---|---|
| af | AF_INET | IPv4 協議族 |
| type | SOCK_STREAM | 面向連接、可靠的字節流(TCP) |
| protocol | 0 | 自動選擇協議(TCP 對應 IPPROTO_TCP) |
返回 INVALID_SOCKET 表示失敗,需檢查 WSAGetLastError()。
sockaddr_in saddr{};
saddr.sin_family = AF_INET;
saddr.sin_port = htons(port); // 主機序轉網絡序
saddr.sin_addr.s_addr = INADDR_ANY; // 綁定所有本地接口
sin_zero[8] 僅為填充對齊,無實際業務含義。INADDR_ANY 允許服務器接受任意網卡上的連接請求。if (::bind(tcp_socket, (sockaddr*)&saddr, sizeof(saddr)) == SOCKET_ERROR) {
// 錯誤處理:地址已被佔用、權限不足等
}
關鍵點:使用 ::bind 顯式指定全局命名空間,避免與 std::bind 冲突(若有 using namespace std;)。
if (::listen(tcp_socket, SOMAXCONN) == SOCKET_ERROR) {
// 錯誤處理
}
backlog 參數指定內核等待隊列最大長度,SOMAXCONN 為系統默認最大值。int make_server_socket(int port) {
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2,2), &wsaData) != 0) return -1;
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
WSACleanup(); return -1;
}
SOCKET s = socket(AF_INET, SOCK_STREAM, 0);
if (s == INVALID_SOCKET) { WSACleanup(); return -1; }
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = INADDR_ANY;
if (::bind(s, (sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR) {
closesocket(s); WSACleanup(); return -1;
}
if (::listen(s, SOMAXCONN) == SOCKET_ERROR) {
closesocket(s); WSACleanup(); return -1;
}
return (int)s; // 32位下安全,64位建議返回 SOCKET 類型
}
掌握 WSAStartup 版本協商、socket/bind/listen 三部曲、網絡字節序轉換(htons)、全局 ::bind 避免命名冲突,是構建健壯 Windows Socket 服務器的基石。下一篇將講解 accept 循環與客戶端通訊處理。