【C++网络编程】第4篇:异步IO与事件驱动模型(select()与IOCP)
异步IO与事件驱动模型(select()与IOCP)
·
一、多线程模型的瓶颈
1. 线程资源消耗
- 内存开销:每个线程默认占用1MB栈空间(Windows),数千连接时内存压力大。
- 切换开销:频繁线程上下文切换(Context Switch)导致CPU利用率下降。
2. 并发限制
- 硬性上限:即使使用线程池,万级并发仍难以实现(如实时游戏服务器)。
二、异步IO模型概述
1. 同步 vs 异步
- 同步IO:send()/recv()阻塞当前线程,直到操作完成。
- 异步IO:操作提交后立即返回,通过回调或事件通知处理结果。
2. 事件驱动模型
- 核心思想:单线程(或少量线程)通过事件循环处理多个连接。
- 实现方式:
a) select():跨平台,但性能较低(O(n)遍历)。
b) IOCP(完成端口):Windows专属,高性能(基于内核队列)。
c) epoll(Linux)/ kqueue(macOS):类Unix系统的高效方案。
三、使用 select() 实现事件驱动服务器
1. select() 原理
- 功能:监视一组
Socket的可读/可写/错误状态。 - 流程:
1. 创建Socket并设为非阻塞模式。
2. 将Socket加入fd_set集合。
3. 调用select()等待事件。
4. 遍历集合处理就绪的Socket。
2. 完整代码示例
#include <iostream>
#include <winsock2.h>
#include <vector>
#pragma comment(lib, "ws2_32.lib")
int main() {
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
SOCKET serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
sockaddr_in serverAddr{};
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
serverAddr.sin_port = htons(8080);
bind(serverSocket, (sockaddr*)&serverAddr, sizeof(serverAddr));
listen(serverSocket, SOMAXCONN);
// 设置非阻塞模式
u_long mode = 1;
ioctlsocket(serverSocket, FIONBIO, &mode);
std::vector<SOCKET> clients;
fd_set readSet;
std::cout << "select() server running..." << std::endl;
while (true) {
FD_ZERO(&readSet);
FD_SET(serverSocket, &readSet);
for (SOCKET s : clients) FD_SET(s, &readSet);
// 等待事件(设置超时为1秒)
timeval timeout{ 1, 0 };
int count = select(0, &readSet, nullptr, nullptr, &timeout);
if (count == SOCKET_ERROR) {
std::cerr << "select failed: " << WSAGetLastError() << std::endl;
break;
}
if (count == 0) continue; // 超时
// 处理新连接
if (FD_ISSET(serverSocket, &readSet)) {
sockaddr_in clientAddr{};
int addrLen = sizeof(clientAddr);
SOCKET clientSocket = accept(serverSocket, (sockaddr*)&clientAddr, &addrLen);
if (clientSocket != INVALID_SOCKET) {
ioctlsocket(clientSocket, FIONBIO, &mode); // 非阻塞
clients.push_back(clientSocket);
std::cout << "New client connected: " << clients.size() << std::endl;
}
}
// 处理客户端数据
for (auto it = clients.begin(); it != clients.end();) {
SOCKET s = *it;
if (FD_ISSET(s, &readSet)) {
char buffer[1024];
int bytesRead = recv(s, buffer, sizeof(buffer), 0);
if (bytesRead > 0) {
std::cout << " received " << bytesRead << " bytes." << std::endl;
send(s, buffer, bytesRead, 0); // Echo回传
}
else {
closesocket(s);
it = clients.erase(it);
std::cout << "Client disconnected. Remaining: " << clients.size() << std::endl;
continue;
}
}
++it;
}
}
closesocket(serverSocket);
WSACleanup();
return 0;
}
3. 测试结果

四、Windows完成端口(IOCP)
1. IOCP核心机制
- 完成队列:内核维护操作队列,线程通过
GetQueuedCompletionStatus获取完成事件。 - 线程池:多个工作线程并行处理事件,无需频繁切换上下文。
2. IOCP服务器框架
#include <iostream>
#include <winsock2.h>
#include <windows.h>
#include <vector>
#pragma comment(lib, "ws2_32.lib")
#define MAX_THREADS 4
// I/O操作上下文结构
struct PerIoData {
WSAOVERLAPPED overlapped;
SOCKET socket;
WSABUF wsaBuf;
char buffer[1024];
bool isSend;
};
// 单例IOCP管理器
class IOCPManager {
public:
static HANDLE GetInstance() {
static HANDLE hCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
return hCompletionPort;
}
};
DWORD WINAPI WorkerThread(LPVOID lpParam) {
HANDLE hCompletionPort = IOCPManager::GetInstance();
DWORD bytesTransferred;
ULONG_PTR completionKey;
LPOVERLAPPED overlapped;
while (true) {
BOOL result = GetQueuedCompletionStatus(
hCompletionPort, &bytesTransferred, &completionKey, &overlapped, INFINITE);
SOCKET clientSocket = (SOCKET)completionKey;
if (!result || (bytesTransferred == 0 && overlapped == NULL)) {
std::cerr << "Client disconnected." << std::endl;
closesocket(clientSocket);
continue;
}
if (overlapped) {
PerIoData* ioData = CONTAINING_RECORD(overlapped, PerIoData, overlapped);
if (ioData->isSend) {
// 发送完成,释放资源
delete ioData;
continue;
}
// 打印接收数据
std::cout << "Received from client (" << bytesTransferred << " bytes): ";
std::cout.write(ioData->buffer, bytesTransferred);
std::cout << std::endl;
// 准备回显数据
PerIoData* sendData = new PerIoData;
ZeroMemory(&sendData->overlapped, sizeof(WSAOVERLAPPED));
sendData->socket = clientSocket;
sendData->wsaBuf.buf = ioData->buffer;
sendData->wsaBuf.len = bytesTransferred;
sendData->isSend = true;
// 异步发送
if (WSASend(clientSocket, &sendData->wsaBuf, 1, NULL, 0, &sendData->overlapped, NULL) == SOCKET_ERROR) {
if (WSAGetLastError() != WSA_IO_PENDING) {
std::cerr << "WSASend failed: " << WSAGetLastError() << std::endl;
delete sendData;
}
}
// 重新投递接收请求
PerIoData* newRecv = new PerIoData;
ZeroMemory(&newRecv->overlapped, sizeof(WSAOVERLAPPED));
newRecv->socket = clientSocket;
newRecv->wsaBuf.buf = newRecv->buffer;
newRecv->wsaBuf.len = sizeof(newRecv->buffer);
newRecv->isSend = false;
DWORD flags = 0;
if (WSARecv(clientSocket, &newRecv->wsaBuf, 1, NULL, &flags, &newRecv->overlapped, NULL) == SOCKET_ERROR) {
if (WSAGetLastError() != WSA_IO_PENDING) {
std::cerr << "WSARecv failed: " << WSAGetLastError() << std::endl;
delete newRecv;
}
}
delete ioData;
}
}
return 0;
}
int main() {
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
// 创建工作线程
std::vector<HANDLE> threads;
for (int i = 0; i < MAX_THREADS; ++i) {
threads.push_back(CreateThread(NULL, 0, WorkerThread, NULL, 0, NULL));
}
// 创建服务器Socket
SOCKET serverSocket = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
sockaddr_in serverAddr{};
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
serverAddr.sin_port = htons(8080);
bind(serverSocket, (sockaddr*)&serverAddr, sizeof(serverAddr));
listen(serverSocket, SOMAXCONN);
// 关联完成端口
HANDLE hCompletionPort = IOCPManager::GetInstance();
CreateIoCompletionPort((HANDLE)serverSocket, hCompletionPort, (ULONG_PTR)serverSocket, 0);
std::cout << "Server started on port 8080..." << std::endl;
while (true) {
SOCKET clientSocket = accept(serverSocket, NULL, NULL);
CreateIoCompletionPort((HANDLE)clientSocket, hCompletionPort, (ULONG_PTR)clientSocket, 0);
// 初始化接收请求
PerIoData* recvData = new PerIoData;
ZeroMemory(&recvData->overlapped, sizeof(WSAOVERLAPPED));
recvData->socket = clientSocket;
recvData->wsaBuf.buf = recvData->buffer;
recvData->wsaBuf.len = sizeof(recvData->buffer);
recvData->isSend = false;
DWORD flags = 0;
// 投递异步接收
if (WSARecv(clientSocket, &recvData->wsaBuf, 1, NULL, &flags, &recvData->overlapped, NULL) == SOCKET_ERROR) {
if (WSAGetLastError() != WSA_IO_PENDING) {
std::cerr << "WSARecv failed: " << WSAGetLastError() << std::endl;
delete recvData;
}
}
}
closesocket(serverSocket);
WSACleanup();
return 0;
}
3. 测试结果

五、关键对比与适用场景
| 模型 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 多线程 | 逻辑简单,易于调试 | 资源消耗大,并发上限低 | 低频短连接(如HTTP API) |
| select() | 跨平台,代码简单 | 性能差(O(n)遍历),仅支持1024连接 | 低并发测试 |
| IOCP | 高性能,支持超大规模并发 | Windows专属,代码复杂 | 实时游戏、高频交易 |
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐



所有评论(0)