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

Winsock 編程入門:Client 端核心 API 與多線程端口掃描器實戰

     編輯:關於C語言

Winsock 客戶端編程遵循固定模式:初始化 → 創建套接字 → 連接服務端 → 收發數據 → 清理。本文梳理核心 API 參數細節,並附贈多線程端口掃描器完整源碼實現。

核心 API 深度解析

1. WSAStartup / WSACleanup:DLL 版本協商與引用計數

WSADATA wsaData;
// 請求 2.2 版本(低字節主版本,高字節次版本)
if (WSAStartup(MAKEWORD(2,2), &wsaData) != 0) {
    // 錯誤處理
}
// 檢查實際加載版本
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
    WSACleanup();
    return; // 不支援請求版本
}
// ... 使用網絡功能 ...
WSACleanup(); // 必須配對調用
  • 內部維護引用計數:首次調用加載 DLL,後續僅增計數
  • WSACleanup 遞減計數,歸零時卸載 DLL
  • 多線程中每線程可獨立調用,但通常進程級調用一次即可

2. socket / WSASocket:套接字創建

SOCKET 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()

3. connect / WSAConnect:主動發起連接

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 等成熟庫。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved