Winsock 客戶端編程遵循固定模式:初始化 → 創建套接字 → 連接服務端 → 收發數據 → 清理。本文梳理核心 API 參數細節,並附贈多線程端口掃描器完整源碼實現。
WSADATA wsaData;
// 請求 2.2 版本(低字節主版本,高字節次版本)
if (WSAStartup(MAKEWORD(2,2), &wsaData) != 0) {
// 錯誤處理
}
// 檢查實際加載版本
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
WSACleanup();
return; // 不支援請求版本
}
// ... 使用網絡功能 ...
WSACleanup(); // 必須配對調用
WSACleanup 遞減計數,歸零時卸載 DLLSOCKET sk = socket(AF_INET, SOCK_STREAM, 0); // AF_INET: IPv4 // SOCK_STREAM: TCP (可靠、有序、面向連接) // SOCK_DGRAM: UDP (不可靠、無序、無連接) // 第三參數 0 = 默認協議 (TCP 對應 IPPROTO_TCP)
WSASocket 支援重疊 I/O、特定提供者等進階特性INVALID_SOCKET 表示失敗,調用 WSAGetLastError()sockaddr_in addr = {0};
addr.sin_family = AF_INET;
addr.sin_port = htons(80); // 必須網絡序
addr.sin_addr.s_addr = inet_addr("202.205.210.1"); // 或 inet_pton
if (connect(sk, (SOCKADDR*)&addr, sizeof(addr)) == SOCKET_ERROR) {
int err = WSAGetLastError();
// WSAEWOULDBLOCK (非阻塞模式下正常)
// WSAEISCONN (已連接)
// WSAECONNREFUSED / WSAETIMEDOUT 等
}
connect 直到三次握手完成或超時/失敗才返回WSAEWOULDBLOCK,需用 select/WSAEventSelect/IOCP 等待寫就緒#include#include #include #pragma comment(lib, "ws2_32.lib") DWORD WINAPI ScanThread(LPVOID lpParam) { int port = *(int*)lpParam; SOCKET s = socket(AF_INET, SOCK_STREAM, 0); if (s == INVALID_SOCKET) return 1; sockaddr_in target = {0}; target.sin_family = AF_INET; target.sin_port = htons(port); target.sin_addr.s_addr = inet_addr("192.168.1.1"); // 目標 IP // 設置非阻塞 + 短超時,加快掃描 u_long mode = 1; ioctlsocket(s, FIONBIO, &mode); int res = connect(s, (SOCKADDR*)&target, sizeof(target)); if (res == SOCKET_ERROR) { int err = WSAGetLastError(); if (err != WSAEWOULDBLOCK) { closesocket(s); return 1; } // 等待 200ms 判斷連接結果 fd_set writeSet; FD_ZERO(&writeSet); FD_SET(s, &writeSet); timeval tv = {0, 200000}; // 200ms if (select(0, NULL, &writeSet, NULL, &tv) > 0) { int optVal, optLen = sizeof(optVal); getsockopt(s, SOL_SOCKET, SO_ERROR, (char*)&optVal, &optLen); if (optVal == 0) printf("[OPEN] Port %d\n", port); } } else { printf("[OPEN] Port %d\n", port); } closesocket(s); return 0; } int main() { WSADATA wsa; WSAStartup(MAKEWORD(2,2), &wsa); HANDLE threads[100]; int ports[100]; for (int i = 0; i < 100; i++) { ports[i] = i + 1; // 掃描 1-100 端口 threads[i] = CreateThread(NULL, 0, ScanThread, &ports[i], 0, NULL); } WaitForMultipleObjects(100, threads, TRUE, INFINITE); for (int i = 0; i < 100; i++) CloseHandle(threads[i]); WSACleanup(); return 0; }
| 技術 | 作用 |
|---|---|
| 非阻塞 + select 超時 | 避免阻塞連接拖慢整體掃描速度 |
| 線程池限制併發 | 防止創建過多線程耗盡系統資源(示例簡化為固定 100) |
| SO_RCVTIMEO/SO_SNDTIMEO | 替代 select 的另一種超時控制方式 |
| 分批次掃描 | 大範圍掃描分批,避免觸發目標防火牆/IDS |
網絡編程調試困難在於「對端不可控」。常用手段:
- WireShark 抓包分析三次握手/數據流
- 本地回環 127.0.0.1 自測
- Netcat (
nc -lvp 80) 側測試服務端- 日誌記錄每步錯誤碼與狀態變化
Winsock 客戶端核心流程固定,關鍵在於錯誤碼處理、阻塞/非阻塞模式選擇、超時控制、資源洩漏預防。端口掃描器展示了非阻塞連接 + 多線程併發的實戰模式。生產級網絡程序建議採用 IOCP (Windows) / epoll (Linux) / kqueue (macOS/BSD) 實現高併發異步架構,或直接使用 Boost.Asio / libuv / cpp-httplib 等成熟庫。