IOCP的内部原理以及关键参数和api

整体

主要看设计, 针对高并发场景应该如何优化我们的设计

内部实现 :

  • 维护两个队列, 提交的任务队列和处理完成的完成队列
  • 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 操作元数据
  • 内核与用户层数据交换桥梁
  • 每个操作需独立实例,防止缓冲区覆盖
生命周期
  1. 创建:提交异步操作前分配(建议对象池获取)
  2. 使用:通过 WSARecv/WSASend 提交内核
  3. 释放:完成事件处理后回收(示例错误码处理)

PerSocketData

struct PerSocketData {
    SOCKET socket;        // 必须字段,连接标识
    //用于处理业务逻辑,下面是示例添加
    uint64_t uid;         // 业务扩展字段:用户ID
    std::string client_ip;// 业务扩展字段:客户端IP
};
核心作用
  • 连接上下文,处理业务逻辑
  • 管理连接全生命周期数据
  • 通过 CreateIoCompletionPort 绑定到 Socket
  • 业务逻辑与网络层解耦关键载体
生命周期
  1. 创建:accept 接收连接时实例化
  2. 绑定:关联到 IOCP 完成端口
  3. 销毁:连接关闭事件触发释放

算法

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 表示阻塞)
);
工作流程
  1. 在创建工作线程池后,里面循环调用消费完成事件的队列
  2. 内核完成事件队列消费
  3. 关键数据提取:
PerSocketData* ctx = reinterpret_cast<PerSocketData*>(completionKey);
PerIoData* io_data = reinterpret_cast<PerIoData*>(overlapped);

时序图
主线程线程池内核IOCP任务队列完成队列客户端1. CreateIoCompletionPort (创建IOCP)2. 创建监听套接字并绑定端口3. 创建工作线程(线程池)4. GetQueuedCompletionStatus (等待完成事件)loop[工作线程循环]5. connect()6. accept() 接收新连接7. CreateIoCompletionPort (关联socket到IOCP)8. WSARecv (投递异步接收请求)8.1 监听任务队列9. 数据到达(网络层)10. 取出WSARecv请求11. 执行实际I/O(从网卡读数据到缓冲区)12. 放入完成事件(数据已就绪)13. GetQueuedCompletionStatus返回事件14. 处理接收数据15. WSASend (投递异步发送请求)15.1 监听任务队列16. 取出WSASend请求17. 执行实际I/O(从缓冲区写数据到网卡)18. 放入完成事件(发送完毕)19. GetQueuedCompletionStatus返回事件20. 清理资源21. 连接关闭22. 放入完成事件(0字节通知)23. GetQueuedCompletionStatus返回事件24. 关闭socket并释放资源主线程线程池内核IOCP任务队列完成队列客户端

对象池优化实现

模板化对象池

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使用率GetProcessTimes70%~85%
内存池命中率对象池统计> 90%
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值