套接字I/O模型-完成端口IOCP

“完成端口”模型是迄今为止最为复杂的一种I/O模型。然而,假若一个应用程序同时需要管理为数众多的套接字,那么采用这种模型,往往可以达到最佳的系统性能!但不幸的是,该模型只适用于Windows NT和Windows 2000操作系统。因其设计的复杂性,只有在你的应用程序需要同时管理数百乃至上千个套接字的时候,而且希望随着系统内安装的CPU数量的增多,应用程序的性能也可以线性提升,才应考虑采用“完成端口”模型。要记住的一个基本准则是,假如要为Windows NT或Windows 2000开发高性能的服务器应用,同时希望为大量套接字I/O请求提供服务(Web服务器便是这方面的典型例子),那么I/O完成端口模型便是最佳选择!

从本质上说,完成端口模型要求创建一个windows完成端口对象,该对象通过指定数量的线程,对重叠I/O进行管理,以便为已完成的重叠I/O请求提供服务。要注意的是,所谓完成端口,实际上是windows采用的一种I/O构造机制,除套接字句柄之外,还可以接受其他东西。

使用这种模型之前,首先要创建一个I/O完成端口对象,用它面向任意数量的套接字句柄,管理多个I/O请求,要做到这一点,首先调用函数:

  
  
  
  
  1. HANDLE CreateIoCompletionPort( 
  2.   HANDLE FileHandle, 
  3.   HANDLE ExistingCompletionPort, 
  4.   DWORD CompletionKey, 
  5.   DWORD NumberOfConcurrentThreads 
  6. ); 

首先注意该函数实际用于两个截然不同的两个目的:

1.用于创建一个完成端口对象

2.将一个句柄同完成端口关联在一起

最开始创建完成端口时,我们唯一感兴趣的是NumberOfConccurrentThreads,前三个参数不太重要。 NumberOfConccurrentThreads定义了在一个完成端口上,同时允许执行的线程数量。若将该参数设为0,则告诉系统安装了多少个处理器,则允许同时运行多少个线程,可用如下代码创建一个I/O完成端口:

  
  
  
  
  1. CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0); 

该语句的作用是返回一个句柄,在为完成端口分配了一个套接字句柄后,用来对那个端口进行标识。

 

工作器线程与完成端口

成功创建一个完成端口后,便可开始将套接字句柄与对象关联到一起。但在关联套接字之前,首先必须创建一个或多个工作器线程,以便在套接字的I/O请求投递给完成端口后,为完成端口提供服务。

假如事先预计到线程有可能暂时处于阻塞状态,那么最好能够创建比CreateIoCompletionPort的NumberOfConccurrentThreads值更多的线程。以便到时候充分发挥系统的潜力。

一旦在完成端口上拥有足够多的工作器线程来为I/O请求提供服务,便可着手将套接字句柄同完成端口关联在一起。需要在一个完成端口上调用CreateIoCompletionPort函数,同时为前三个参数FileHandle,ExistingCompletionPort和CompletionKey提供套接字信息。其中,FileHandle参数指定一个要同完成端口关联在一起的套接字句柄,ExistingCompletionPort参数标识的是一个现有的完成端口套接字句柄已经与他关联在一起。CompletionKey参数标识的是要与某个特定套接字句柄关联在一起的单句柄数据;在这个参数中,应用程序可保持与一个套接字对应的任意类型信息。之所以叫它单句柄数据,是由于它代表了与套接字句柄关联在一起的数据。可将它作为指向一个数据结构的指针;在这个结构中,同时包含了套接字的句柄,以及与该套接字有关的其他信息。为完成端口提供服务的线程的例程可通过这个参数,取得与套接字句柄有关的信息。

下面示例阐述了如何使用完成端口模型,来开发一个回应服务器应用程序,这个程序基本按照如下步骤进行:

1.创建一个完成端口,第四个参数为0,它指定完成端口上每个处理器一次只允许执行一个工作器线程

2.判断系统内有多少个处理器

3.创建工作器线程,根据步骤2得到的处理器信息,在完成端口上为已完成的I/O请求提供服务。在这个简单的例子中,我们为每个处理器只创建一个工作器线程。调用CreateThread函数时,必须同时提供一个工作器例程,由线程在创建好后执行

4.准备好一个监听套接字,在端口上监听传入的连接

5.使用accept接收入站的连接请求

6.创建一个数据结构,用于容纳单句柄数据,同时在结构中存入接收的套接字句柄

7.调用CreateIoCompletionPort,将自accept返回的新套接字句柄同完成端口关联在一起。通过 CompletionKey 参数,将单句柄数据结构传递给CreateIoCompletionPort

8.开始在已结束的连接上进行I/O操作,在此,我们希望通过重叠I/O机制,在新建套接字投递一个或多个WSARecv或WSASend请求。这些I/O请求完成后,工作器线程会为I/O请求提供服务,同时继续处理以后的I/O请求

9.重复步骤5~8,直到服务器终止

  
  
  
  
  1. HANDLE CompletionPort; 
  2. WSADATA wsd; 
  3. SYSTEM_INFO SystemInfo; 
  4. SOCKADDR_IN addr; 
  5. SOCKET Listen; 
  6. int i; 
  7. typedef struct _PER_HANDLE_DATA 
  8.     SOCKET Socket; 
  9.     SOCKADDR_STORAGE ClientAddr; 
  10.     //将和这个句柄关联的其他信息  
  11. }PER_HANDLE_DATA, *LPPER_HANDLE_DATA; 
  12.  
  13. //加载Winsock 
  14. StartWinsock(MAKEWORD(2,2), &wsd); 
  15.  
  16. //第一步 
  17. //创建一个I/O完成端口 
  18. CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0); 
  19.  
  20. //第二步 
  21. //确定系统有多少个处理器 
  22. GetSystemInfo(&SystemInfo); 
  23.  
  24. //第三步 
  25. //基于系统中可用的处理器数量创建工作器线程 
  26. //对这个例子,为每个处理器创建一个工作器线程 
  27. for(i=0; i<SystemInfo.dwNumberOfProcessors; i++) 
  28.     HANDLE ThreadHandle; 
  29.      
  30.     //创建一个服务器的工作线程,并将完成端口传递到该线程 
  31.     ThreadHandle = CreateThread(NULL, 0, ServerWorkerThread,  
  32.                                 CompletionPort, 0, NULL); 
  33.     //关闭线程句柄 
  34.     CloseHandle(ThreadHandle);  
  35. }   
  36.  
  37. //第四步 
  38. //创建一个监听套接字 
  39. Listen = WSASocket(AF_INET,SOCK_STREAM,0,NULL,0,WSA_FLAG_OVERLAPPED); 
  40. addr.sin_family = AF_INET; 
  41. addr.sin_port = htons(5050); 
  42. addr.sin_addr.s_addr = htonl(INADDR_ANY); 
  43. bind(Listen, (PSOCKADDR)&addr, sizeof(SOCKADDR_IN)); 
  44. listen(Listen, 5); 
  45.  
  46. while(TRUE) 
  47.     PER_HANDLE_DATA *PerHandleData = NULL; 
  48.     SOCKADDR_IN saRemote; 
  49.     SOCKET Accept; 
  50.     int RemoteLen; 
  51.      
  52.     //第五步 
  53.     //接收连接,并分配到完成端口 
  54.     RemoteLen = sizeof(SOCKADDR_IN); 
  55.     Accept = WSAAccept(Listen, (SOCKADDR*)&saRemote, &RemoteLen); 
  56.      
  57.     //第六步 
  58.     //创建用来和套接字关联的单句柄数据信息结构 
  59.     PerHandleData  = (LPPER_HANDLE_DATA)GlobalAlloc(GPTR,sizeof(PER_HANDLE_DATA)); 
  60.     printf("Socket Number %d connected\n",Accept); 
  61.     PerHandleData->Socket = Accept; 
  62.     memcpy(&PerHandleData->ClientAddr,&saRemote,RemoteLen); 
  63.      
  64.     //第七步 
  65.     //将接收套接字和完成端口关联起来 
  66.     CreateIoCompletionPort((HANDLE)Accept, 
  67.                            CompletionPort, 
  68.                            (DWORD)PerHandleData, 
  69.                            0); 
  70.      
  71.     //第八步 
  72.     //开始在接受套接字上处理I/O 
  73.     //使用重叠I/O,在套接字上投递一个或多个WSASend或WSARecv调用 
  74.     WSARecv(...);              
  75. }  
  76.  
  77. DWORD WINAPI ServerWorkerThread(LPVOID lpParam) 
  78.     //工作器线程 
  79.     return 0;  

 

完成端口和重叠I/O

将套接字句柄与一个完成端口关联在一起后,便能以套接字句柄为基础,投递重叠发送与接收请求,开始对I/O请求进行处理,之后可开始依赖完成端口,接收有关I/O操作完成情况通知。从本质上说,完成端口模型利用了Windows重叠I/O机制。在这种机制中,类似WSASend和WSARecv这样的WindowsAPI调用会立即返回。此时,需要由应用程序负责在以后的某个时间,通过OVERLAPPED结构来检索调用的结果。在完成端口模型中,想要做到这一点需要使用GetQueuedCompletionStatus函数,让一个或多个工作器线程在完成端口上等待:

  
  
  
  
  1. BOOL GetQueuedCompletionStatus( 
  2.   HANDLE CompletionPort, 
  3.   LPWORD lpNumberOfBytesTransferred, 
  4.   PULONG_PTR lpCompletionkey, 
  5.   LPOVERLAPPED * lpOverlapped, 
  6.   DWORD dwMilliseconds 
  7. ); 

其中,CompletionPort对应与线程所在的完成端口。lpNumberOfBytesTransferred参数负责在完成一次I/O操作后,接收实际传输的字节数。lpCompletionkey参数为原先传递到CreateIoCompletionPort函数的套接字返回单句柄数据。如前所述,大家最好将套接字句柄保持在这个键中。lpOverlapped参数用于接收已完成的I/O操作的WSAOVERLAPPED结构。因为可用它获取每个I/O操作的数据,所有这实际上也是一个相当重要的参数。dwMilliseconds用于指明调用者等待一个完成数据包在完成端口上出现时,希望等候的毫秒数。假如将其设为INFINITE,调用会无休止的等待下去。

 

单句柄数据和单I/O操作数据

当一个工作器线程从GetQueuedCompletionStatus这个API调用中接收到I/O完成通知后,在lpCompletionKey和lpOverlapped参数中,会包含一些必要的套接字信息。利用这些信息,可通过完成端口,继续在一个套接字上进行I/O处理。通过这些参数,可获得两种重要的套接字数据类型:单句柄数据和单I/O操作数据。

因为在一个套接字首次与完成端口关联到一起的时候,单句柄数据便与一个特定的套接字句柄对应起来了,所有lpCompletionKey参数也包含了单句柄数据。这些数据真是在进行CreateIoCompletionPort调用的时候,通过CompletionKey参数传递的。通常情况下,应用程序会将与I/O请求有关的套接字句柄保存在这里。

lpOverlappde则包含了一个OVERLAPPED结构,在它后面跟随单I/O操作数据。工作器线程处理一个完成数据包时(回应数据,接受连接以及投递另一个线程等),这些信息是它必须知道的。单I/O操作数据是包含在一个结构内的,任意数量的字节,这个结果本身也包含了一个OVERLAPPED结构,假如一个函数要求用到一个OVERLAPPED结构,我们便必须将这样的一个结构传递进去,以满足它的要求。要想做到这一点,一个简单的方法是定义一个结构,然后将OVERLAPPED结构作为新结构的第一个元素使用,举个例子:

  
  
  
  
  1. typedef struct 
  2.   OVERLAPPED Overlapped; 
  3.   char Buffer[DATA_BUFSIZE]; 
  4.   int BufferLen; 
  5.   int OperationType; 
  6. }PER_IO_DATA 

要想调用windowsAPI函数,同时为其分配一个OVERLAPPED结构,只要简单的撤销对结构中OVERLAPPED机构的引用即可,如下所示:

  
  
  
  
  1. PER_IO_OPERATION_DATA PerIoData; 
  2. WSABUF wbuf; 
  3. DWORD Bytes,Flags; 
  4.  
  5. //初始化wbuf 
  6.  
  7. WSARecv(socket,&wbuf,1,&Bytes,&Flags,&(PerIoData.Overlapped),NULL); 

在工作器线程的后面部分,GetQueuedCompletionStatus函数返回了一个重叠结构和完成键,获取单I/O数据应使用宏CONTAINING_RECORD,例如:

  
  
  
  
  1. PER_IO_DATA *PerIoData = NULL; 
  2. OVERLAPPED *lpOverlapped = NULL; 
  3.  
  4. ret = GetQueuedCompletionStatus( 
  5.       ComPortHandle, 
  6.       &Transferred, 
  7.       (PULONG_PTR)&CompletionKey, 
  8.        &lpOverlapped, 
  9.        INFINITE); 
  10. //检查成功的返回 
  11. PerIoData = CONTAINING_RECORD(lpOverlapped,PER_IO_DATA,Overlapped); 

应该使用这个宏;否则,结构PER_IO_DATA的成员OVERLAPPED就始终不得不首先出现,这会成为一个危险的假设(多个开发者开发同一段代码时尤为严重)。

可以使用单I/O结构的一个字段来表示被投递的操作类型,从而可以确定到底是哪个操作投递到了句柄上。在我们的例子中,OpdrationType字段应设为可以指示读写等操作的值。对单I/O操作数据来说,它最大的优点便是允许我们在同一个句柄上,同时管理多个I/O操作(读写,多个读写操作等等)。

Windows完成端口的一个重要方面是,所有重叠操作可确保按照应用程序安排好的顺序执行。然而,不能确保从完成端口返回的完成通知也按上述顺序执行。

设计一个工作器线程,令其使用单句柄数据和单I/O操作数据为I/O请求提供服务:

  
  
  
  
  1. DWORD WINAPI ServerWorkerThread(LPVOID lpParam) 
  2.     HANDLE CompletionPort = (HANDLE)lpParam; 
  3.     DWORD BytesTransferred; 
  4.     LPOVERLAPPED Overlapped; 
  5.     LPPER_HANDLE_DATA PerHandleData; 
  6.     LPPER_IO_DATA PerIoData; 
  7.     DWORD SendBytes, RecvBytes; 
  8.     DWORD Flags; 
  9.     while(TRUE) 
  10.     { 
  11.         //等待和完成端口关联的任意套接字上的I/O完成 
  12.         ret = GetQueuedCompletionStauts(CompletionPort, 
  13.                                         &BytesTransferred, 
  14.                                         (LPWORD)&PerHandleData, 
  15.                                         (LPOVERLAPPED*)&PerIoData, 
  16.                                         INFINITE); 
  17.         //先检查一下,看是否在套接字上发生错误; 
  18.         //如果发生了,关闭套接字,并清除和这个套接字关联的单句柄数据和单I/O操作数据 
  19.         if(BytesTransferred==0 && 
  20.            (PerIoData->OperationType == RECV_POSTED || PerIoData->OperationType == SEND_POSTED)) 
  21.         { 
  22.             //BytesTransferred为0时,表明套接字已被通信对方关闭,因此我们也要关闭套接字 
  23.             //注意:单句柄数据用来引用和I/O关联的套接字 
  24.             closesocket(PerHandleData->Socket); 
  25.             GlobalFree(PerHandleData); 
  26.             GlobalFree(PerIoData); 
  27.             continue;  
  28.         }  
  29.         //为完成的I/O请求提供服务。可以通过查看单I/O操作数据中包含的 OperationType字段, 
  30.         //来确定刚完成的是哪个I/O请求 
  31.         if(PerIoData->OperationType == RECV_POSTED)  
  32.         { 
  33.             //对PerIoData->Buffer中接收到的数据施加某种操作  
  34.         } 
  35.          
  36.         //投递另外一个WSASend或WSARecv操作 
  37.         //这里只投递一个WSARecv操作 
  38.         Flags = 0; 
  39.          
  40.         //为下一个重叠调用建立单I/O操作数据 
  41.         ZeroMemory(&(PerIoData->Overlapped),sizeof(OVERLAPPED)); 
  42.         PerIoData->DataBuf.len = DATA_BUFSIZE; 
  43.         PerIoData->DataBuf.buf = PerIoData->Buffer; 
  44.         PerIoData->OperationType = RECV_POSTED; 
  45.         WSARecv(PerHandleData->Socket,  
  46.                 &(PerIoData->DataBuf), 
  47.                 1, 
  48.                 &RecvBytes, 
  49.                 &Flags, 
  50.                 &(PerIoData->Overlapped), 
  51.                 NULL); 
  52.     } 

对于一个给定的重叠操作,如果发生错误,则GetQueuedCompletionStatus将返回FALSE,因为完成端口是Windows采用的一种I/O构造机制,所有,如果调用GetLastError或WSAGetLastError,则错误代码及可能是一个Windows错误代码,而非Winsock错误。要想得到winsock错误代码,可以在指定了套接字句柄和结构WSAOVERLAPPED的情况下,对已完成的操作调用WSAGetOverlappedResult,之后WSAGetLastError将返回转换后的Winsock错误代码。

最后要注意一处细节,是如何正确关闭I/O完成端口--特别是同时运行一个或多个线程,在几个不同的套接字上执行I/O操作时。要注意的一个主要问题是,在进行重叠I/O操作时,应避免强行释放OVERLAPPED结构。要想不出现这种情况,最好的办法是针对每个套接字句柄,调用closesocket函数,则任何尚未进行的重叠I/O操作都会完成。一旦所有套接字句柄都已关闭,便须在完成端口上终止所有工作器线程的运行。要想做到这一点可以使用PostQueuedCompletionStatus函数,向每个工作器线程都发送一个特殊的完成数据包。该函数会提示每个线程立即结束并推出:

  
  
  
  
  1. BOOL PostQueuedCompletionStatus( 
  2.   HANDLE CompletionPort, 
  3.   DWORD dwNumberOfBytesTransferred, 
  4.   ULONG_PTR dwCompletionKey, 
  5.   LPOVERLAPPED lpOverlapped 
  6. ); 

CompletionPort参数指明程序想向其发送一个完成数据包的完成端口对象。而就dwNumberOfBytesTransferred,dwCompletionKey,lpOverlapped这3个参数来说,每一个都允许指定一个值,直接传递给GetQueuedCompletionStatus函数中对应的参数,这样,根据参数,决定何时退出。 

 

=========================================================================

  
  
  
  
  1. #include<stdio.h> 
  2. #include<winsow2.h> 
  3. #pragma comment(lib, "ws2_32.lib") 
  4.  
  5. #define PORT 5050 
  6. #define MSGSIE 1024 
  7.  
  8. typedef enum 
  9.     RECV_POSTED 
  10. }OPERATION_TYPE; 
  11.  
  12. typedef struct 
  13.     OVERLAPPED overlap; 
  14.     WSABUF     Buffer; 
  15.     char       szMessage[MSGSIZE]; 
  16.     DWORD      NumberOfBytesRecvd; 
  17.     DWORD      Flags; 
  18.     OPERATION_TYPE OpetationType; 
  19. }PER_IO_OPERATION_DATA, *LPPER_IO_OPERATION_DATA; 
  20.  
  21. DWORD WINAPI WorkerThread(LPVOID lpParam); 
  22.  
  23. int main() 
  24.     WSADATA wsaData; 
  25.     SOCKET sListen, sClient; 
  26.     SOCKADDR_IN local, client; 
  27.     DWORD i, dwThreadId; 
  28.     int iAddrSize = sizeof(SOCKADDR_IN); 
  29.     HANDLE CompletionPort = INVALID_HANDLE_VALUE; 
  30.     SYSTEM_INFO sysinfo; 
  31.     LPPER_IO_OPERATION_DATA lpPerIoData = NULL; 
  32.      
  33.     WSAStartup(MAKEWORD(2,2), &wsaData); 
  34.     CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0); 
  35.     GetSystemInfo(&sysinfo); 
  36.     for(i = 0; i<sysinfo.dwNumberOfProcessors; i++) 
  37.     { 
  38.         CreateThread(NULL,0,WorkerThread,CompletionPort,0,&dwThreadId); 
  39.     } 
  40.      
  41.     sListen = socket(AF_INET,SOCK_STREAM,0); 
  42.     memset(&local,0,sizeof(SOCKADDR_IN)); 
  43.     local.sin_family = AF_INET; 
  44.     local.sin_port = htons(PORT); 
  45.     local.sin_addr.s_addr = htonl(INADDR_ANY); 
  46.     bind(sListen,(SOCKADDR*)&local,sizeof(SOCKADDR_IN)); 
  47.     listen(sListen, 5); 
  48.     while(TRUE) 
  49.     { 
  50.         sCient = accept(sListen,(SOCKADDR*)&client,&iAddrSize); 
  51.         printf("Accept Client:%s:%d\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port)); 
  52.         CreateIoCompletionPort((HANDLE)sClient,CompletionPort,(DWORD)sClient,0); 
  53.         lpPetIoData=(LPPER_IO_OPERATION_DATA)HeapAlloc( 
  54.                               GetProcessHeap(), 
  55.                               HEAP_ZERO_MEMORY, 
  56.                               sizeof(PER_IO_OPERATION_DATA)); 
  57.         lpPerIoData->Buffer.len = MSGSIZE; 
  58.         lpPerIoData->Buffer.buf = lpPerIoData->szMessage; 
  59.         lpPerIoData->OpetationType = RECV_POSTED; 
  60.         WSARecv(sClient, 
  61.                 &lpPerIoData->Buffer, 
  62.                 1, 
  63.                 &lpPerIoData->NumberOfBytesRecvd, 
  64.                 &lpPerIoData->Flags, 
  65.                 &lpPerIoData->overlap, 
  66.                 NULL); 
  67.     } 
  68.      
  69.     PostQueuedCompletionStauts(CompletionPort,0xFFFFFFFF,0,NULL); 
  70.     CloseHandle(CompletionPort); 
  71.     closesocket(sListen); 
  72.     WSACleanup(); 
  73.     return 0;     
  74.  
  75. DWORD WINAPI WorkerThread(LPVOID lpParam) 
  76.     HANDLE CompletionPort = (HANDLE)lpParam; 
  77.     DWORD dwBytesTransferred; 
  78.     SOCKET sClient; 
  79.     LPPER_IO_OPERATION_DATA lpPerIoData = NULL; 
  80.     while(TRUE) 
  81.     { 
  82.         GetQueuedCompletionStatus(CompletionPort, 
  83.                                   &dwBytesTransferred, 
  84.                                   (DWORD*)sClient, 
  85.                                   (LPOVERLAPPED*)&lpPerIoData, 
  86.                                   INFINITE); 
  87.         if(dwBytesTransferred==0xFFFFFFFF) 
  88.         { 
  89.             return 0; 
  90.         } 
  91.         if(lpPerIoData->OpetationType==RECV_POSTED) 
  92.         { 
  93.             if(dwBytesTransferred==0) 
  94.             { 
  95.                 closesocket(sClient); 
  96.                 HeapFree(GetProcessHeap(),0,lpPerIoData); 
  97.             } 
  98.             else 
  99.             { 
  100.                 lpPerIoData->szMessage[dwBytesTransferred]='\0'
  101.                 send(sClient,lpPerIoData->szMessage,dwBytesTransferred,0); 
  102.                  
  103.                 memset(lpPerIoData,0,sizeof(PER_IO_OPERATION_DATA)); 
  104.                 lpPerIoData->Buffer.len = MSGSIZE; 
  105.                 lpPerIoData->Buffer.buf = lpPerIoData->szMessage; 
  106.                 lpPerIoData->OpetationType = RECV_POSTED; 
  107.                 WSARecv(sClient, 
  108.                         &lpPerIoData->Buffer, 
  109.                         1, 
  110.                         &lpPerIoData->NumberOfBytesRecvd, 
  111.                         &lpPerIoData->Flags, 
  112.                         &lpPerIoData->overlap, 
  113.                         NULL); 
  114.             } 
  115.         } 
  116.     } 
  117.     return 0; 

服务器端得主要流程: 

1.创建完成端口对象 

2.创建工作者线程(这里工作者线程的数量是按照CPU的个数来决定的,这样可以达到最佳性能) 

3.创建监听套接字,绑定,监听,然后程序进入循环 

4.在循环中,我做了以下几件事情: 

(1).接受一个客户端连接 

(2).将该客户端套接字与完成端口绑定到一起(还是调用CreateIoCompletionPort,但这次的作用不同),注意,按道理来讲,此时传递给CreateIoCompletionPort的第三个参数应该是一个完成键,一般来讲,程序都是传递一个单句柄数据结构的地址,该单句柄数据包含了和该客户端连接有关的信息,由于我们只关心套接字句柄,所以直接将套接字句柄作为完成键传递; 

(3).触发一个WSARecv异步调用,这次又用到了“尾随数据”,使接收数据所用的缓冲区紧跟在WSAOVERLAPPED对象之后,此外,还有操作类型等重要信息。 

 

在工作者线程的循环中,我们 

1.调用GetQueuedCompletionStatus取得本次I/O的相关信息(例如套接字句柄、传送的字节数、单I/O数据结构的地址等等) 

2.通过单I/O数据结构找到接收数据缓冲区,然后将数据原封不动的发送到客户端 

3.再次触发一个WSARecv异步操作 

你可能感兴趣的:(职场,模型,休闲)