GetQueuedCompletionStatus如何无需自定义套接字结构体读取客户端消息
回答
可以实现,但仅能用于极简演示场景,没有实际生产价值。原因是IOCP完成通知需要携带IO操作对应的套接字、缓冲区地址、操作类型等上下文信息,没有自定义结构体的情况下只能用全局变量存储这些信息,无法支持多客户端、多类型IO操作的场景。
实现原理
不使用自定义结构体时,我们可以利用GetQueuedCompletionStatus的两个原生输出参数传递基础信息:
- 把客户端套接字直接作为完成键在绑定套接字到IOCP时传入,从
*lpCompletionKey参数直接拿到对应的客户端套接字 - 用全局的
OVERLAPPED结构和全局的接收缓冲区处理IO操作,不需要从OVERLAPPED扩展自定义数据
极简示例代码
#include <winsock2.h> #include <windows.h> #include <stdio.h> #pragma comment(lib, "ws2_32.lib") #define BUF_SIZE 1024 // 全局变量,仅用于演示,生产环境禁止这么用 SOCKET g_ClientSock = INVALID_SOCKET; OVERLAPPED g_Overlapped = {0}; char g_RecvBuf[BUF_SIZE] = {0}; HANDLE g_Iocp = NULL; // 工作线程 DWORD WINAPI WorkerThread(LPVOID lpParam) { DWORD bytesTransferred; ULONG_PTR completionKey; LPOVERLAPPED pOverlapped; while (1) { BOOL ret = GetQueuedCompletionStatus( g_Iocp, &bytesTransferred, &completionKey, &pOverlapped, INFINITE ); if (!ret || bytesTransferred == 0) { // 客户端断开或出错,演示直接退出 printf("客户端断开连接\n"); closesocket((SOCKET)completionKey); return 0; } // completionKey就是我们绑定的客户端套接字 SOCKET clientSock = (SOCKET)completionKey; g_RecvBuf[bytesTransferred] = '\0'; printf("收到客户端[%d]消息:%s\n", clientSock, g_RecvBuf); // 继续投递下一个接收请求 DWORD flags = 0; WSABUF wsaBuf = {BUF_SIZE, g_RecvBuf}; ZeroMemory(&g_Overlapped, sizeof(OVERLAPPED)); WSARecv(clientSock, &wsaBuf, 1, NULL, &flags, &g_Overlapped, NULL); } return 0; } int main() { // 初始化Winsock WSADATA wsaData; WSAStartup(MAKEWORD(2,2), &wsaData); // 创建监听套接字 SOCKET listenSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); SOCKADDR_IN addr = {0}; addr.sin_family = AF_INET; addr.sin_port = htons(12345); addr.sin_addr.s_addr = INADDR_ANY; bind(listenSock, (SOCKADDR*)&addr, sizeof(addr)); listen(listenSock, 10); // 创建IOCP g_Iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0); // 创建工作线程 CreateThread(NULL, 0, WorkerThread, NULL, 0, NULL); printf("服务端启动,监听端口12345...\n"); // 接受客户端连接,演示仅接受一个客户端 g_ClientSock = accept(listenSock, NULL, NULL); printf("新客户端连接:%d\n", g_ClientSock); // 把客户端套接字绑定到IOCP,完成键直接传套接字本身 CreateIoCompletionPort((HANDLE)g_ClientSock, g_Iocp, (ULONG_PTR)g_ClientSock, 0); // 投递第一个接收请求 DWORD flags = 0; WSABUF wsaBuf = {BUF_SIZE, g_RecvBuf}; WSARecv(g_ClientSock, &wsaBuf, 1, NULL, &flags, &g_Overlapped, NULL); // 阻塞主线程,演示用 getchar(); // 清理资源 CloseHandle(g_Iocp); closesocket(listenSock); WSACleanup(); return 0; }
注意事项
- 该示例仅用于验证逻辑,不能用于生产环境,全局变量会导致多客户端场景下数据覆盖
- 没有处理重叠操作的错误情况,也没有处理
GetQueuedCompletionStatus返回的各种异常状态 - 实际开发中还是建议使用自定义扩展
OVERLAPPED结构体的方式传递上下文,这是IOCP的标准实现方式
内容的提问来源于stack exchange,提问作者sakdkjkj jjsdjds
相关产品推荐
相关产品推荐

