IO完成端口键混淆

6 投票
6 回答
3197 浏览
提问于 2025-04-15 13:07

我正在用Python的ctypes模块,利用Windows的DLL API写一个基于IO完成端口的服务器(源代码在这里)。不过,这个API的用法比较直接,我的问题主要是针对那些了解IOCP的人,而不是Python新手。

根据我对CreateIoCompletionPort文档的理解,当你用一个文件句柄(在我这里是一个套接字)调用这个函数时,你需要指定一个“用户定义”的完成键。然后,当你调用GetQueuedCompletionStatus时,会得到一个完成键值和一个指向重叠对象的指针。这个完成键应该能告诉你哪个重叠对象和请求已经完成。

但是,假设我在CreateIoCompletionPort调用中传入了100作为完成键,并且与一个重叠对象关联。当这个重叠对象的IO操作完成后,通过GetQueuedCompletionStatus返回时,伴随而来的完成键却是一个比100大得多的值,完全和最开始的100没有关系。

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

6 个回答

1

GetQueuedCompletionStatus这个函数会返回两个东西,一个是OVERLAPPED结构,另一个是完成键(completion key)。完成键是用来表示每个设备的信息,而OVERLAPPED结构则是用来表示每次调用的信息。完成键应该和你在调用CreateIoCompletionPort时提供的内容一致。通常,你会用一个指向包含连接信息的结构的指针作为完成键。

看起来你没有对GetQueuedCompletionStatus返回的completionKey做任何处理。

我猜你想要的是:

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

补充:

Python是否以某种方式知道在CreateAcceptSocket中创建的OVERLAPPED结构是被Win32 API异步使用的,并且防止它被垃圾回收(GC)掉?

2

你可以把完成键想象成是“每个连接”相关的数据,而扩展的重叠结构则是“每个输入输出”操作的数据。

有些人会使用一个扩展的重叠结构来同时处理这两种情况,把他们需要的所有信息都存储在这个扩展的重叠结构里。我一直在完成键中存储一个引用计数的对象,这个对象是用来包装我的套接字的,而在扩展的重叠结构中存储一个引用计数的数据缓冲区。如果你感兴趣的话,可以看看这里的一些C++示例IOCP代码

完成键实际上就是一个不透明的句柄,它指向一些数据,当套接字完成操作时,I/O完成系统会把这些数据返回给你。

4

我在日常实践中发现,最好的做法就是专注于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在Windows上实现的ASIO(这里是1.42版本的头文件)。还有一些细节,比如验证你从GetQueuedCompletionStatus得到的OVERLAPPED指针,不过,具体的实现方法可以参考那个链接。

撰写回答