常見的網絡通訊分爲兩種:同步和異步。
在同步通訊中,每一次接受數據都會致使主線程的掛起,從而阻塞住了其餘操做。爲了解決這一問題,咱們一般會採起同步通訊+多線程的策略,即爲每個連入的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庫... ... listenSoc = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED); ... 綁定端口,並監聽...
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就開多少個線程 );
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 );
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模型深刻分析服務器