IOCP结合WSASend使用时GetQueuedCompletionStatus返回998错误咨询
错误原因分析
- 直接触发998(ERROR_NOACCESS,无效内存访问)的问题出在工作线程的
GetQueuedCompletionStatus调用参数:
你定义的PULONG_PTR my_CompletionKey是未初始化的野指针,而GetQueuedCompletionStatus的第三个参数是输出参数,要求传入ULONG_PTR类型变量的地址,函数会把IO对应的完成键写入你提供的地址。你传入野指针后,函数会尝试往非法内存地址写数据,直接触发访问错误。 - 第二个严重内存问题:你传给
WSASend的WSAOVERLAPPED结构体是main函数栈上的局部变量。IOCP异步操作的生命周期和调用函数的栈生命周期不一致,内核在IO完成时会往你提交的OVERLAPPED结构写数据,如果栈内存已经被释放/覆盖,就会触发内存访问错误。所有异步IO用的OVERLAPPED必须是堆分配,在IO完成通知处理完之后再释放。 - 第三个内存问题:
WSABUF指向的std::string内部缓冲区也是栈上局部变量,异步发送未完成时如果string被销毁,缓冲区地址就会失效,内核发送数据时读取无效内存也会报错。 - 逻辑错误1:绑定套接字到完成端口时,第三个参数完成键传了NULL,后续从完成端口取出IO完成通知时,无法区分对应的是哪个套接字的操作,也无法关联套接字对应的上下文数据。
- 逻辑错误2:没有判断
WSAConnect的返回值,无法确认连接是否成功就直接调用WSASend。 - 逻辑错误3:仅提交了WSASend异步请求,没有提交WSARecv异步请求,无法收到服务器返回的响应数据。
- 逻辑错误4:工作线程中收到IO完成通知后没有做任何处理,也没有释放堆分配的OVERLAPPED等资源,会导致内存泄漏。
核心代码修正示例
首先修正工作线程的参数错误:
void My_func_for_Thread(HANDLE iocp) { DWORD transferedBytes; ULONG_PTR completionKey; // 改成普通变量,传地址进去 WSAOVERLAPPED* pOverlapped = NULL; while (1) { BOOL ret = GetQueuedCompletionStatus(iocp, &transferedBytes, &completionKey, &pOverlapped, INFINITE); if (ret == FALSE) { std::cout << "GetQueuedCompletionStatus错误,错误码:" << GetLastError() << std::endl; // 这里要处理错误,释放pOverlapped等资源 continue; } // 在这里处理IO完成逻辑:判断是发送完成还是接收完成 // 处理完后释放堆分配的pOverlapped、对应缓冲区 delete pOverlapped; } }
然后修正main函数中WSASend部分的OVERLAPPED和缓冲区分配:
// 发送请求部分改堆分配 WSAOVERLAPPED* pSendOverlapped = new WSAOVERLAPPED(); ZeroMemory(pSendOverlapped, sizeof(WSAOVERLAPPED)); // 缓冲区也要堆分配,保证在发送完成前不被销毁 std::string request_text_string = "GET / HTTP/1.1\r\nHost: government.ru\r\nConnection: keep-alive\r\n\r\n"; char* sendBuf = new char[request_text_string.size()]; memcpy(sendBuf, request_text_string.c_str(), request_text_string.size()); WSABUF my_WSABUF; my_WSABUF.buf = sendBuf; my_WSABUF.len = request_text_string.size(); int my_WSASend = WSASend(my_WSASocketA, &my_WSABUF, 1, NULL, 0, pSendOverlapped, NULL); if (my_WSASend == SOCKET_ERROR && WSAGetLastError() != WSA_IO_PENDING) { // 只有错误不是IO_PENDING才是真的失败,要释放资源 std::cout << "WSASend错误,错误码:" << WSAGetLastError() << std::endl; delete[] sendBuf; delete pSendOverlapped; }
绑定完成端口时可以把套接字作为完成键传入,方便后续处理:
My_handle_Create_IOCP = CreateIoCompletionPort((HANDLE)my_WSASocketA, My_handle_IOCP, (ULONG_PTR)my_WSASocketA, 0);
补充说明:发送完成通知触发后,需要主动提交WSARecv异步请求,才能接收服务器返回的响应数据。
内容的提问来源于stack exchange,提问作者Optimus1
相关产品推荐
相关产品推荐

