Jirairya

WinSocket——part3

2017-10-17
code  C++

IOCP(I/O completion port,I/O完成端口)是伸缩性最好的一种I/O模型,适合处理成百上千的套接字。

I/O完成端口对象定义

I/O完成端口是应用程序使用线程池处理异步I/O请求的一种机制,处理多个并发异步I/O请求时,使用I/O完成端口比在I/O请求时创建线程更快更有效。

I/O完成端口最初的设计是应用程序发出一些异步I/O请求,当这些请求完成时,设备驱动将把这些工作项目排序到完成端口,这样,在完成端口上等待的线程池便可以处理这些完成I/O。完成端口实际是一个Windows I/O结构,它可以接收多种对象的句柄,如文件对象、套接字对象等。

IOCP的使用方法

创建完成端口对象

使用完成端口模型,首先调用CreateIoCompletionPort函数创建一个完成端口对象,Winsock将使用这个对象为任意数量的套接字句柄管理I/O请求。

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

该函数两个不同的功能:

  • 创建一个完成端口对象
  • 将一个或者多个文件句柄关联到I/O完成端口对象

最初创建完成端口对象时,唯一需要设置的参数是NumberOfConcurrentThreads,定义了允许在完成端口上同时执行的线程的数量。理想情况下,每个处理器仅运行一个线程来为完成端口提供服务,以避免线程上下文切换。NumberOfConcurrentThreads为0表示系统允许的线程数量与处理器数量一样多。

创建完成端口对象,取得标识完成端口的句柄:

HANDLEhCompletion = ::CreateIoCompletionPort(INVALID_HANDLE_VALUE,0,0,0);

I/O服务线程和完成端口

成功创建完成端口对象后,可以向该对象关联套接字句柄。在关联套接字之前,需要创建一个或者多个工作线程(称为I/O服务线程),在完成端口上执行并处理投递到完成端口上的I/O请求。创建完成端口时指定的线程数量和这里要创建的线程数量不是一回事,之前推荐线程数量为处理器的数量,以避免上下文切换CreateIoCompletionPort函数的NumberOfConcurrentThreads参数明确告诉系统允许在完成端口上同时运行的线程数量,若创建的线程多于NumberOfConcurrent Threads,也就仅有NumberOfConcurrentThreads个线程允许运行。但有时候确实需要创建更多的线程,这取决于程序的总体设计。若某线程调用了一个函数,如sleep或WaitForSingleObject进入了暂停状态,多出来的线程中就会有一个开始运行,占据休眠线程的位置。总之,总希望在完成端口上参加I/O处理工作的线程和CreateIoCompletionPort函数指定的线程一样多。若工作线程会遇到阻塞(进入暂停状态),那就应该创建比CreateIoCompletionPort指定的数量还要多的线程

有足够的工作线程来处理完成端口上的I/O请求之后,就该为完成端口关联套接字句柄,这就用到CreateIoCompletionPort函数的前三个参数。

  • FileHandle:要关联的套接字句柄
  • ExistingCompletionPort:上面创建的完成端口对象的句柄
  • CompletionKey:指定一个句柄唯一(per-handle)数据,它将与FileHandle套接字句柄关联在一起。应用程序可以在此存储任意类型的信息,通常是一个指针。(CompletionKey参数通常用于描述与套接字相关的信息,所以称为句柄唯一数据)

完成端口和重叠I/O

向完成端口关联套接字句柄之后,便可以在套接字上投递重叠发送和接收请求处理I/O了。在这些I/O操作完成时,I/O系统会向完成端口对象发送一个完成通知封包。I/O完成端口先以先进先出的方式为这些封包排队。应用程序使用GetQueuedCompletionStatus函数可以取得这些队列中的封包。这个函数应该在处理完成对象I/O的服务线程中调用。

BOOL GetQueuedCompletionStatus(
    HANDLE CompletionPort, //完成端口对象句柄
    LPDWORD lpNumberOfBytes, //取得I/O操作期间传输的字节数
    PULONG_PTR lpCompletionKey, //取得在关联套接字时指定的句柄唯一数据
    LPOVERLAPPED* lpOverlapped, // 取得投递IO操作时指定的OVERLAPPED结构
    DWORD dwMillisseconds //若完成端口没有完成封包,此参数指定了等待的事件,INFINITE为无穷大
    );

I/O服务线程调用GetQueuedCompletionStatus函数取得有事件发生的套接字的信息,通过lpNumberOfBytes参数得到传输的字节数,通过lpCompletionKey参数得到与套接字关联的句柄唯一数据,通过lpOverlapped参数得到投递I/O请求时使用的重叠对象地址,进一步得到I/O唯一(per-I/O)数据

这些参数中,最重要的是per-handle数据和per-I/O。

lpCompletionKey参数包含了称为per-handle的数据,因为当套接字第一次与完成端口关联时,这个数据就关联到了一个套接字句柄。这是传递给CreateIoCompletionPort函数的CompletionKey参数。如前所述,可以给这个参数传递任何类型的数据。

lpOverlapped


上一篇 VNC攻击

下一篇 工控

Comments

Content