套接字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異步操作 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章