Windows下性能最好的I/O模型——完成端口


I/O模型——完成端口


設計目的:

  常見的網絡通信分為兩種:同步和異步。
  在同步通信中,每一次接受數據都會導致主線程的掛起,從而阻塞住了其他操作。為了解決這一問題,我們通常會采取同步通信+多線程的策略,即為每一個連入的Socket分配一個線程。然而隨着連入的Socket的數量的增加,線程的數量也在增加,這樣CPU則需要不停地進行線程的切換,因此難以成為高性能的服務器程序。
  異步通信則可以把接收數據這一操作交給內核,即在內核接收數據的時候,主線程可以不用被阻塞並且繼續執行其他操作,而一旦接收數據完成以后,再由內核通知主線程。而如何通知主線程是一個關鍵,不同的異步通信策略有着不同的通知方式。
  在這樣的情況下,完成端口這一I/O模型被提出,成為目前Windows下性能最好的I/O模型之一。
  

實現原理:

  首先根據CPU數量開好線程,當有用戶請求的時候,把這些請求加入一個特定的消息隊列中,而事先開好的線程則會排隊從這個消息隊列中獲取請求並作出處理。完成端口正是指這一消息隊列.
  

基本流程:


主要的API:

  • 創建完成端口
HANDLE iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0 ); 
HANDLE WINAPI CreateIoCompletionPort(
  _in_      HANDLE  FileHandle,  
  // Socket的句柄,置為INVALID_HANDLE_VALUE表示創建一個沒有和任何HANDLE有關系的完成端口
  
  _in_opt   HANDLE  ExistingCompletionPort,  
  // NULL表示新建一個完成端口
  
  _in_      ULONG_PTR CompletionKey, 
  // 完成鍵,創建完成端口時置為0 
  
  _in_      DWORD NumberOfConcurrentThreads 
  // 完成端口並發線程的數量,置0表示有多少個CPU就開多少個線程
);
  • 創建監聽Socket
初始化Socket庫...
...
listenSoc = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
...
綁定端口,並監聽...
  • 將監聽的Socket綁定到完成端口上,這里同樣使用HANDLE WINAPI CreateIoCompletionPort(...)這一API.
CreateIoCompletionPort(listenSoc, iocp, CompKey, 0);
HANDLE WINAPI CreateIoCompletionPort(
  _in_      HANDLE  FileHandle,  
  // 監聽Socket的句柄
  
  _in_opt   HANDLE  ExistingCompletionPort,  
  // 剛才創建的完成端口
  
  _in_      ULONG_PTR CompletionKey, 
  // 完成鍵,我們在綁定的同時為其分配一段內存空間,以存儲與這一Socket相關的信息,當網絡操作完成的時候,我們可以根據這段內存空間里面的信息分辨這是哪一個Socket 
  
  _in_      DWORD NumberOfConcurrentThreads 
  // 完成端口並發線程的數量,置0表示有多少個CPU就開多少個線程
);
  • 在監聽端口上投遞AcceptEX請求
    AcceptEX與傳統的Accept有三個主要不同點:
  1. AcceptEX采取異步方式,可以同時投遞多個請求,而Accept采取阻塞的方式,依次只能處理一個請求。
  2. AcceptEX會事先准備好Socket,當用戶請求連入的時候直接使用這一新的Socket,避免臨時創建Socket。
  3. AcceptEX接受連入請求的同時,我們可以附加一些數據,這樣我們就可以在接受用戶連入的同時,接受來自用戶的第一組數據。
BOOL AcceptEx ( 	
  SOCKET sListenSocket,  // 監聽Socket
  SOCKET sAcceptSocket,  // 事先准備好給新用戶的Socket
  PVOID lpOutputBuffer,  // 接受緩沖區
  DWORD dwReceiveDataLength,  // 用於存放用戶第一組數據的空間大小
  DWORD dwLocalAddressLength, // 本地地址的空間大小
  DWORD dwRemoteAddressLength, // 客戶端地址的空間大小
  LPDWORD lpdwBytesReceived, 
  LPOVERLAPPED lpOverlapped  
  // 重疊結構,每一個網絡操作都會對應一個重疊結構,相當於網絡操作的ID
);
  • 投遞接受數據請求
int WSARecv(
  SOCKET s,  // 接受數據的Socket
  LPWSABUF lpBuffers,  // 接收緩沖區 
  DWORD dwBufferCount,  // 置為1
  LPDWORD lpNumberOfBytesRecvd,  // 所接收到的字節數
  LPDWORD lpFlags,  // 置為0
  LPWSAOVERLAPPED lpOverlapped,  // 這個Socket對應的重疊結構
  NULL
);
  • 解析AcceptEX接收到的數據
    AcceptEX緩沖區里面保存着本地地址,客戶端地址以及客戶端發來的第一組數據,因此我們需要使用GetAcceptExSockAddrs()來解析這些數據.
void GetAcceptExSockaddrs(
  _In_   PVOID lpOutputBuffer,
  // AcceptEX中的緩沖區
  _In_   DWORD dwReceiveDataLength,
  // 用戶第一組數據的空間大小
  _In_   DWORD dwLocalAddressLength,
  // 本地地址的空間大小
  _In_   DWORD dwRemoteAddressLength,
  // 客戶端地址的空間大小
  _Out_  LPSOCKADDR *LocalSockaddr,
  // 本地地址
  _Out_  LPINT LocalSockaddrLength,
  // 實際本地地址的空間大小
  _Out_  LPSOCKADDR *RemoteSockaddr,
  // 客戶端地址
  _Out_  LPINT RemoteSockaddrLength
  // 實際客戶端地址的大小
);

參考: 1. 完成端口(CompletionPort)詳解 - 手把手教你玩轉網絡編程系列之三
    2. Overlapped模型深入分析


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM