Windows Socket IO 模型

Windows Socket IO 模型

套接字架構

應用程序使用Winsock與傳輸協議驅動溝通時AFD.SYS負責緩衝區的管理。這就意味着當一個程序調用send或者WSASend發送數據時,數據將被複制到AFD.SYS它自己的內部緩衝區中(依賴SO_SNDBUF的設置)WSASend調用立即返回。然後AFD.SYS在程序後臺將數據發送出去。當然,如果程序想要處理一個比SO_SNDBUF設置的緩衝區需求更大的發送請求,WSASend的調用就會阻塞直到所有的數據都被髮送出去。

類似的,從遠程客戶端接收數據時,只要SO_RCVBUF設置的緩衝區還沒有滿,AFD.SYS就會將數據複製進它自己的緩衝區直到所有的發送都已完成。當程序調用recv或者是WSARecv,數據就從AFD.SYS的緩衝區複製到了程序提供的緩衝區中了。

使用Winsock的時候還會間接碰到另外兩種資源的限制。第一個頁面鎖定的限制。注意重疊操作可能偶然性地以ERROR_INSUFFICIENT_RESOURCES調用失敗,這基本上意味着有太多的發送和接收操作在等待中。另外一個限制是操作系統的非分頁池(non-paged pool)的限制。

阻塞模型

int recv(
SOCKET s,
char* buf,
int len,
int flags
);
int send(
SOCKET s,
const char* buf,
int len,
int flags
);

這種方式最爲大家熟悉,Socket默認的就是阻塞模式。

在recv的時候,Socket會阻塞在那裏,直到連接上有數據可讀,把數據讀到buffer裏後recv函數纔會返回,不然就會一直阻塞在那裏。

如果在主線程中被阻塞,而數據遲遲沒有過來,那麼程序就會被鎖死。這樣的問題可以用多線程解決,但是在有多個套接字連接的情況下,這不是一個好的選擇,擴展性很差,而且也容易有鎖的問題。線程過多,也導致上下文切換過於頻繁,導致系統變慢,而且大部分線程是處於非活動狀態的話,這就大大浪費了系統的資源。

非阻塞模型

int ioctlsocket(
IN SOCKET s,
IN long cmd,
IN OUT u_long FAR * argp
);
#define FIONBIO /* set/clear non-blocking i/o */

調用ioctlsocket函數設置FIONBIO爲1就轉爲非阻塞模式。

當recv和send函數沒有準備好數據時,函數不會阻塞,立即返回錯誤值,用GetLastError返回的錯誤碼爲WSAEWOULDBLOCK,中文解釋爲“無法立即完成一個非阻擋性套接字的操作”。

當然,這裏你可以用非阻塞模擬阻塞模式,就是用while循環不停調用recv,直到recv返回成功爲止。這樣的效率也不高,但好處在於你能在沒接收到數據時,有空進行其他操作,或者直接Sleep。

Select模型

int select(
int nfds,
fd_set* readfds,
fd_set* writefds,
fd_set* exceptfds,
const struct timeval* timeout
);

Select模型是非阻塞的,函數內部自動檢測WSAEWOULDBLOCK狀態,還能有超時設定。對read,write,except三種事件進行分別檢測,except指帶外數據可讀取,read和write的定義是廣義的,accept,close等消息也納入到read。

Select函數使用fd_set結構,它的結構非常的簡單,只有一個數組和計數器。

Timeval結構裏可以設置超時的時間。

Select函數返回值表示集合中有事件觸發的sock總數,其餘操作使用fd_set的宏來完成。

#ifndef FD_SETSIZE
#define FD_SETSIZE      64
#endif /* FD_SETSIZE */

typedef struct fd_set {
u_int fd_count;               /* how many are SET? */
SOCKET  fd_array[FD_SETSIZE];   /* an array of SOCKETs */
} fd_set;

FD_CLR(s, *set)
FD_ISSET(s, *set)
FD_SET(s, *set)
FD_ZERO(*set)

Select模型流程如下:

fd_set fdread;
timeval tv = {1, 0};
while (1) {
// 初始化fd_set
FD_ZERO(&fdread);
for (int i = 0; i < nSock; i ++)
FD_SET(socks[i], &fdread);
// 等待事件觸發,或超時返回
int ret = select(0, &fdread, NULL, NULL, &tv);
for (int i = 0; ret > 0 && i < nSock; i ++)
// 檢測哪個sock有事件觸發
if (FD_ISSET(socks[i], &fdread)) {
read_buf(socks[i]);
ret –;
}
}

其實select的原理就是對sock集合進行掃描,有事件或者超時則退出,所以select的效率也是和sock數量成線性關係,而且需要我們自己循環檢查哪個sock有事件發生。

它的優點是模型簡單,過程清晰,容易管理,支持多個sock服務。缺點也很明顯,本質還是個循環的改進版本,而且fd_set裏最多隻能放64個sock,還有它無法很好的支持sock事件的先後順序。

WSAAsynSelect模型

WSAAsynSelect是Windows特有的,可以在一個套接字上接收以Windows消息爲基礎的網絡事件通知。該模型的實現方法是通過調用WSAAsynSelect函數自動將套接字設置(轉變)爲非阻塞模式,並向Windows註冊一個或多個網絡事件lEvent,並提供一個通知時使用的窗口句柄hWnd。當註冊的事件發生時,對應的窗口將收到一個基於消息的通知wMsg。

int WSAAsyncSelect(
SOCKET s,
HWND hWnd,
unsigned int wMsg,
long lEvent
);

WSAAsyncSelect模型流程如下:

#define WM_SOCKET WM_USER+1

int WINAPI WinMain(HINSTANCE hINstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
SOCKET Listen;
HWND Window;
// 創建窗口,綁定上WinProc
// 創建sock
WSAStartup(…);
Listen = Socket();
bind(…);
WSAAsyscSelect(Listen, Window, WM_SOCKET, FD_ACCEPT | FD_CLOSE);
listen(Listen, 5);
}

BOOL CALLBACK WinProc(HWND hDlg, WORD wMsg, WORD wParam, DWORD lParam) {
SOCKET Accept;
switch(wMsg) {
case WM_SOCKET:
// lParam的高字節包含了可能出現的任何的錯誤代碼
// lParam的低字節指定已經發生的網絡事件
// 發生錯誤
if(WSAGETSELECTERROR(lParam)) {
closesocket…
}
// 事件觸發
switch( WSAGETSELECTEVENT(lParam) ) {
case FD_ACCEPT:
case FD_READ:
case FD_WRITE:
}
}
}

WSAAsyncSelect是模仿Windows消息機制來實現的,使用起來很方便,僅僅只是在消息處理中加入了對WM_SOCKET的處理,這樣就能嚴格得按先後順序處理sock事件。

MFC中的CSOCKET也採用了這個模型。

lEvent事件表:

FD_READ 應用程序想要接收有關是否可讀的通知,以便讀入數據
FD_WRITE 應用程序想要接收有關是否可寫的通知,以便寫入數據
FD_OOB 應用程序想接收是否有帶外(OOB)數據抵達的通知
FD_ACCEPT 應用程序想接收與進入連接有關的通知
FD_CONNECT 應用程序想接收與一次連接或者多點join操作完成的通知
FD_CLOSE 應用程序想接收與套接字關閉有關的通知
FD_QOS 應用程序想接收套接字“服務質量”(QoS)發生更改的通知
FD_GROUP_QOS 應用程序想接收套接字組“服務質量”發生更改的通知(現在沒什麼用處,爲未來套接字組的使用保留)
FD_ROUTING_INTERFACE_CHANGE 應用程序想接收在指定的方向上,與路由接口發生變化的通知
FD_ADDRESS_LIST_CHANGE 應用程序想接收針對套接字的協議家族,本地地址列表發生變化的通知

只有在以下3種條件下,會發送FD_WRITE事件:

  1. 使用connect。連接首次被建立。
  2. 使用accept。套接字被接受。
  3. 使用send,sendto。

它的缺點就是,每個sock事件處理需要一個窗口句柄,如果sock很多的情況下,資源和性能可想而知了。

WSAEventSelect模型

WSAEventSelect模型類似WSAAsynSelect模型,但最主要的區別是網絡事件發生時會被髮送到一個Event對象句柄,而不是發送到一個窗口。這樣你就可以使用Event對象的特性了。但WSAEventSelect模型明顯複雜很多。

它需要由以下函數一起完成。

// 1. 創建事件對象來接收網絡事件:
WSAEVENT WSACreateEvent( void );
// 2. 將事件對象與套接字關聯,同時註冊事件,使事件對象的工作狀態從未傳信轉變未已傳信。
int WSAEventSelect( SOCKET s,WSAEVENT hEventObject,long lNetworkEvents );
// 3. I/O處理後,設置事件對象爲未傳信
BOOL WSAResetEvent( WSAEVENT hEvent );
// 4. 等待網絡事件來觸發事件句柄的工作狀態:
DWORD WSAWaitForMultipleEvents( DWORD cEvents,const WSAEVENT FAR * lphEvents, BOOL fWaitAll,DWORD dwTimeout, BOOLfAlertable );
// 5.  獲取網絡事件類型
int WSAEnumNetworkEvents( SOCKET s, WSAEVENT hEventObject, LPWSANETWORKEVENTS lpNetworkEvents );

WSACreateEvent其實跟CreateEvent的效果類似,返回的WSAEVENT類型其實就是HANDLE類型,所以可以直接使用CreateEvent創建特殊的Event。

sock和Event對象是對應的,當一個套接字有事件發生,WSAWaitForMultipleEvents返回相應的值,通過這個值來索引這個套接字。 但它也和select一樣,在Event數組大小上也有限制,MAXIMUM_WAIT_OBJECTS的值爲64。

有了Event對象的支持,signaled/non-signaled和manual reset/auto reset的概念也就可以應用到程序裏,這樣能使sock事件處理的方式比較豐富靈活。而且它也能嚴格按先後順序處理sock事件。

閃電郵PushMail的處理就是WSAEventSelect模型。

Over-Lapped IO模型

它和之前模型不同的是,使用重疊模型的應用程序通知緩衝區收發系統直接使用數據,也就是說,如果應用程序投遞了一個10KB大小的緩衝區來接收數據,且數據已經到達套接字,則該數據將直接被拷貝到投遞的緩衝區。之前的模型都是在套接字的緩衝區中,當通知應用程序接收後,在把數據拷貝到程序的緩衝區。

這種模型適用於除WindowsCE外的其他Windows平臺,該模型是以Windows的重疊IO機制爲基礎,通過ReadFile和WriteFile,針對設備執行IO操作。

早先這種機制是用於文件IO,在Socket IO和文件IO統一接口之後,這種機制也被引入Socket IO。但這類模型的實現就相對複雜多了。

有兩個方法可以實現重疊IO請求的完成情況(接到重疊操作完成的通知):

  1. 事件對象通知(event object notification)。
  2. 完成例程(completion routines)。注意,這裏並不是完成端口。

WSAOVERLAPPED

重疊結構是不得不提的,之後的完成端口模型也需要用到。這個結構等同於OVERLAPPED。

typedef struct _WSAOVERLAPPED {
DWORD Internal;
DWORD InternalHigh;
DWORD Offset;
DWORD OffsetHigh;
WSAEVENT hEvent; // 只關注這個參數,用來關聯WSAEvent對象
} WSAOVERLAPPED, *LPWSAOVERLAPPED;

使用重疊結構,我們常用的send, sendto, recv, recvfrom也都要被WSASend, WSASendto, WSARecv, WSARecvFrom替換掉了,是因爲它們的參數中都有一個Overlapped參數。

int WSARecv(
SOCKET s, // [in] 套接字
LPWSABUF lpBuffers, // [in,out] 接收緩衝區,WSABUF的數組
DWORD dwBufferCount, // [in] 數組中WSABUF的數量
LPDWORD lpNumberOfBytesRecvd, // [out] 此刻函數所接收到的字節數
LPDWORD lpFlags,             // [in,out] 這裏設置爲0 即可
LPWSAOVERLAPPED lpOverlapped,  // [in] 綁定重疊結構
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine

// [in] 完成例程中將會用到的參數
);

沒有錯誤且收取立刻完成時,返回值爲0,否則是SOCKET_ERROR。常見的錯誤碼是WSA_IO_PENDING,表示重疊操作正在進行。相應的其他函數也是類似參數,具體參考MDSN。

獲取重疊操作的結果,由WSAWaitForMultipleEvents函數來完成。

BOOL WSAGetOverlappedResult(
SOCKET s, // [in] 套接字
LPWSAOVERLAPPED lpOverlapped, // [in] 要查詢的重疊結構的指針
LPDWORD lpcbTransfer,// [out] 本次重疊操作的實際接收(或發送)的字節數
BOOL fWait, // [in] 設置爲TRUE,除非重疊操作完成,否則函數不會返回
// 設置FALSE,而且操作仍處於掛起狀態,那麼函數就會返回FALSE,錯誤爲WSA_IO_INCOMPLETE
LPDWORD lpdwFlags // [out] 負責接收結果標誌
);

事件通知

事件等待函數和WaitForMultipleObjects類似。

DWORD WSAWaitForMultipleEvents(
DWORD cEvents, // [in] 等候事件的總數量
const WSAEVENT* lphEvents, // [in] 事件數組的指針
BOOL fWaitAll, // [in] 是否等待所有事件
DWORD dwTimeout, // [in] 超時時間
BOOL fAlertable // [in] 在完成例程中會用到這個參數
);

返回值有這麼幾個:

WSA_WAIT_TIMEOUT 超時,我們要繼續Wait
WSA_WAIT_FAILED 出現錯誤
WAIT_IO_COMPLETION 一個或多個完成例程入隊列執行
WSA_WAIT_EVENT_0 ~ (WSA_WAIT_EVENT_0 + cEvents – 1) 觸發的事件下標

事件通知的重疊IO模型大致流程如下:

// 1. 建立並初始化buf和overlap
WSAOVERLAPPED Overlap;
WSABUF DataBuf;
char* SendBuf = new char[BufLen];
DWORD Flags = 0;

DataBuf.len = BufLen;
DataBuf.buf = SendBuf;
Overlap.hEvent = EventArray[dwEventTotal ++] = WSACreateEvent();

// 2. 在套接字上投遞WSARecv請求
int ret = WSARecv(Sock, &DataBuf, 1, &NumberOfBytesRecvd,
&Flags, &Overlap, NULL);
if (ret == SOCKET_ERROR && WSAGetLastError() != WSA_IO_PENDING)
error_handle(…);

// 3. 等待事件通知
DWORD dwIndex = WSAWaitForMultipleEvents(dwEventTotal,EventArray,     FALSE, WSA_INFINITE, FALSE);
if (dwIndex == WSA_WAIT_FAILED || dwIndex == WSA_WAIT_TIMEOUT)
error_handle(…);
dwIndex -= WSA_WAIT_EVENT_0;

// 4. 重置事件對象
WSAResetEvent(EventArray[dwIndex]);

// 5. 取得重疊調用的返回狀態
DWORD dwBytesTransferred;
WSAGetOverlappedResult(Sock, Overlap, &dwBytesTransferred, TRUE, &Flags);
if (dwBytesTransferred == 0)
closesocket(Sock);

dosomething(…);

如果是服務端使用事件通知模型,則需要再起一個線程來循環Wait事件通知,主線程則接受請求的連接。

實際編碼過程中,要注意緩衝區不要搞錯,因爲全都需要自己來管理,稍有不慎就容易寫髒數據和越界。還要注意WSARecv時,可能立即有數據返回的情況,即返回值爲0且NumberOfBytesRecvd > 0。

完成例程

完成例程(Completion Routine),不是完成端口。它是使用APC(Asynchronous Procedure Calls)異步回調函數來實現,大致流程和事件通知模型差不多,只不過WSARecv註冊時,加上了lpCompletionRoutine參數。

Void CALLBACK CompletionROUTINE(
DWORD dwError, // [in] 標誌咱們投遞的重疊操作完成的狀態
DWORD cbTransferred, // [in] 重疊操作期間,實際傳輸的字節量是多大
LPWSAOVERLAPPED lpOverlapped, // [in] 傳遞到最初IO調用的重疊結構
DWORD dwFlags  // [in] 返回操作結束時可能用的標誌(一般沒用)
);

但完成例程有一個比較隱晦的地方,就是APC機制本身。

APC機制

ReadFileEx / WriteFileEx在發出IO請求的同時,提供一個回調函數(APC過程),當IO請求完成後,一旦線程進入可告警狀態,回調函數將會執行。

以下五個函數能夠使線程進入告警狀態:

SleepEx

WaitForSingleObjectEx

WaitForMultipleObjectsEx

SignalObjectAndWait

MsgWaitForMultipleObjectsEx

線程進入告警狀態時,內核將會檢查線程的APC隊列,如果隊列中有APC,將會按FIFO方式依次執行。如果隊列爲空,線程將會掛起等待事件對象。以後的某個時刻,一旦APC進入隊列,線程將會被喚醒執行APC,同時等待函數返回WAIT_IO_COMPLETION。

回到完成例程的話題上。

需要一個輔助線程,輔助線程的工作是判斷有沒有新的客戶端連接被建立,如果有,就爲那個客戶端套接字激活一個異步的WSARecv操作,然後調用SleepEx使線程處於一種可警告的等待狀態,以使得I/O完成後 CompletionROUTINE可以被內核調用,而CompletionROUTINE會在當初激活WSARecv異步操作的代碼的同一個線程之內!而且調用SleepEx時,需要把bAlertable參數設爲TRUE,這樣當有APC喚醒時立即調用完成例程,否則例程就不會被執行。當然也可以使用WSAWaitForMultipleEvents函數,但這樣就需要一個事件對象。

從圖中就能看到CompletionROUTINE是在輔助線程(調用過WSARecv)裏執行的。

Completion Port模型

“完成端口”模型是迄今爲止最爲複雜的一種I/O模型。

假若一個應用程序同時需要管理爲數衆多的套接字,那麼採用這種模型,往往可以達到最佳的系統性能!它能最大限度的減少上下文切換的同時最大限度的提高系統併發量。但不幸的是,該模型只適用於Windows NT和Windows 2000操作系統。

因其設計的複雜性,只有在你的應用程序需要同時管理數百乃至上千個套接字的時候,而且希望隨着系統內安裝的CPU數量的增多,應用程序的性能也可以線性提升,才應考慮採用“完成端口”模型。

要記住的一個基本準則是,假如要爲Windows NT或Windows 2000開發高性能的服務器應用,同時希望爲大量套接字I/O請求提供服務(Web服務器便是這方面的典型例子),那麼I/O完成端口模型便是最佳選擇!

完成端口是一種WINDOWS內核對象。完成端口用於異步方式的重疊I/O。簡單地,可以把完成端口看成系統維護的一個隊列,操作系統把重疊IO操作完成的事件通知放到該隊列裏,由於是暴露 “操作完成”的事件通知,所以命名爲“完成端口”(Completion Ports)。

完成端口內部提供了線程池的管理,可以避免反覆創建線程的開銷,同時可以根據CPU的個數靈活的決定線程個數,而且可以讓減少線程調度的次數從而提高性能。

它需要以下函數的支持,CreateIoCompletionPort函數用於創建和綁定完成端口。

HANDLE CreateIoCompletionPort(
HANDLE FileHandle, // [in] IO句柄對象,這裏是套接字
HANDLE ExistingCompletionPort, // [in] 完成端口
ULONG_PTR CompletionKey, // [in] 自定義數據指針
DWORD NumberOfConcurrentThreads // [in] 最大線程數,0爲自動
);

我們還需要類似WSAGetOverlappedResult的函數來獲取完成端口的狀態。

BOOL GetQueuedCompletionStatus(
HANDLE CompletionPort, // [in] 完成端口
LPDWORD lpNumberOfBytes, // [out] 此次IO操作的字節數
PULONG_PTR lpCompletionKey, // [out] 自定義數據指針,CreateIoCompletionPort初始化的
LPOVERLAPPED* lpOverlapped, // [out] 投遞請求時的重疊結構指針
DWORD dwMilliseconds // [in] 超時設置
);

還有PostQueuedCompletionStatus函數,能模擬一個完成的重疊I/O操作。我們可以當成類似PostMessage的函數,以此控制工作線程。

BOOL PostQueuedCompletionStatus(
HANDLE CompletionPort, // [in] 完成端口
DWORD dwNumberOfBytesTransferred, // [in] 此次IO操作的字節數
ULONG_PTR dwCompletionKey, // [in] 自定義數據指針
LPOVERLAPPED lpOverlapped // [in] 重疊結構指針
);

完成端口模型大致流程如下:

// 1. 參數設空,就能創建完成端口
HANDLE CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,NULL,0);
// 2. 創建工作線程
DWORD dwThreadId;
SYSTEM_INFO sysinfo;
GetSystemInfo(&sysinfo);
for (int i = 0; i < sysinfo.dwNumberOfProcessors; i++)
CreateThread(NULL, 0, iocp_work_thread, CompletionPort, 0, &dwThreadId);

// 3. 建立並初始化buf和overlap(參照重疊IO)

// 4. 將套接字綁定到完成端口
CreateIoCompletionPort((HANDLE)Sock,CompletionPort,Sock,0);

// 5. 在套接字上投遞WSARecv請求(參照重疊IO)

// 6. 在工作線程中取本次I/O的相關信息
GetQueuedCompletionStatus(CompletionPort,&dwBytesTransferred,
(DWORD*)&Sock,(LPOVERLAPPED*)&lpPerIOData,INFINITE);
if (dwBytesTransferred == 0)
closesocket(Sock);
dosomething(…);

測試圖例

來自於《Windows網絡編程》的數據。

  1. 阻塞模型難以應對大規模的客戶連接,因爲它在創建線程上耗費了太多的系統資源。因此,服務器創建太多的線程後,再調用CreateThread函數時,將返回ERROR_NOT_ENOUGH_MEMORY的錯誤,那些發出連接請求的客戶則收到WSAECONNREFUSED的錯誤提示,表示連接的嘗試被拒絕。其併發處理量是極難突破的。
  2. 非阻塞模型和Select模型的性能要比阻塞模式稍好,但是佔用了太多的CPU處理時間。瓶頸在於,fd_set集合的線性掃描上。還需要注意的一個問題就是,非分頁池(即直接在物理內存中分配的內存)的使用極高。這是因爲AFD(Ancillary Function Driver,由afd.sys提供的支持Windows Sockets應用程序的底層驅動程序,其中運行在內核模式下afd.sys驅動程序主要管理Winsock TCP/IP通信)和TCP都將使用I/O緩存,因爲服務器讀取數據的速度是有限的,相對於CPU的處理速度而言,I/O基本是零字節的吞吐量。
  3. 基於Windows消息機制的WSAAsyncSelect模型能夠處理一定的客戶連接量,但是擴展性也不是很好。因爲消息泵很快就會阻塞,降低了消息處理的速度。在幾次測試中,服務器只能處理大約1/3的客戶端連接。過多的客戶端連接請求都將返回錯誤提示碼WSAECONNREFUSED。上表中的數據可以發現,對那些已經建立的連接,其平均吞吐量也是極低的。
  4. 基於事件通知的WSAEventSelect模型表現得出奇的不錯。在所有的測試中,大多數時候,服務器基本能夠處理所有的客戶連接,並且保持着較高的數據吞吐量。這種模型的缺點是,每當有一個新連接時,需要動態管理線程池,因爲每個線程只能夠等待64個事件對象。但最後,服務器不能再接受更多的連接,原因是WSAENOBUFS(無可用的緩衝區空間),套接字無法創建。另外,客戶端程序也達到了極限,不能維持已經建立的連接。
  5. 事件通知的重疊I/O模型和WSAEventSelect模型在伸縮性上差不多。這兩種模型都依賴於等待事件通知的線程池,處理客戶通信時,大量線程上下文的切換是它們共同的制約因素。重疊I/O模型和WSAEventSelect模型的測試結果很相似,都表現得不錯,直到線程數量超過極限。
  6. 例程通知的重疊I/O模型,性能和事件通知的重疊I/O模型相同,但因爲以下幾個原因,也不是開發高性能服務器的最佳選擇。首先,許多擴展功能不允許使用APC完成通知。其次,由於APC在系統內部特有的處理機制,應用程序線程可能無限等待而得不到完成通知。當一個線程處於“可警告狀態”時,所有掛起的APC按照先進先出的順序(FIFO)接受處理。
  7. 完成端口模型的是所有I/O模型中性能最佳的。內存使用率(包括用戶分頁池和非分頁池)基本差不多。真正不同的地方,在於對CPU的佔用。完成端口模型只佔用了60%的CPU,但是在維持同樣規模的連接量時,另外兩種模型(基於事件通知的重疊I/O模型和WSAEventSelect模型)佔用更多的CPU。完成端口的另外一個明顯的優勢是,它維持更大的吞吐量。

總結

客戶端的選擇

爲了能在一定程度上提升性能,建議使用重疊IO模型或者WSAEventSelect模型。

如果是窗口程序,且socket不多的情況下,可以使用WSAAsyncSelect模型。

當然,如果性能啥的都不需要考慮的,那簡潔的Select模式值得被考慮。

服務端的選擇

既然是服務端,必然要需要性能不錯的。

重疊IO模型可以使你在給定的時間段內同時控制多個套接字。

但是,如果服務器在任意時間裏都有大量IO請求,那就用完成端口模型。

參考

[1]      Windows核心編程;

[2]      手把手教你玩轉SOCKET模型之重疊I/O篇;

http://dev.csdn.net/htmls/39/39122.html

[3]      手把手教你玩轉網絡編程模型之完成例程(Completion Routine)篇;

http://blog.csdn.net/PiggyXP/archive/2009/02/19/3910726.aspx

[4]      Windows Sockets 2.0: Write Scalable Winsock Apps Using Completion Ports;

http://msdn.microsoft.com/zh-cn/magazine/cc302334(en-us).aspx

[5]      Inside I/O Completion Ports;

http://hi.baidu.com/jrckkyy/blog/item/401422527c131b070df3e37b.html

[6]      Windows 2000 非分頁池被 Afd.sys 耗盡;

http://support.microsoft.com/kb/296265/zh-cn

[7]      WinSock五種I/O模型的性能分析;

http://www.rover12421.com/2010/04/02/winsock%E4%BA%94%E7%A7%8Dio%E6%A8%A1%E5%9E%8B%E7%9A%84%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90.html

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