在传统同步阻塞模型中,每个socket连接都需要独立的线程处理,当并发量上升时会产生:
通过单个线程管理多个socket连接,核心优势包括:
模型类型 | 工作原理 | 适用场景 |
---|---|---|
Select | 轮询检测就绪状态 | 中小型并发 |
WSAAsyncSelect | 窗口消息通知 | GUI应用程序 |
WSAEventSelect | 事件对象通知 | 服务端程序 |
完成端口(IOCP) | 异步I/O操作 | 高性能服务器 |
int select(
int nfds, // 忽略,仅为兼容性保留
fd_set* readfds, // 可读套接字集合
fd_set* writefds, // 可写套接字集合
fd_set* exceptfds, // 异常套接字集合
const timeval* timeout// 超时时间
);
#include
#include
#define MAX_CLIENTS 64
int main() {
WSADATA wsaData;
WSAStartup(MAKEWORD(2,2), &wsaData);
SOCKET listenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
sockaddr_in service;
service.sin_family = AF_INET;
service.sin_addr.s_addr = INADDR_ANY;
service.sin_port = htons(8888);
bind(listenSocket, (SOCKADDR*)&service, sizeof(service));
listen(listenSocket, SOMAXCONN);
fd_set readSet;
SOCKET clients[MAX_CLIENTS] = {0};
while(true) {
FD_ZERO(&readSet);
FD_SET(listenSocket, &readSet);
// 添加已连接客户端
for(int i=0; i<MAX_CLIENTS; i++){
if(clients[i] != 0)
FD_SET(clients[i], &readSet);
}
timeval timeout = {1, 0}; // 1秒超时
int result = select(0, &readSet, NULL, NULL, &timeout);
if(result > 0){
// 处理新连接
if(FD_ISSET(listenSocket, &readSet)){
SOCKET newClient = accept(listenSocket, NULL, NULL);
// 添加到客户端数组
for(int i=0; i<MAX_CLIENTS; i++){
if(clients[i] == 0){
clients[i] = newClient;
break;
}
}
}
// 处理客户端数据
for(int i=0; i<MAX_CLIENTS; i++){
if(clients[i] && FD_ISSET(clients[i], &readSet)){
char buffer[1024];
int recvSize = recv(clients[i], buffer, sizeof(buffer), 0);
if(recvSize <= 0){
closesocket(clients[i]);
clients[i] = 0;
} else {
// 处理接收数据
}
}
}
}
}
closesocket(listenSocket);
WSACleanup();
return 0;
}
优点:
缺点:
int WSAAsyncSelect(
SOCKET s, // 套接字句柄
HWND hWnd, // 窗口句柄
unsigned int wMsg, // 自定义消息
long lEvent // 事件组合
);
事件标志 | 说明 |
---|---|
FD_READ | 可读通知 |
FD_WRITE | 可写通知 |
FD_ACCEPT | 连接到达通知 |
FD_CONNECT | 连接完成通知 |
FD_CLOSE | 连接关闭通知 |
#include
#include
#define WM_SOCKET (WM_USER + 1)
#define PORT 8888
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
switch (message) {
case WM_SOCKET: {
SOCKET s = wParam;
int event = WSAGETSELECTEVENT(lParam);
int error = WSAGETSELECTERROR(lParam);
if (error != 0) {
closesocket(s);
return 0;
}
switch (event) {
case FD_ACCEPT: {
SOCKET newClient = accept(s, NULL, NULL);
WSAAsyncSelect(newClient, hWnd, WM_SOCKET, FD_READ | FD_CLOSE);
break;
}
case FD_READ: {
char buffer[1024];
int recvSize = recv(s, buffer, sizeof(buffer), 0);
if (recvSize > 0) {
// 处理接收数据
}
break;
}
case FD_CLOSE:
closesocket(s);
break;
}
return 0;
}
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hWnd, message, wParam, lParam);
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
// 初始化Winsock
WSADATA wsaData;
WSAStartup(MAKEWORD(2,2), &wsaData);
// 创建窗口
WNDCLASS wc = {0};
wc.lpfnWndProc = WndProc;
wc.hInstance = hInstance;
wc.lpszClassName = "AsyncSelectWindow";
RegisterClass(&wc);
HWND hWnd = CreateWindow("AsyncSelectWindow", "WSAAsyncSelect Demo",
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
300, 200, NULL, NULL, hInstance, NULL);
// 创建监听Socket
SOCKET listenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
sockaddr_in service = {0};
service.sin_family = AF_INET;
service.sin_addr.s_addr = INADDR_ANY;
service.sin_port = htons(PORT);
bind(listenSocket, (SOCKADDR*)&service, sizeof(service));
listen(listenSocket, SOMAXCONN);
// 注册网络事件
WSAAsyncSelect(listenSocket, hWnd, WM_SOCKET, FD_ACCEPT | FD_CLOSE);
// 消息循环
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
closesocket(listenSocket);
WSACleanup();
return msg.wParam;
}
消息参数结构:
事件注册机制:
// 注册多个事件类型
WSAAsyncSelect(socket, hWnd, WM_SOCKET, FD_READ | FD_WRITE | FD_CLOSE);
// 注销事件通知
WSAAsyncSelect(socket, hWnd, 0, 0);
资源管理规范:
// 创建事件对象
WSAEVENT WSACreateEvent();
// 绑定socket与事件
int WSAEventSelect(
SOCKET s,
WSAEVENT hEventObject,
long lNetworkEvents
);
// 等待事件
DWORD WSAWaitForMultipleEvents(
DWORD cEvents,
const WSAEVENT* lphEvents,
BOOL fWaitAll,
DWORD dwTimeout,
BOOL fAlertable
);
#include
#include
#define MAX_EVENTS 64
#define PORT 8888
int main() {
WSADATA wsaData;
WSAStartup(MAKEWORD(2,2), &wsaData);
// 创建监听socket
SOCKET listenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
sockaddr_in service = {0};
service.sin_family = AF_INET;
service.sin_addr.s_addr = INADDR_ANY;
service.sin_port = htons(PORT);
bind(listenSocket, (SOCKADDR*)&service, sizeof(service));
listen(listenSocket, SOMAXCONN);
// 创建事件对象数组
WSAEVENT events[MAX_EVENTS];
SOCKET sockets[MAX_EVENTS];
int numEvents = 0;
// 注册监听socket
events[numEvents] = WSACreateEvent();
WSAEventSelect(listenSocket, events[numEvents], FD_ACCEPT | FD_CLOSE);
sockets[numEvents] = listenSocket;
numEvents++;
while(true) {
// 等待事件
DWORD index = WSAWaitForMultipleEvents(
numEvents, events, FALSE, WSA_INFINITE, FALSE);
if(index == WSA_WAIT_FAILED) {
std::cerr << "Wait failed: " << WSAGetLastError() << std::endl;
break;
}
index -= WSA_WAIT_EVENT_0;
WSANETWORKEVENTS networkEvents;
WSAEnumNetworkEvents(sockets[index], events[index], &networkEvents);
// 处理事件
if(networkEvents.lNetworkEvents & FD_ACCEPT) {
if(networkEvents.iErrorCode[FD_ACCEPT_BIT] == 0) {
SOCKET newClient = accept(listenSocket, NULL, NULL);
if(numEvents < MAX_EVENTS) {
events[numEvents] = WSACreateEvent();
WSAEventSelect(newClient, events[numEvents], FD_READ | FD_CLOSE);
sockets[numEvents] = newClient;
numEvents++;
}
else {
closesocket(newClient);
}
}
}
if(networkEvents.lNetworkEvents & FD_READ) {
char buffer[1024];
int recvSize = recv(sockets[index], buffer, sizeof(buffer), 0);
if(recvSize <= 0) {
closesocket(sockets[index]);
WSACloseEvent(events[index]);
// 从数组中移除...
}
}
if(networkEvents.lNetworkEvents & FD_CLOSE) {
closesocket(sockets[index]);
WSACloseEvent(events[index]);
// 从数组中移除...
}
}
// 清理资源
for(int i=0; i<numEvents; i++) {
WSACloseEvent(events[i]);
closesocket(sockets[i]);
}
WSACleanup();
return 0;
}
// 创建完成端口
HANDLE CreateIoCompletionPort(
HANDLE FileHandle, // 初始端口填INVALID_HANDLE_VALUE
HANDLE ExistingCompletionPort,
ULONG_PTR CompletionKey,
DWORD NumberOfConcurrentThreads
);
// 获取完成状态
BOOL GetQueuedCompletionStatus(
HANDLE CompletionPort,
LPDWORD lpNumberOfBytes,
PULONG_PTR lpCompletionKey,
LPOVERLAPPED* lpOverlapped,
DWORD dwMilliseconds
);
// 典型工作线程结构
DWORD WINAPI WorkerThread(LPVOID lpParam) {
HANDLE hIOCP = (HANDLE)lpParam;
DWORD bytesTransferred;
ULONG_PTR completionKey;
LPOVERLAPPED overlapped;
while(true) {
BOOL result = GetQueuedCompletionStatus(
hIOCP, &bytesTransferred,
&completionKey, &overlapped, INFINITE);
// 处理I/O结果
if(result) {
// 成功处理数据
}
else {
// 处理错误
}
}
return 0;
}
指标 | Select | WSAAsyncSelect | WSAEventSelect | IOCP |
---|---|---|---|---|
最大并发连接 | 64 | 1000+ | 1000+ | 10000+ |
CPU利用率 | 低-中 | 中 | 中-高 | 高 |
延迟 | 高 | 中 | 中 | 低 |
开发复杂度 | ★☆☆☆☆ | ★★★☆☆ | ★★★★☆ | ★★★★★ |
线程资源消耗 | 单线程 | 单线程 | 多线程 | 线程池 |
Select模型:
WSAAsyncSelect:
WSAEventSelect:
IOCP模型:
设计支持以下特性的TCP服务器:
// IOCP服务器框架
#include
#include
#include
#define WORKER_THREADS 4
#define DATA_BUFSIZE 8192
struct PerIoData {
OVERLAPPED overlapped;
WSABUF wsaBuf;
char buffer[DATA_BUFSIZE];
DWORD bytesTransferred;
SOCKET socket;
};
int main() {
// 初始化Winsock
WSADATA wsaData;
WSAStartup(MAKEWORD(2,2), &wsaData);
// 创建完成端口
HANDLE hIOCP = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
// 创建工作线程
std::vector<HANDLE> threads;
for(int i=0; i<WORKER_THREADS; ++i) {
threads.push_back(CreateThread(NULL, 0, WorkerThread, hIOCP, 0, NULL));
}
// 创建监听socket
SOCKET listenSocket = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);
sockaddr_in service = {0};
service.sin_family = AF_INET;
service.sin_addr.s_addr = INADDR_ANY;
service.sin_port = htons(8888);
bind(listenSocket, (SOCKADDR*)&service, sizeof(service));
listen(listenSocket, SOMAXCONN);
// 关联监听socket到IOCP
CreateIoCompletionPort((HANDLE)listenSocket, hIOCP, 0, 0);
// 接收循环
while(true) {
SOCKET newClient = accept(listenSocket, NULL, NULL);
CreateIoCompletionPort((HANDLE)newClient, hIOCP, 0, 0);
// 投递初始读取操作
PerIoData* perIoData = new PerIoData;
ZeroMemory(&perIoData->overlapped, sizeof(OVERLAPPED));
perIoData->socket = newClient;
perIoData->wsaBuf.len = DATA_BUFSIZE;
perIoData->wsaBuf.buf = perIoData->buffer;
DWORD flags = 0;
WSARecv(newClient, &perIoData->wsaBuf, 1, NULL, &flags, &perIoData->overlapped, NULL);
}
// 清理资源
closesocket(listenSocket);
for(auto& t : threads) {
TerminateThread(t, 0);
CloseHandle(t);
}
WSACleanup();
return 0;
}
DWORD WINAPI WorkerThread(LPVOID lpParam) {
HANDLE hIOCP = (HANDLE)lpParam;
DWORD bytesTransferred;
ULONG_PTR completionKey;
LPOVERLAPPED overlapped;
while(true) {
BOOL result = GetQueuedCompletionStatus(
hIOCP, &bytesTransferred,
&completionKey, &overlapped, INFINITE);
PerIoData* perIoData = CONTAINING_RECORD(overlapped, PerIoData, overlapped);
if(!result || bytesTransferred == 0) {
closesocket(perIoData->socket);
delete perIoData;
continue;
}
// 处理接收到的数据
ProcessData(perIoData->buffer, bytesTransferred);
// 继续投递读取操作
DWORD flags = 0;
WSARecv(perIoData->socket, &perIoData->wsaBuf, 1, NULL, &flags, &perIoData->overlapped, NULL);
}
return 0;
}
内存池管理:
class MemoryPool {
public:
PerIoData* Alloc() {
if(pool.empty()) return new PerIoData;
auto ptr = pool.back();
pool.pop_back();
return ptr;
}
void Free(PerIoData* ptr) {
pool.push_back(ptr);
}
private:
std::vector<PerIoData*> pool;
};
线程亲和性设置:
SetThreadAffinityMask(threads[i], 1 << (i % 8)); // 绑定到不同CPU核心
零拷贝技术:
TransmitFile(socket, hFile, 0, 0, NULL, NULL, TF_DISCONNECT);
现象 | 可能原因 | 解决方案 |
---|---|---|
连接数达到64后失败 | Select模型默认限制 | 改用WSAEventSelect/IOCP |
客户端接收数据不完整 | TCP粘包问题 | 添加包头长度字段 |
服务器CPU占用100% | 忙等待循环 | 增加适当的Sleep间隔 |
WSAENOBUFS错误 | 非分页内存池耗尽 | 使用SO_RCVBUF调节缓冲区 |
连接随机断开 | 心跳机制缺失 | 添加应用层心跳包 |
网络状态监控:
netstat -ano | findstr :8888
性能计数器分析:
Wireshark抓包分析:
tcp.port == 8888 && tcp.flags.syn == 1
void PrintResourceStatus() {
MEMORYSTATUSEX memInfo;
memInfo.dwLength = sizeof(memInfo);
GlobalMemoryStatusEx(&memInfo);
std::cout << "Memory in use: "
<< (memInfo.ullTotalPhys - memInfo.ullAvailPhys)/1024/1024
<< " MB" << std::endl;
}
发展时期 | 技术特征 | 代表模型 |
---|---|---|
早期阶段 | 同步阻塞 | 基本Socket API |
中期发展 | 事件驱动 | WSAEventSelect |
现代方案 | 异步I/O+线程池 | IOCP |
未来趋势 | 用户态协议栈+RDMA | DPDK/RSocket |
HTTP/3支持:
// 使用MsQuic库
QUIC_API_TABLE* ApiTable;
QUIC_SEC_CONFIG* SecurityConfig;
QuicApiTableOpen(&ApiTable);
RSS(接收侧扩展):
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\TCPIP\Parameters]
"EnableRSS"=dword:00000001
内核旁路技术:
希望本文能对你有所帮助!