如何使用 IOCP 将用户定义的数据传递给工作线程?
Posted
技术标签:
【中文标题】如何使用 IOCP 将用户定义的数据传递给工作线程?【英文标题】:How to pass user-defined data to a worker thread using IOCP? 【发布时间】:2010-12-03 09:33:32 【问题描述】:嘿...我使用 I/O 完成端口和 winsock 创建了一个小型测试服务器。 我可以成功连接套接字句柄并将其与完成端口相关联。 但我不知道如何将用户定义的数据结构传递到工作线程中......
到目前为止,我尝试将用户结构作为(ULONG_PTR)&structure as
传递给CreateIoCompletionPort()
的关联调用中的完成键
但这不起作用。
现在我尝试定义自己的 OVERLAPPED 结构并使用 CONTAINING_RECORD(),如 http://msdn.microsoft.com/en-us/magazine/cc302334.aspx 和 http://msdn.microsoft.com/en-us/magazine/bb985148.aspx 所述。 但这也行不通。 (我得到 pHelper 内容的怪异值)
所以我的问题是:如何使用 WSARecv()、GetQueuedCompletionStatus() 和 Completion 数据包或 OVERLAPPED 结构将数据传递给工作线程?
编辑:我怎样才能成功传输“每个连接数据”?...看来我做这件事的艺术(就像上面两个链接中解释的那样)是错误的。
这是我的代码:(是的,它很丑,也是唯一的测试代码)
struct helper
SOCKET m_sock;
unsigned int m_key;
OVERLAPPED over;
;
///////
SOCKET newSock = INVALID_SOCKET;
WSABUF wsabuffer;
char cbuf[250];
wsabuffer.buf = cbuf;
wsabuffer.len = 250;
DWORD flags, bytesrecvd;
while(true)
newSock = accept(AcceptorSock, NULL, NULL);
if(newSock == INVALID_SOCKET)
ErrorAbort("could not accept a connection");
//associate socket with the CP
if(CreateIoCompletionPort((HANDLE)newSock, hCompletionPort, 3,0) != hCompletionPort)
ErrorAbort("Wrong port associated with the connection");
else
cout << "New Connection made and associated\n";
helper* pHelper = new helper;
pHelper->m_key = 3;
pHelper->m_sock = newSock;
memset(&(pHelper->over), 0, sizeof(OVERLAPPED));
flags = 0;
bytesrecvd = 0;
if(WSARecv(newSock, &wsabuffer, 1, NULL, &flags, (OVERLAPPED*)pHelper, NULL) != 0)
if(WSAGetLastError() != WSA_IO_PENDING)
ErrorAbort("WSARecv didnt work");
//Cleanup
CloseHandle(hCompletionPort);
cin.get();
return 0;
DWORD WINAPI ThreadProc(HANDLE h)
DWORD dwNumberOfBytes = 0;
OVERLAPPED* pOver = nullptr;
helper* pHelper = nullptr;
WSABUF RecvBuf;
char cBuffer[250];
RecvBuf.buf = cBuffer;
RecvBuf.len = 250;
DWORD dwRecvBytes = 0;
DWORD dwFlags = 0;
ULONG_PTR Key = 0;
GetQueuedCompletionStatus(h, &dwNumberOfBytes, &Key, &pOver, INFINITE);
//Extract helper
pHelper = (helper*)CONTAINING_RECORD(pOver, helper, over);
cout << "Received Overlapped item" << endl;
if(WSARecv(pHelper->m_sock, &RecvBuf, 1, &dwRecvBytes, &dwFlags, pOver, NULL) != 0)
cout << "Could not receive data\n";
else
cout << "Data Received: " << RecvBuf.buf << endl;
ExitThread(0);
【问题讨论】:
【参考方案1】:如果你像这样传递你的结构,它应该可以正常工作:
helper* pHelper = new helper;
CreateIoCompletionPort((HANDLE)newSock, hCompletionPort, (ULONG_PTR)pHelper,0);
...
helper* pHelper=NULL;
GetQueuedCompletionStatus(h, &dwNumberOfBytes, (PULONG_PTR)&pHelper, &pOver, INFINITE);
编辑以添加每个 IO 数据:
异步 api 经常被滥用的特性之一是它们不复制 OVERLAPPED 结构,它们只是使用提供的结构 - 因此从 GetQueuedCompletionStatus 返回的重叠结构指向最初提供的结构。所以:
struct helper
OVERLAPPED m_over;
SOCKET m_socket;
UINT m_key;
;
if(WSARecv(newSock, &wsabuffer, 1, NULL, &flags, &pHelper->m_over, NULL) != 0)
请再次注意,在您的原始示例中,您的投射错误。 (OVERLAPPED*)pHelper 传递了一个指向辅助结构的 START 的指针,但最后声明了 OVERLAPPED 部分。我将其更改为传递实际重叠部分的地址,这意味着代码在没有强制转换的情况下编译,这让我们知道我们正在做正确的事情。我还将重叠的结构移动为结构的第一个成员。
要捕获另一边的数据:
OVERLAPPED* pOver;
ULONG_PTR key;
if(GetQueuedCompletionStatus(h,&dw,&key,&pOver,INFINITE))
// c cast
helper* pConnData = (helper*)pOver;
在这方面,重叠结构是辅助结构的第一个成员尤为重要,因为这样可以很容易地从 API 给我们的 OVERLAPPED* 以及我们真正想要的辅助* 中回滚。
【讨论】:
哇...我已经尝试过了,但它没有工作 O_o...我想我搞砸了取消引用... 这种取消引用错误非常容易出错。强制我们一直强制转换的编译器无济于事。 呵呵...我一直是一个证明这一点的人^^...您知道如何将“per-I/O-call”数据与 OVERLAPPED 结构一起发送到例如,处理 WSARecv 的线程?【参考方案2】:您可以通过PostQueuedCompletionStatus将自己的专用数据发送到完成端口。
I/O 完成包将满足 对 GetQueuedCompletionStatus 函数。 这个函数返回三个 作为第二个,第三个传递的值, 和调用的第四个参数 PostQueuedCompletion 状态。系统 不使用或验证这些值。 特别是,lpOverlapped 参数不必指向 重叠的结构。
【讨论】:
嘿,谢谢你的帮助。你也可以帮我解决我的第二个问题吗?如何正确地通过 OVERLAPPED 结构发送此“每个连接数据”?...正如我的 OP 中所述,我没有让它与 CONTAINING_RECORD()-Macro 一起正常工作 为什么不将OVERLAPPED over;
成员移动到struct
的顶部,然后将指针投射到helper*
?
即使它有效,但对我来说这似乎是错误的 - 依赖于结构中结构的顺序 - ...
这是一个猜测,因为您在 MSDN 上提到的示例代码就是这样。所以这有效吗?
是的,它终于可以工作了......但是 - 对不起 - 但它很糟糕,MSDN 上提到的那些方法(不管它们有多老 - 宏仍然存在,所以我认为其余的应该也工作)根本不起作用^^【参考方案3】:
我使用标准套接字例程(socket、closesocket、bind、accept、connect ...)来创建/销毁,并为 I/O 使用 ReadFile/WriteFile,因为它们允许使用 OVERLAPPED 结构。
在您的套接字接受或连接后,您应该将它与它所服务的会话上下文相关联。然后将您的套接字关联到一个 IOCP 并(在第三个参数中)为它提供对会话上下文的引用。 IOCP 不知道这个引用是什么,也不关心这个问题。该引用供您使用,因此当您通过 GetQueuedCompletionStatus 获得 IOC 时,参数 3 指向的变量将填充该引用,以便您立即找到与套接字事件关联的上下文并开始为事件提供服务。我通常使用包含(除其他外)套接字声明、重叠结构以及其他特定于会话的数据的索引结构。我在参数 3 中传递给 CreateIoCompletionPort 的引用将是包含套接字的结构成员的索引。
您需要检查 GetQueuedCompletionStatus 是否返回完成或超时。使用超时,您可以遍历索引结构并查看(例如)其中一个是否超时或其他原因,并采取适当的内务处理措施。
还需要检查重叠结构以查看 I/O 是否正确完成。
服务于 IOCP 的函数应该是一个单独的多线程实体。使用与系统中的内核数相同的线程数,或者至少不超过这个数,因为它会浪费系统资源(您没有比系统中的内核数更多的资源来服务事件,对吧?) .
IOCP 确实是世界上最好的(好得令人难以置信),任何说“每个套接字一个线程”或“在一个函数中等待多个套接字列表”的人都不知道他们在说什么。前者强调你的调度程序,后者是轮询,轮询总是非常浪费。
【讨论】:
以上是关于如何使用 IOCP 将用户定义的数据传递给工作线程?的主要内容,如果未能解决你的问题,请参考以下文章
如何将表单 FormSeetController 中的数据传递给 ViewController?
如何将视图控制器中的数据传递给另一个视图控制器中的集合视图? [复制]