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

I/O模型——完成端口


設計目的:

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

實現原理:

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

基本流程:


主要的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模型深刻分析服務器

相關文章
相關標籤/搜索