现在的位置: 首页 > 综合 > 正文

WinSock完成端口I/O模型

2012年06月14日 ⁄ 综合 ⁄ 共 2765字 ⁄ 字号 评论关闭

关于重叠I/O,参考《WinSock重叠I/O模型》;关于完成端口的概念及内部机制,参考译文《深度探索I/O完成端口》。

完成端口对象取代了WSAAsyncSelect中的消息驱动和WSAEventSelect中的事件对象,当然完成端口模型的内部机制要比WSAAsyncSelectWSAEventSelect模型复杂得多。

IOCP内部机制如下图所示:

   WinSock中编写完成端口程序,首先要调用CreateIoCompletionPort函数创建完成端口。其原型如下:

WINBASEAPI HANDLE WINAPI

CreateIoCompletionPort(

       HANDLE FileHandle,

       HANDLE ExistingCompletionPort,

       DWORD CompletionKey,

       DWORD NumberOfConcurrentThreads );

第一次调用此函数创建一个完成端口时,通常只关注NumberOfConcurrentThreads,它定义了在完成端口上同时允许执行的线程数量。一般设为0,表示系统内安装了多少个处理器,便允许同时运行多少个线程为完成端口提供服务。每个处理器各自负责一个线程的运行,避免了过于频繁的线程上下文切换。

hCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0)

这个类比重叠I/O事件通知模型中(WSA)CreateEvent

然后再调用GetSystemInfo(&SystemInfo);取得系统安装的处理器的个数SystemInfo.dwNumberOfProcessors,根据CPU创建线程池,在完成端口上,为已完成的I/O请求提供服务。一般线程池的规模,即线程数 = CPU*2+2

下面的代码片段演示了线程池的创建。

// 创建线程池,规模为CPU数的两倍

  for(int i = 0; i < SystemInfo.dwNumberOfProcessors * 2; i++)

   {

      HANDLE ThreadHandle;

      // 创建一个工作线程,并将完成端口作为参数传递给它。

      if ((ThreadHandle = CreateThread(NULL, 0, WorkerThread, hCompletionPort,

         0, &ThreadID)) == NULL)

      {

         printf("CreateThread() failed with error %d/n", GetLastError());

         return;

      }

      // 关闭线程句柄

      CloseHandle(ThreadHandle);

   }

然后需要将一个句柄与已经创建的完成端口关联起来,这里主要指套接字AcceptSocket,以后针对这个套接字的I/O完成状态交由完成端口通知,程序接到完成通知后做善后处理。

这需要再次调用CreateIoCompletionPort函数(囧)。参数四NumberOfConcurrentThreads依旧填0,参数一一般就是AcceptSocket,参数二为上面创建的完成端口hCompletionPort。参数三即完成键,一般存放套接字句柄的背景信息,也就是所谓的单句柄数据。之所以把它叫作单句柄数据,因为它是用来保存参数一套接字句柄的关联信息。一般可简单定义如下:

typedef struct {

        SOCKET Socket;

} PER_HANDLE_DATA, * LPPER_HANDLE_DATA;

下面的代码片段演示了每次Accept返回时,调用CreateIoCompletionPort使返回的AcceptSocket与完成端口关联,并传递一个PerHandleData

AcceptSocket = WSAAccept(Listen, NULL, NULL, NULL, 0);

PerHandleData->Socket = AcceptSocket;

CreateIoCompletionPort((HANDLE) AcceptSocket, hCompletionPort, (DWORD) PerHandleData, 0)

这个类比重叠I/O事件通知模型中设置(WSAOVERLAPPED结构中的hEvent字段,使一个事件对象句柄同一个文件/套接字关联起来。

将套接字句柄与一个完成端口关联在一起后,便可以套接字句柄为基础,投递发送与接收请求,开始对I/O请求的处理。接下来,可开始依赖完成端口,来接收有关I/O操作完成情况的通知。从本质上说,完成端口模型利用了Win32重叠I/O机制。在这种机制中,像WSARecv()WSASend()这样的WinSock API调用会立即返回。此时,需要由我们的应用程序负责在以后的某个时间,通过一个OVERLAPPED结构,来接收调用的结果。在完成端口模型中,要想做到这一点,工作者线程WorkerThread需要调用GetQueuedCompletionStatus函数,在完成端口上等待。

GetQueuedCompletionStatus函数原型如下:

WINBASEAPI BOOL WINAPI

GetQueuedCompletionStatus(

    HANDLE CompletionPort,

    LPDWORD lpNumberOfBytesTransferred,

    LPDWORD lpCompletionKey,

    LPOVERLAPPED *lpOverlapped,

    DWORD dwMilliseconds );

When you perform an input/output operation with a file handle that has an associated input/output completion port, the I/O system sends a completion notification packet to the completion port when the I/O operation completes. The completion port places the completion packet in a first-in-first-out queue. The GetQueuedCompletionStatus function retrieves these queued completion packets. —MSDN

这个类比重叠I/O事件通知模型中的WSAWaitForMultipleEvents/WSAGetOverlappedResult

抱歉!评论已关闭.