关于IOCP的方方面面

时间:2021-07-08 15:19:36

与IOCP相关的数据结构和API

IOCP是以OVERLAPPED IO为基础的。

 

HANDLE WINAPI CreateIoCompletionPort(
  __in      HANDLE FileHandle,
  __in_opt  HANDLE ExistingCompletionPort,
  __in      ULONG_PTR CompletionKey,
  __in      DWORD NumberOfConcurrentThreads
);

用      途:创建一个IOCP port并将一个file handle与之关联;创建一个没有file handle 关联的IOCP port,之后可以再关联。将一个已经打开的file handle与一个IOCP port关联,允许一个进程能收到与这个file handle 相关的异步IO操作完成的通知。

参数解释:FileHandle 是一个支持overlapped I/O的对象;ExistingCompletionPort是一个已经存在的IOCP port handle,可以为NULL。如果这个参数不为NULL,则将这个file handle与该IOCP port 关联;CompletionKey由用户定义的,每个handle一个,被包含在每个IO completion packet中。NumberOfConcurrentThreads指定对这个IOCP port,OS允许并行处理IO completion packet的工作者线程的数量,只有新创建IOCP port时会有用,关联新的file handle是这个参数会被忽略。如果这个参数是0,系统使用跟processor一样多的线程进行处理。

特别关注: Use the CompletionKey parameter to help your application track which I/O operations have completed. This value is not used by CreateIoCompletionPort for functional control; rather, it is attached to the file handle specified in the FileHandle parameter at the time of association with an I/O completion port. This completion key should be unique for each file handle, and it accompanies the file handle throughout the internal completion queuing process. It is returned in the GetQueuedCompletionStatus function call when a completion packet arrives. The CompletionKey parameter is also used by the PostQueuedCompletionStatus function to queue your own special-purpose completion packets.

 

BOOL WINAPI GetQueuedCompletionStatus(
  __in   HANDLE CompletionPort,
  __out  LPDWORD lpNumberOfBytes,
  __out  PULONG_PTR lpCompletionKey,
  __out  LPOVERLAPPED *lpOverlapped,
  __in   DWORD dwMilliseconds
);

用      途:从指定的IOCP port中dequeue一个completion packet。如果IOCP port队列中没有completion packet,该函数会被block,直到IOCP port中有completion packet完成。

参数解释:CompletionPort 是目标port的handle; lpNumberOfBytes是传出指针,用于接收一次已完成的IO操作所传送的字节数;lpCompletionKey

 

BOOL WINAPI PostQueuedCompletionStatus(
  __in      HANDLE CompletionPort,
  __in      DWORD dwNumberOfBytesTransferred,
  __in      ULONG_PTR dwCompletionKey,
  __in_opt  LPOVERLAPPED lpOverlapped
);

用      途:向指定的IOCP port中post一个新的completion packet。

参数解释:CompletionPort 是目标port的handle,而dwNumberOfBytesTransferred, dwCompletionKey和lpOverlapped这3个参数会直接传递给GetQueuedCompletionStatus函数。

IOCP的使用方法

 

IOCP Internal及注意事项

1. 工作者线程与完成端口
成功创建一个完成端口后,便可开始将套接字句柄与对象关联到一起。但在关联套接字之前,首先必须创建一个或多个“工作者线程”,以便在I/O请求投递给完成端口对象后,为完成端口提供服务。在这个时候,大家或许会觉得奇怪,到底应创建多少个线程,以便为完成端口提供服务呢?这实际正是完成端口模型显得颇为“复杂”的一个方面,因为服务I/O请求所需的数量取决于应用程序的总体设计情况。在此要记住的一个重点在于,在我们调用CreateIoCompletionPort时指定的并发线程数量,与打算创建的工作者线程数量相比,它们代表的并非同一件事情。早些时候,我们曾建议大家用CreateIoCompletionPort函数为每个处理器
都指定一个线程(处理器的数量有多少,便指定多少线程)以避免由于频繁的线程“场景”交换活动,从而影响系统的整体性能。CreateIoCompletionPort函数的NumberOfConcurrentThreads参数明确指示系统:在一个完成端口上,一次只允许n个工作者线程运行。假如在完成端口上创建的工作者线程数量超出n个,那么在同一时刻,最多只允许n个线程运行。但实际上,在一段较短的时间内,系统有可能超过这个值,但很快便会把它减少至事先在CreateIoCompletionPort函数中设定的值。那么,为何实际创建的工作者线程数量有时要比CreateIoCompletionPort函数设定的多一些呢?这样做有必要吗?如先前所述,这主要取决于
应用程序的总体设计情况。假定我们的某个工作者线程调用了一个函数,比如Sleep或WaitForSingleObject,但却进入了暂停(锁定或挂起)状态,那么允许另一个线程代替它的位置。换言之,我们希望随时都能执行尽可能多的线程;当然,最大的线程数量是事先在CreateIoCompletionPort调用里设定好的。这样一来,假如事先预计到自己的线程有可能暂时处于停顿状态,那么最好能够创建比CreateIoCompletionPort的NumberOfConcurrentThreads参数的值多的线程,以便到时候充分发挥系统的潜力。一旦在完成端口上拥有足够多的工作者线程来为I/O请求提供服务,便可着手将套接字句柄同完成端口关联到一起。这要求我们在一个现有的完成端口上,调用CreateIoCompletionPort函数,同时为前三个参数——FileHandle,ExistingCompletionPort和CompletionKey——提供套接字的信息。其中, FileHandle参数指定一个要同完成端口关联在一起的套接字句柄。ExistingCompletionPort参数指定的是一个现有的完成端口。CompletionKey(完成键)参数则指定要与某个特定套接字句柄关联在一起的“单句柄数据”;在这个参数中,应用程序可保存与一个套接字对应的任意类型的信息。之所以把它叫作“单句柄数据”,是由于它只对应着与那个套接字句柄关联在一起的数据。可将其作为指向一个数据结构的指针,来保存套接字句柄;在那个结构中,同时包含了套接字的句柄,以及与那个套接字有关的其他信息。


2. 当发送一个很大的数据包的时候,是整个数据包都被发送完成,才会收到一个completion packet还是收到若干个;当接收一个很大的数据包的时候,是整个包都被收到后,才收到一个completion packet还是收到若干个?

 

在Windows上实现IOCP


用epoll实现IOCP