Overlapped模型与CompletionPort模型

时间:2022-09-19 00:07:04

Overlapped模型与CompletionPort模型

    Win32重叠I/O(Overloapped I/O)机制允许发起一个操作,然后在操作完成之后接受
到信息。对于那种需要很长时间才能完成的操作来说,重叠I/O机制尤其有用,因为发起
重叠操作的线程在重叠请求发出后就可以*地做别的事情了。
    在Windows NT/2000上,提供真正可扩展的I/O模型就是使用完成端口(Completion 
Port)的重叠I/O。
    ……
    可以把完成端口看成系统维护的一个队列,操作系统把重叠I/O操作完成的事件通知
放到该队列里,由于是暴露“操作完成”的事件通知,所以命名为“完成端口”(Completion
Ports)。一个Socket被创建后,可以在任何时刻和一个完成端口联系起来。
    一般来说,一个应用程序可以创建多个工作线程来处理完成端口上的通知事件。工作
线程的数量依赖于程序的具体需要。但是在理想的情况下,应该对应一个CPU创建一个线
程。因为在完成端口理想模型中,每个线程都可以从系统获得一个“原子”性的时间片,轮
番运行并检查完成端口,线程的切换是额外的开销。在实际开发的时候,还要考虑这些线
程是否牵涉到其他堵塞操作的情况。如果某线程进行堵塞操作,系统则将其挂起,让别的
线程获得运行时间。因此,如果有这样的情况,可以多创建几个线程来尽量利用时间。
    应用完成端口分两步走:
    1. 创建完成端口句柄:
    HANDLE hIocp;
    hIocp=CreateIoCompletionPort(
    INVALID_HANDLE_VALUE,
    NULL,
    (ULONG_PTR)0,
    0);
    if(hIocp==NULL) {
    //如果错误
      ……
    }
    注意在第1个参数(FileHandle)传入INVALID_FILE_HANDLE,第2个参数(ExistingCompletionPort)
传入NULL,系统将创建一个新的完成端口句柄,没有任何I/O句柄与其关联。

    2. 完成端口创建成功后,在Socket和完成端口之间建立关联。再次调用CreateIoCompletionPort
函数,这一次在第1个参数FileHandle传入创建的Socket句柄,参数ExistingCompletionPort
为已经创建的完成端口句柄。
    以下代码创建了一个Socket并把它和完成端口联系起来。
    SOCKET s;
    s=Socket(AF_INET,SOCK_STREAM,0);
    if(s==INVALID_SOCKET) {
    if(CreateIoCompletionPort((HANDLE)s,
    hIocp,
    (ULONG_PTR)0,
    0)==NULL)
    {
    //如果创建失败
      ……
    }
    }
    到此为止,Socket已经成功和完成端口相关联。在此Socket进行的重叠I/O操作结果均
使用完成端口发出通知。

    注意:CreateIoCompletionPort函数的第3个参数允许开发人员传入一个类型为ULONG_PTR
的数据成员,我们把它称为完成键(Completion Key),此数据成员可以设计为指向包含Socket
信息的一个结构体的一个指针,用来把相关的环境信息和Socket联系起来,每次完成通知来
到的同时,该环境信息也随着通知一起返回给开发人员。

    完成端口创建以及与Socket关联之后,就要创建一个或多个工作线程来处理完成通知,
每个线程都可以循环地调用GetQueuedCompletionStatus函数,检查完成端口上的通知事件。
    在举例说明一个典型的工作线程之前,我们先讨论一下重叠I/O的过程。到一个重叠I/O
被发起,一个Overlapped结构体的指针就要作为参数传递给系统。当操作完成时,
GetQueueCompletionStatus就可以返回指向同一个Overlapped结构的指针。为了辨认和定位
这个已完成的操作,开发人员最好定义自己的OVERLAPPED结构,以包含一些自己定义的关于
操作本身的额外信息。比如:
    typedef struct _OVERLAPPELUS {
      OVERLAPPED ol;
      SOCKET s, sclient;
      int OpCode;
      WSABUF wbuf;
      DWORD dwBytes, dwFlags;
    } OVERLAPPELUS;
    此结构的第1个成员为默认的OVERLAPPED结构,第2和第3个为本地服务Socket和与该
操作相关的客户socket,第4个成员为操作类型,对于Socket,现在定义的有以下3种:
    #define OP_READ 0
    #define OP_WRITE 1
    #define OP_ACCEPT 2
    然后还有应用程序的Socket缓冲区,操作数据量,标志位以及其他开发人员认为有用
的信息。
    当进行重叠I/O操作,把OVERLAPPELUS结构作为重叠I/O的参数lpOverlapp传递(如
WSASend,WASRecv,等函数的lpOverlapped参数,要求传入一个OVERLAPP结构的指针)。
    当操作完成后,GetQueuedCompletionStatus函数返回一个LPOVERLAPPED类型的指针,
这个指针其实是指向开发人员定义的扩展OVERLAPPELUS结构,包含着开发人员早先传入的
全部信息。

    注意:OVERLAPPED成员不一定要求是OVERLAPPELUS扩展结构的一个成员,在获得
OVERLAPPED指针之后,可以用CONTAINING_RECORD宏获得相应的扩展结构的指针。

    典型的Worker Thread结构:
    DWORD WINAPI WorkerThread(LPVOID lpParam)
    {
      ULONG_PTR *PerHandleKey;
      OVERLAPPED *Overlap;
      OVERLAPPELUS *OverlapPlus, *newolp;
      DWORD dwBytesXfered;
      
      while(1)
      {
        ret=GetQueuedCompletionStatus(
        hIocp,
        &dwBytesXfered,
        (PULONG_PTR)&PerHandleKey,
        &Overlap,
        INFINITE);
        if(ret==0)
        {
          //如果操作失败
          continue;
        }
        
        OverlapPlus=CONTATING_RECORD(Overlap, OVERLAPPELUS, ol);
        switch(OverlapPlus->OpCode)
        {
          case OP_ACCEPT:
            CreateIoCompletionPort(
                (HANDLE)OverlapPlus->sclient,
                hIocp,
                (ULONG_PTR)0,
                0);
            newolp=AllocateOverlappelus();
            newolp->s=OverlapPlus->sclient;
            newolp->OpCode=OP_READ;
            PrepareSendBuffer(&newolp->wbuf);
            ret=WSASend(
              newolp->s,
              &newolp->wbuf,
              1,
              &newolp->dwBytes,
              0,
              &newolp.ol,
              NULL);
            if(ret==SOCKET_ERROR)
            {
              if(WSAGetLastError()!=WSA_IO_PENDING)
              {
                //进行错误处理
                ……
              }
            }
            FreeOverlappelus(OverlapPlus);
            SetEvent(hAcceptThread);
            break;
          case OP_READ:
            memset(&OverlapPlus->ol,0,sizeof(OVERLAPPED));
            ret=WSARecv(
                OverlapPlus->s,
                &OverlapPlus->wbuf,
                1,
                &OverlapPlus->dwBytes,
                &OverlapPlus->dwFlags,
                &OverlapPlus->ol,
                NULL);
            if(ret==SOCKET_ERROR)
            {
              if(WSAGetLastError()!=WSA_IO_PENDING)
              {
                //错误处理
                ……
              }
            }
            break;
          case OP_WRITE:
            break;
        }/*switch结束*/
      }/*while结束*/
    }/*WorkerThread结束*/
    
    注意:如果Overlapped操作立刻失败(比如,返回SOCKET_ERROR或其他非
    WSA_IO_PENDING的错误),则没有任何完成通知事件会被放到完成端口队列里。反之,
    则一定有相应的通知事件被放到端口队列。