IO完成端口键混淆
我正在用Python的ctypes模块,利用Windows的DLL API写一个基于IO完成端口的服务器(源代码在这里)。不过,这个API的用法比较直接,我的问题主要是针对那些了解IOCP的人,而不是Python新手。
根据我对CreateIoCompletionPort文档的理解,当你用一个文件句柄(在我这里是一个套接字)调用这个函数时,你需要指定一个“用户定义”的完成键。然后,当你调用GetQueuedCompletionStatus时,会得到一个完成键值和一个指向重叠对象的指针。这个完成键应该能告诉你哪个重叠对象和请求已经完成。
但是,假设我在CreateIoCompletionPort调用中传入了100作为完成键,并且与一个重叠对象关联。当这个重叠对象的IO操作完成后,通过GetQueuedCompletionStatus返回时,伴随而来的完成键却是一个比100大得多的值,完全和最开始的100没有关系。
我是不是对完成键的工作原理理解错了,还是我在上面链接的源代码中做错了什么?
6 个回答
GetQueuedCompletionStatus
这个函数会返回两个东西,一个是OVERLAPPED
结构,另一个是完成键(completion key)。完成键是用来表示每个设备的信息,而OVERLAPPED
结构则是用来表示每次调用的信息。完成键应该和你在调用CreateIoCompletionPort
时提供的内容一致。通常,你会用一个指向包含连接信息的结构的指针作为完成键。
看起来你没有对GetQueuedCompletionStatus
返回的completionKey
做任何处理。
我猜你想要的是:
if completionKey != acceptKey:
Cleanup()
...
补充:
Python是否以某种方式知道在CreateAcceptSocket
中创建的OVERLAPPED
结构是被Win32 API异步使用的,并且防止它被垃圾回收(GC)掉?
你可以把完成键想象成是“每个连接”相关的数据,而扩展的重叠结构则是“每个输入输出”操作的数据。
有些人会使用一个扩展的重叠结构来同时处理这两种情况,把他们需要的所有信息都存储在这个扩展的重叠结构里。我一直在完成键中存储一个引用计数的对象,这个对象是用来包装我的套接字的,而在扩展的重叠结构中存储一个引用计数的数据缓冲区。如果你感兴趣的话,可以看看这里的一些C++示例IOCP代码。
完成键实际上就是一个不透明的句柄,它指向一些数据,当套接字完成操作时,I/O完成系统会把这些数据返回给你。
我在日常实践中发现,最好的做法就是专注于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
指针,不过,具体的实现方法可以参考那个链接。