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

Windows 網絡編程入門:基於 Winsock API 的異步 Socket 服務器/客戶端模型

     編輯:VC++

面向初學者的經典入門指南:拒絕 MFC 封裝,直擊 Socket 核心機制

為什麼選擇原生 API?

MFC 的 CAsyncSocket/CSocket 雖方便,但封裝掩蓋了底層事件驅動本質。直接使用 WSAAsyncSelect 異步模型,能讓你真正理解:

  • 網絡事件如何映射到 Windows 消息隊列
  • 服務器如何並發處理多客戶端
  • 發送/接收緩衝區與應用層協議的協作

服務器端架構:雙線程模型

主線程:網絡事件分發器


// 定義自定義消息
#define WM_NET_EVENT (WM_USER + 100)

// 全局/成員變量
SOCKET g_sListen = INVALID_SOCKET;
HWND   g_hWnd = NULL;
sockaddr_in g_clientAddr;

// 消息映射
ON_MESSAGE(WM_NET_EVENT, OnNetEvent)

// 初始化網絡
BOOL InitNetwork(HWND hWnd) {
    g_hWnd = hWnd;
    WSADATA wsa; 
    if (WSAStartup(MAKEWORD(2,2), &wsa)) return FALSE;

    g_sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (g_sListen == INVALID_SOCKET) return FALSE;

    sockaddr_in local = { AF_INET, htons(1688), {INADDR_ANY} };
    if (bind(g_sListen, (sockaddr*)&local, sizeof(local)) == SOCKET_ERROR) return FALSE;

    // 關鍵:將 Socket 事件映射到窗口消息
    WSAAsyncSelect(g_sListen, g_hWnd, WM_NET_EVENT, 
                   FD_ACCEPT | FD_CLOSE | FD_READ | FD_WRITE);

    listen(g_sListen, SOMAXCONN);
    return TRUE;
}

// 網絡事件總入口
LRESULT OnNetEvent(WPARAM wParam, LPARAM lParam) {
    SOCKET s = (SOCKET)wParam;
    int event = WSAGETSELECTEVENT(lParam);
    int error = WSAGETSELECTERROR(lParam);
    if (error) { HandleError(s, error); return 0; }

    switch (event) {
        case FD_ACCEPT: OnAccept(s); break;
        case FD_READ:   OnReceive(s); break;
        case FD_WRITE:  OnSend(s); break;
        case FD_CLOSE:  OnClose(s); break;
    }
    return 0;
}

核心事件處理


void OnAccept(SOCKET sListen) {
    sockaddr_in client; int len = sizeof(client);
    SOCKET sClient = accept(sListen, (sockaddr*)&client, &len);
    if (sClient == INVALID_SOCKET) return;

    // 新 Socket 註冊事件(無 FD_ACCEPT)
    WSAAsyncSelect(sClient, g_hWnd, WM_NET_EVENT, 
                   FD_READ | FD_WRITE | FD_CLOSE);
    
    SaveClient(sClient, client); // 保存到客戶端列表
}

void OnReceive(SOCKET s) {
    char buf[4096];
    int n = recv(s, buf, sizeof(buf), 0);
    if (n > 0) {
        NetMsg msg = { s, vector(buf, buf+n) };
        PushToQueue(msg); // 放入線程安全隊列
        SetEvent(g_hDataEvent); // 喚醒工作線程
    }
}

void OnClose(SOCKET s) {
    RemoveClient(s); closesocket(s);
}

工作線程:業務邏輯處理


UINT WorkerThread(LPVOID) {
    HANDLE events[2] = { g_hDataEvent, g_hExitEvent };
    while (true) {
        DWORD ret = WaitForMultipleObjects(2, events, FALSE, INFINITE);
        if (ret == WAIT_OBJECT_0 + 1) break; // 退出信號
        
        NetMsg msg;
        while (PopFromQueue(msg)) {
            ProcessBusiness(msg); // 解析協議、操作數據庫、廣播等
        }
    }
    return 0;
}

客戶端:單線程簡化模型


BOOL ConnectServer(LPCTSTR ip, int port) {
    m_sock = socket(AF_INET, SOCK_STREAM, 0);
    WSAAsyncSelect(m_sock, m_hWnd, WM_NET_EVENT, 
                   FD_CONNECT | FD_READ | FD_WRITE | FD_CLOSE);
    
    sockaddr_in srv = { AF_INET, htons(port) };
    inet_pton(AF_INET, ip, &srv.sin_addr);
    connect(m_sock, (sockaddr*)&srv, sizeof(srv)); // 非阻塞,觸發 FD_CONNECT
    return TRUE;
}

void OnConnect() { SendLoginPacket(); } // 連接成功發送登入包
void OnReceive() { /* 同服務器 */ }
void OnClose() { /* 重連邏輯 */ }

進階擴展方向

  1. 協議設計:定長包頭 + 變長體,處理粘包/拆包。
  2. 心跳機制:定時發送 PING,檢測死連接。
  3. UDP 組播聊天室:局域網廣播發現 + 多點傳輸。
  4. IOCP 完成端口:高併發(萬級連接)的專業選擇。

推薦學習路徑

  1. 先跑通上述「最小可行性服務器/客戶端」
  2. 閱《Windows 核心編程》第 10 章、Unix Network Programming 卷 1
  3. 嘗試實現:文件傳輸、簡易 HTTP 服務器、RPC 框架

掌握異步 Socket 模型,是從「調用 API」進階為「設計網絡架構」的關鍵一步。

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