如何使用 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?

如何将视图控制器中的数据传递给另一个视图控制器中的集合视图? [复制]

React 在模态中使用表单将输入的数据传递给函数

如何将片段中的 ListView 对象的数据传递给 Activity?

如何将一个组件中查询的数据传递给另一个组件以用作查询变量?

如何将更新的数据传递给已经渲染的子 vue 组件