本文展示一個基於 Windows Socket API (Winsock) 的阻塞式多線程服務器,支持多個客戶端並發連接。服務器採用「主線程監聽 + 每客戶端一線程接收」的經典模型,使用信號量保護共享數據結構,實現了基本的上線/下線廣播與消息收發功能。
WaitAcceptThread):持續調用 accept() 等待新連接,建立連接後創建接收線程。RecMsgThread):每客戶端一線程,循環 recv() 讀取數據,斷開時清理資源。clientSocketGroup (Socket 列表)、m_ipSocket (IP 映射),由 bufferMutex 信號量保護。#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);
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);
}
accept/recv 默認阻塞,簡化邏輯但單線程只能服務一客戶端,故需多線程。CreateSemaphore/WaitForSingleObject/ReleaseSemaphore) 保護 vector 和 map 的並發訪問。recv 返回 0 或 SOCKET_ERROR) 必須從容器移除、關閉 Socket、釋放映射表條目。htons/ntohs 轉換;IP 地址用 inet_addr/inet_ntop。MAKEWORD(2,2)) 以獲得完整功能。