整体
主要看设计, 针对高并发场景应该如何优化我们的设计
内部实现 :
- 维护两个队列, 提交的任务队列和处理完成的完成队列
- IOCP不断从提交队列里面异步安全的拿任务,处理完之后放到完成队列
对外接口 :
- 通过PerIoData和PerSocketData来完成数据的传输
- 通过CreateIoCompletionPort来监听客户端socket
- 通过WSASend/WSARecv向任务队列提交任务,需要socket参数
- 通过GetQueuedCompletionStatus来获取完成队列里面的数据
数据结构
PerIoData
struct PerIoData {
OVERLAPPED overlapped; // 必须为首成员,内核操作标识
WSABUF wsaBuf; // 数据缓冲区描述 {buf, len}
char buffer[BUFFER_SIZE]; // 数据存储区
DWORD bytesTransferred; // 实际传输字节数(内核填充)
DWORD flags; // 状态标志(如 MSG_PARTIAL)
};
核心作用
- IO上下文
- 承载单次异步 I/O 操作元数据
- 内核与用户层数据交换桥梁
- 每个操作需独立实例,防止缓冲区覆盖
生命周期
- 创建:提交异步操作前分配(建议对象池获取)
- 使用:通过
WSARecv
/WSASend
提交内核 - 释放:完成事件处理后回收(示例错误码处理)
PerSocketData
struct PerSocketData {
SOCKET socket; // 必须字段,连接标识
//用于处理业务逻辑,下面是示例添加
uint64_t uid; // 业务扩展字段:用户ID
std::string client_ip;// 业务扩展字段:客户端IP
};
核心作用
- 连接上下文,处理业务逻辑
- 管理连接全生命周期数据
- 通过
CreateIoCompletionPort
绑定到 Socket - 业务逻辑与网络层解耦关键载体
生命周期
- 创建:
accept
接收连接时实例化 - 绑定:关联到 IOCP 完成端口
- 销毁:连接关闭事件触发释放
算法
CreateIoCompletionPort
HANDLE CreateIoCompletionPort(
HANDLE FileHandle, // 要绑定的 Socket
HANDLE ExistingCompletionPort, // 已存在的 IOCP 句柄
ULONG_PTR CompletionKey, // 上下文指针(PerSocketData*)
DWORD NumberOfConcurrentThreads // 内核并发线程数(通常设为0,即默认为核心数)
);
核心作用
- 创建/关联 IOCP 实例
- 绑定 Socket 与业务上下文
- 参数四的特殊性:控制内核并行处理线程数(非工作线程总数)
典型调用场景
// 首次创建 IOCP
HANDLE iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
// 绑定客户端 Socket
CreateIoCompletionPort((HANDLE)socket, iocp, (ULONG_PTR)perSocketData, 0);
WSARecv/WSASend
int WSARecv(
SOCKET s, // 目标 Socket
LPWSABUF lpBuffers, // 指向 PerIoData.wsaBuf
DWORD dwBufferCount, // 缓冲区数量(通常为1)
LPDWORD lpNumberOfBytesRecvd, // 可选接收字节数
LPDWORD lpFlags, // 指向 PerIoData.flags
LPOVERLAPPED lpOverlapped, // 指向 PerIoData.overlapped
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine // NULL
);
int WSASend(...); // 参数结构与 WSARecv 类似
使用要点
作用 :
非阻塞立即返回, 将任务提交到队列中,实际操作由内核异步执行
返回值处理:
if (result == SOCKET_ERROR) {
DWORD err = WSAGetLastError();
if (err != WSA_IO_PENDING) { // 非预期错误
HandleError(err);
}
}
大文件分片处理:
if (io_data->flags & MSG_PARTIAL) {
// 处理不完整数据包,继续提交接收
PostRecv(ctx);
}
GetQueuedCompletionStatus
BOOL GetQueuedCompletionStatus(
HANDLE CompletionPort, // IOCP 句柄
LPDWORD lpNumberOfBytes, // 传输字节数
PULONG_PTR lpCompletionKey, // 返回 PerSocketData*
LPOVERLAPPED* lpOverlapped, // 返回 PerIoData.overlapped
DWORD dwMilliseconds // 超时时间(INFINITE 表示阻塞)
);
工作流程
- 在创建工作线程池后,里面循环调用消费完成事件的队列
- 内核完成事件队列消费
- 关键数据提取:
PerSocketData* ctx = reinterpret_cast<PerSocketData*>(completionKey);
PerIoData* io_data = reinterpret_cast<PerIoData*>(overlapped);
时序图
对象池优化实现
模板化对象池
template <typename T>
class ObjectPool {
std::vector<T*> pool_;
std::mutex mutex_;
public:
T* Alloc() {
std::lock_guard lock(mutex_);
if (pool_.empty()) return new T();
auto obj = pool_.back();
pool_.pop_back();
return obj;
}
void Free(T* obj) {
std::lock_guard lock(mutex_);
if constexpr (std::is_same_v<T, PerIoData>) {
ZeroMemory(&obj->overlapped, sizeof(OVERLAPPED));
}
pool_.push_back(obj);
}
};
全局池实例
ObjectPool<PerIoData> g_io_pool; // I/O 操作池
ObjectPool<PerSocketData> g_conn_pool; // 连接池
智能指针集成
// 安全获取实例
auto io_data = std::unique_ptr<PerIoData>(
g_io_pool.Alloc(),
[&](PerIoData* p) { g_io_pool.Free(p); }
);
// 连接建立时
auto ctx = std::shared_ptr<PerSocketData>(
g_conn_pool.Alloc(),
[&](PerSocketData* p) {
closesocket(p->socket);
g_conn_pool.Free(p);
}
);
高级优化策略
动态线程池
// 根据负载动态调整工作线程数
void AdjustThreadPool(HANDLE iocp) {
ULONG active_threads = 0;
GetQueuedCompletionStatusEx(iocp, nullptr, 0, &active_threads, 0, TRUE);
if (active_threads > current_threads * 2) {
AddWorkerThread(iocp);
} else if (active_threads < current_threads / 2) {
RemoveWorkerThread();
}
}
跨平台设计
// 抽象 I/O 接口
class IIOHandler {
public:
virtual void OnData(Connection* conn, const char* data, size_t len) = 0;
virtual void OnDisconnect(Connection* conn) = 0;
};
// Windows 实现
class WinIOCPHandler : public IIOHandler {
// 封装 IOCP 相关操作
};
性能监控指标
指标 | 监控方法 | 健康阈值 |
---|---|---|
完成端口队列深度 | GetQueuedCompletionStatusEx | < CPU核心数*2 |
工作线程CPU使用率 | GetProcessTimes | 70%~85% |
内存池命中率 | 对象池统计 | > 90% |