【问题标题】:IO completion port key confusionIO完成端口key混淆
【发布时间】:2010-11-13 16:07:21
【问题描述】:

我正在使用 ctypes 模块在 Python 中使用 Windows DLL API 编写基于 IO 完成端口的服务器 (source code here)。但这是 API 的一个非常直接的用法,而且这个问题是针对那些了解 IOCP 而不是 Python 的人。

据我了解 CreateIoCompletionPort 的文档,当您使用与创建的 IOCP 关联的文件句柄(在我的情况下为套接字)调用此函数时,您指定了“用户定义的”完成键。当您开始调用 GetQueuedCompletionStatus 时,您将获得一个完成键值以及一个指向重叠对象的指针。完成键应标识已完成的重叠对象和请求。

但是,假设我在 CreateIoCompletionPort 调用中传入 100 作为完成键,并带有一个重叠的对象。当同一个重叠对象的 IO 完成并通过 GetQueuedCompletionStatus 返回时,伴随它的完成键要大得多,与原始值 100 没有任何相似之处。

我误解了完成键的工作原理,还是我在上面链接的源代码中做错了?

【问题讨论】:

    标签: python windows winsock ctypes iocp


    【解决方案1】:

    GetQueuedCompletionStatus 返回两个东西,一个OVERLAPPED 结构和一个完成键。完成键代表每个设备的信息,OVERLAPPED 结构代表每个调用的信息。完成键应与调用 CreateIoCompletionPort 时给出的匹配。通常,您会使用指向包含连接信息的结构的指针作为完成键。

    您似乎没有对GetQueuedCompletionStatus 返回的completionKey 执行任何操作。

    我猜你想要:

    if completionKey != acceptKey:
        Cleanup()
        ...
    

    编辑:

    Python 是否知道在 CreateAcceptSocket 中创建的 OVERLAPPED 结构正在被 Win32 API 异步使用并阻止它被 GC 处理?

    【讨论】:

    • "假设我在 CreateIoCompletionPort 调用中使用重叠对象传入 100 作为完成键。当同一个重叠对象的 IO 已完成并通过 GetQueuedCompletionStatus 返回时,完成键伴随它大得多,与原始值 100 没有任何相似之处。”从我的问题以及为什么我不在源代码中比较它。您可能对重叠的对象进行垃圾收集是正确的,但这与问题无关。
    【解决方案2】:

    问题在于我如何传递完成键。完成键参数是一个指针,但它传回的是指针而不是指向的值 - 至少让我有点困惑。

    此外,为接受的连接重叠数据包传递的完成密钥是侦听套接字的完成密钥,而不是接受的套接字。

    【讨论】:

      【解决方案3】:

      我在日常实践中发现,最好只关注OVERLAPPED 结果,因为它不会改变。您可以有效使用它的一种方法是使用以下内容:

      struct CompletionHandler
      {
          OVERLAPPED dummy_ovl;
          /* Stuff that actually means something to you here */
      };
      

      当您向 IOCP 发布内容(无论是通过 I/O 调用还是仅通过 Win32 API 发布)时,您首先创建一个 CompletionHandler 对象,用于跟踪调用,并转换该对象的地址到OVERLAPPED*

      CompletionHander my_handler;
      // Fill in whatever you need to in my_handler
      // Don't forget to keep the original my_handler!
      
      // I/O call goes here, and for OVERLAPPED* give: (OVERLAPPED*)&my_handler
      

      这样,当您获得OVERLAPPED 结果时,您所要做的就是将其转换回CompletionHandler,瞧!您拥有通话的原始上下文。

      OVERLAPPED* from_queued_completion_status;
      // Actually get a value into from_queued_completion_status
      
      CompletionHandler* handler_for_this_completion = (CompletionHandler*)from_queued_completion_status;
      // Have fun!
      

      有关真实环境的更多详细信息,请查看 Boost 的 ASIO for Windows 实现 (ver 1.42 header here)。有一些细节,例如验证您从 GetQueuedCompletionStatus 获得的 OVERLAPPED 指针,但同样,请参阅链接以了解实现的好方法。

      【讨论】:

        【解决方案4】:

        完成键不是指针 - 它是 ULONG_PTR 类型的数字,仅表示“指针大小的整数”,因此它在 x86 上是 32 位,在 x64 上是 64 位。类型名令人困惑,但是当 win32 类型名引用指针时,它们会通过在名称前面而不是末尾添加 P 来做到这一点。

        【讨论】:

          【解决方案5】:

          如果您的应用程序是多线程的,请确保您传递的 CompletionKey 是常量或指向堆而不是堆栈上的对象的指针值。在您将 100 作为常量传递的示例中,说任何更改都一定是错误的。但至于问题,可能是您在 CreateIoCompletionPort 中传递了一个套接字句柄,但没有在 GetQueuedCompletionStatus 中传递对句柄的引用以检索它。你可以这样做

          HANDLE socket;
          CreateIoCompletionPort((HANDLE)socket, existed_io_completion_port, (ULONG_PTR)socket, 0);
          /*some I/Os*/
          ...
          

          HANDLE socket;
          GetQueuedCompletionStatus(existed_io_completion_port, &io_bytes_done, (PULONG_PTR)&socket, &overlapped);
          

          并注意括号中的类型。

          【讨论】:

            【解决方案6】:

            您应该将完成键视为“每个连接”数据,将(扩展的)重叠结构视为“每个 i/o”操作。

            有些人对 BOTH 使用扩展重叠结构,并将他们需要的所有信息存储在扩展重叠结构中。我总是存储一个引用计数对象,它将我的套接字包装在完成键中,并将一个引用计数数据缓冲区作为扩展的重叠结构存储。有兴趣可以看some example IOCP code in C++ here

            实际上,完成键只是一个不透明的数据句柄,当套接字上发生完成时,I/O 完成系统将返回给您。

            【讨论】:

              猜你喜欢
              • 2014-02-08
              • 1970-01-01
              • 2021-08-05
              • 1970-01-01
              • 1970-01-01
              • 2021-08-30
              • 2014-08-15
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多