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

C++ Socket 實戰:阻塞式多客戶端服務器架構實現

     編輯:關於C++

本文展示一個基於 Windows Socket API (Winsock) 的阻塞式多線程服務器,支持多個客戶端並發連接。服務器採用「主線程監聽 + 每客戶端一線程接收」的經典模型,使用信號量保護共享數據結構,實現了基本的上線/下線廣播與消息收發功能。

架構設計

  • 監聽線程 (WaitAcceptThread):持續調用 accept() 等待新連接,建立連接後創建接收線程。
  • 接收線程 (RecMsgThread):每客戶端一線程,循環 recv() 讀取數據,斷開時清理資源。
  • 共享數據:clientSocketGroup (Socket 列表)、m_ipSocket (IP 映射),由 bufferMutex 信號量保護。

服務器核心代碼

ServerNet.h - 類定義

#pragma once
#include <winsock2.h>
#include <vector>
#include <map>
#include <string>

#pragma comment(lib, "ws2_32.lib")

class CServerNet {
private:
    SOCKET m_sock;
public:
    CServerNet();
    ~CServerNet();
    // 初始化服務器,返回 0 表示成功
    int Init(const char* address, int port);
    // 運行主循環
    void Run();
};

// 全局共享資源
extern SOCKET sockConn;
extern HANDLE bufferMutex;
extern std::vector<SOCKET> clientSocketGroup;
extern std::map<SOCKET, std::string> m_ipSocket;
extern DWORD WINAPI WaitAcceptThread(LPVOID);
extern DWORD WINAPI RecMsgThread(LPVOID);

ServerNet.cpp - 關鍵實現

int CServerNet::Init(const char* address, int port) {
    WSADATA wsaData;
    if (WSAStartup(MAKEWORD(2,2), &wsaData) != 0) return 1; // 建議使用 2.2 版本

    m_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (m_sock == INVALID_SOCKET) return 2;

    sockaddr_in serverAddr{};
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(port);           // 注意網絡字節序
    serverAddr.sin_addr.s_addr = inet_addr(address);

    if (bind(m_sock, (sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR)
        return 3;

    return 0;
}

void CServerNet::Run() {
    listen(m_sock, SOMAXCONN);
    bufferMutex = CreateSemaphore(NULL, 1, 1, NULL);

    HANDLE acceptThread = CreateThread(NULL, 0, WaitAcceptThread, (LPVOID)m_sock, 0, NULL);
    WaitForSingleObject(acceptThread, INFINITE); // 等待監聽線程結束(實際不會結束)

    CloseHandle(bufferMutex);
    WSACleanup();
    closesocket(m_sock);
}

// 監聽線程:接受連接並創建接收線程
DWORD WINAPI WaitAcceptThread(LPVOID IpParameter) {
    SOCKET listenSock = (SOCKET)IpParameter;
    while (true) {
        sockaddr_in clientAddr{};
        int len = sizeof(clientAddr);
        SOCKET connSock = accept(listenSock, (sockaddr*)&clientAddr, &len);
        if (connSock == INVALID_SOCKET) continue;

        char ipStr[INET_ADDRSTRLEN];
        inet_ntop(AF_INET, &clientAddr.sin_addr, ipStr, INET_ADDRSTRLEN);
        int port = ntohs(clientAddr.sin_port);
        printf("%s:%d 上線\n", ipStr, port);

        WaitForSingleObject(bufferMutex, INFINITE);
        clientSocketGroup.push_back(connSock);
        m_ipSocket[connSock] = std::string(ipStr) + ":" + std::to_string(port);
        ReleaseSemaphore(bufferMutex, 1, NULL);

        CreateThread(NULL, 0, RecMsgThread, (LPVOID)connSock, 0, NULL);
    }
    return 0;
}

// 接收線程:讀取數據,斷開則清理
DWORD WINAPI RecMsgThread(LPVOID IpParameter) {
    SOCKET clientSock = (SOCKET)IpParameter;
    char recvBuf[1024];
    while (true) {
        int rval = recv(clientSock, recvBuf, sizeof(recvBuf), 0);
        WaitForSingleObject(bufferMutex, INFINITE);

        if (rval <= 0) { // 斷開或錯誤
            printf("客戶端 %s 下線\n", m_ipSocket[clientSock].c_str());
            // 從容器移除
            auto it = std::find(clientSocketGroup.begin(), clientSocketGroup.end(), clientSock);
            if (it != clientSocketGroup.end()) clientSocketGroup.erase(it);
            m_ipSocket.erase(clientSock);
            closesocket(clientSock);
            ReleaseSemaphore(bufferMutex, 1, NULL);
            break;
        }

        recvBuf[rval] = '\0'; // 確保字符串結束
        printf("%s 說: %s\n", m_ipSocket[clientSock].c_str(), recvBuf);
        ReleaseSemaphore(bufferMutex, 1, NULL);
    }
    return 0;
}

客戶端實現

客戶端極簡,僅封裝連接、發送、關閉:

// ClientNet.h
class CClientNet {
    SOCKET m_sock;
public:
    CClientNet();
    ~CClientNet();
    int Connect(int port, const char* address);
    int SendMsg(const char* msg, int len);
    void Close();
};

// ClientNet.cpp
int CClientNet::Connect(int port, const char* address) {
    WSADATA wsaData;
    if (WSAStartup(MAKEWORD(2,2), &wsaData) != 0) return -1;
    m_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (m_sock == INVALID_SOCKET) return -2;

    sockaddr_in serverAddr{};
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(port);
    serverAddr.sin_addr.s_addr = inet_addr(address);

    return connect(m_sock, (sockaddr*)&serverAddr, sizeof(serverAddr));
}

int CClientNet::SendMsg(const char* msg, int len) {
    return send(m_sock, msg, len, 0);
}

關鍵技術點總結

  1. 阻塞模式:accept/recv 默認阻塞,簡化邏輯但單線程只能服務一客戶端,故需多線程。
  2. 線程同步:使用信號量 (CreateSemaphore/WaitForSingleObject/ReleaseSemaphore) 保護 vector 和 map 的並發訪問。
  3. 資源清理:客戶端斷開時 (recv 返回 0 或 SOCKET_ERROR) 必須從容器移除、關閉 Socket、釋放映射表條目。
  4. 網絡字節序:端口號必須用 htons/ntohs 轉換;IP 地址用 inet_addr/inet_ntop。
  5. Winsock 版本:建議請求 2.2 版本 (MAKEWORD(2,2)) 以獲得完整功能。

擴展方向

  • 引入 IOCP (I/O Completion Ports) 實現高性能非阻塞模型。
  • 實現消息幀協議(長度前綴/分隔符)解決 TCP 粘包問題。
  • 添加心跳機制檢測僵死連接。
  • 使用線程池避免頻繁創建/銷毀線程開銷。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved