青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

WisKeyのLullaby

huangwei.pro 『我失去了一只臂膀』「就睜開(kāi)了一只眼睛」

  C++博客 :: 首頁(yè) :: 聯(lián)系 :: 聚合  :: 管理
  12 Posts :: 0 Stories :: 23 Comments :: 0 Trackbacks

公告

“我該走哪條路?”
“這取決于你要去哪里。”
“我只想能到某個(gè)地方。”
“只要你走的夠遠(yuǎn),你始終能到達(dá)那個(gè)地方。”

Home: huangwei.pro
E-Mail: sir.huangwei [at] gmail.com
09.6 畢業(yè)于杭州電子科技大學(xué)
進(jìn)入網(wǎng)易杭州研究院工作至今

常用鏈接

留言簿(1)

我參與的團(tuán)隊(duì)

搜索

  •  

積分與排名

  • 積分 - 51756
  • 排名 - 447

最新評(píng)論

閱讀排行榜

評(píng)論排行榜

http://blog.huang-wei.com/2010/11/21/winsock-io/


Windows Socket IO 模型

套接字架構(gòu)

應(yīng)用程序使用Winsock與傳輸協(xié)議驅(qū)動(dòng)溝通時(shí)AFD.SYS負(fù)責(zé)緩沖區(qū)的管理。這就意味著當(dāng)一個(gè)程序調(diào)用send或者WSASend發(fā)送數(shù)據(jù)時(shí),數(shù)據(jù)將被復(fù)制到AFD.SYS它自己的內(nèi)部緩沖區(qū)中(依賴SO_SNDBUF的設(shè)置)WSASend調(diào)用立即返回。然后AFD.SYS在程序后臺(tái)將數(shù)據(jù)發(fā)送出去。當(dāng)然,如果程序想要處理一個(gè)比SO_SNDBUF設(shè)置的緩沖區(qū)需求更大的發(fā)送請(qǐng)求,WSASend的調(diào)用就會(huì)阻塞直到所有的數(shù)據(jù)都被發(fā)送出去。

類似的,從遠(yuǎn)程客戶端接收數(shù)據(jù)時(shí),只要SO_RCVBUF設(shè)置的緩沖區(qū)還沒(méi)有滿,AFD.SYS就會(huì)將數(shù)據(jù)復(fù)制進(jìn)它自己的緩沖區(qū)直到所有的發(fā)送都已完成。當(dāng)程序調(diào)用recv或者是WSARecv,數(shù)據(jù)就從AFD.SYS的緩沖區(qū)復(fù)制到了程序提供的緩沖區(qū)中了。

使用Winsock的時(shí)候還會(huì)間接碰到另外兩種資源的限制。第一個(gè)頁(yè)面鎖定的限制。注意重疊操作可能偶然性地以ERROR_INSUFFICIENT_RESOURCES調(diào)用失敗,這基本上意味著有太多的發(fā)送和接收操作在等待中。另外一個(gè)限制是操作系統(tǒng)的非分頁(yè)池(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默認(rèn)的就是阻塞模式。

在recv的時(shí)候,Socket會(huì)阻塞在那里,直到連接上有數(shù)據(jù)可讀,把數(shù)據(jù)讀到buffer里后recv函數(shù)才會(huì)返回,不然就會(huì)一直阻塞在那里。

如果在主線程中被阻塞,而數(shù)據(jù)遲遲沒(méi)有過(guò)來(lái),那么程序就會(huì)被鎖死。這樣的問(wèn)題可以用多線程解決,但是在有多個(gè)套接字連接的情況下,這不是一個(gè)好的選擇,擴(kuò)展性很差,而且也容易有鎖的問(wèn)題。線程過(guò)多,也導(dǎo)致上下文切換過(guò)于頻繁,導(dǎo)致系統(tǒng)變慢,而且大部分線程是處于非活動(dòng)狀態(tài)的話,這就大大浪費(fèi)了系統(tǒng)的資源。

非阻塞模型

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

調(diào)用ioctlsocket函數(shù)設(shè)置FIONBIO為1就轉(zhuǎn)為非阻塞模式。

當(dāng)recv和send函數(shù)沒(méi)有準(zhǔn)備好數(shù)據(jù)時(shí),函數(shù)不會(huì)阻塞,立即返回錯(cuò)誤值,用GetLastError返回的錯(cuò)誤碼為WSAEWOULDBLOCK,中文解釋為“無(wú)法立即完成一個(gè)非阻擋性套接字的操作”。

當(dāng)然,這里你可以用非阻塞模擬阻塞模式,就是用while循環(huán)不停調(diào)用recv,直到recv返回成功為止。這樣的效率也不高,但好處在于你能在沒(méi)接收到數(shù)據(jù)時(shí),有空進(jìn)行其他操作,或者直接Sleep。

Select模型

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

Select模型是非阻塞的,函數(shù)內(nèi)部自動(dòng)檢測(cè)WSAEWOULDBLOCK狀態(tài),還能有超時(shí)設(shè)定。對(duì)read,write,except三種事件進(jìn)行分別檢測(cè),except指帶外數(shù)據(jù)可讀取,read和write的定義是廣義的,accept,close等消息也納入到read。

Select函數(shù)使用fd_set結(jié)構(gòu),它的結(jié)構(gòu)非常的簡(jiǎn)單,只有一個(gè)數(shù)組和計(jì)數(shù)器。

Timeval結(jié)構(gòu)里可以設(shè)置超時(shí)的時(shí)間。

Select函數(shù)返回值表示集合中有事件觸發(fā)的sock總數(shù),其余操作使用fd_set的宏來(lái)完成。

#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);
// 等待事件觸發(fā),或超時(shí)返回
int ret = select(0, &fdread, NULL, NULL, &tv);
for (int i = 0; ret > 0 && i < nSock; i ++)
// 檢測(cè)哪個(gè)sock有事件觸發(fā)
if (FD_ISSET(socks[i], &fdread)) {
read_buf(socks[i]);
ret –;
}
}

其實(shí)select的原理就是對(duì)sock集合進(jìn)行掃描,有事件或者超時(shí)則退出,所以select的效率也是和sock數(shù)量成線性關(guān)系,而且需要我們自己循環(huán)檢查哪個(gè)sock有事件發(fā)生。

它的優(yōu)點(diǎn)是模型簡(jiǎn)單,過(guò)程清晰,容易管理,支持多個(gè)sock服務(wù)。缺點(diǎn)也很明顯,本質(zhì)還是個(gè)循環(huán)的改進(jìn)版本,而且fd_set里最多只能放64個(gè)sock,還有它無(wú)法很好的支持sock事件的先后順序。

WSAAsynSelect模型

WSAAsynSelect是Windows特有的,可以在一個(gè)套接字上接收以Windows消息為基礎(chǔ)的網(wǎng)絡(luò)事件通知。該模型的實(shí)現(xiàn)方法是通過(guò)調(diào)用WSAAsynSelect函數(shù)自動(dòng)將套接字設(shè)置(轉(zhuǎn)變)為非阻塞模式,并向Windows注冊(cè)一個(gè)或多個(gè)網(wǎng)絡(luò)事件lEvent,并提供一個(gè)通知時(shí)使用的窗口句柄hWnd。當(dāng)注冊(cè)的事件發(fā)生時(shí),對(duì)應(yīng)的窗口將收到一個(gè)基于消息的通知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;
// 創(chuàng)建窗口,綁定上WinProc
// 創(chuàng)建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的高字節(jié)包含了可能出現(xiàn)的任何的錯(cuò)誤代碼
// lParam的低字節(jié)指定已經(jīng)發(fā)生的網(wǎng)絡(luò)事件
// 發(fā)生錯(cuò)誤
if(WSAGETSELECTERROR(lParam)) {
closesocket…
}
// 事件觸發(fā)
switch( WSAGETSELECTEVENT(lParam) ) {
case FD_ACCEPT:
case FD_READ:
case FD_WRITE:
}
}
}

WSAAsyncSelect是模仿Windows消息機(jī)制來(lái)實(shí)現(xiàn)的,使用起來(lái)很方便,僅僅只是在消息處理中加入了對(duì)WM_SOCKET的處理,這樣就能嚴(yán)格得按先后順序處理sock事件。

MFC中的CSOCKET也采用了這個(gè)模型。

lEvent事件表:

FD_READ 應(yīng)用程序想要接收有關(guān)是否可讀的通知,以便讀入數(shù)據(jù)
FD_WRITE 應(yīng)用程序想要接收有關(guān)是否可寫的通知,以便寫入數(shù)據(jù)
FD_OOB 應(yīng)用程序想接收是否有帶外(OOB)數(shù)據(jù)抵達(dá)的通知
FD_ACCEPT 應(yīng)用程序想接收與進(jìn)入連接有關(guān)的通知
FD_CONNECT 應(yīng)用程序想接收與一次連接或者多點(diǎn)join操作完成的通知
FD_CLOSE 應(yīng)用程序想接收與套接字關(guān)閉有關(guān)的通知
FD_QOS 應(yīng)用程序想接收套接字“服務(wù)質(zhì)量”(QoS)發(fā)生更改的通知
FD_GROUP_QOS 應(yīng)用程序想接收套接字組“服務(wù)質(zhì)量”發(fā)生更改的通知(現(xiàn)在沒(méi)什么用處,為未來(lái)套接字組的使用保留)
FD_ROUTING_INTERFACE_CHANGE 應(yīng)用程序想接收在指定的方向上,與路由接口發(fā)生變化的通知
FD_ADDRESS_LIST_CHANGE 應(yīng)用程序想接收針對(duì)套接字的協(xié)議家族,本地地址列表發(fā)生變化的通知

只有在以下3種條件下,會(huì)發(fā)送FD_WRITE事件:

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

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

WSAEventSelect模型

WSAEventSelect模型類似WSAAsynSelect模型,但最主要的區(qū)別是網(wǎng)絡(luò)事件發(fā)生時(shí)會(huì)被發(fā)送到一個(gè)Event對(duì)象句柄,而不是發(fā)送到一個(gè)窗口。這樣你就可以使用Event對(duì)象的特性了。但WSAEventSelect模型明顯復(fù)雜很多。

它需要由以下函數(shù)一起完成。

// 1. 創(chuàng)建事件對(duì)象來(lái)接收網(wǎng)絡(luò)事件:
WSAEVENT WSACreateEvent( void );
// 2. 將事件對(duì)象與套接字關(guān)聯(lián),同時(shí)注冊(cè)事件,使事件對(duì)象的工作狀態(tài)從未傳信轉(zhuǎn)變未已傳信。
int WSAEventSelect( SOCKET s,WSAEVENT hEventObject,long lNetworkEvents );
// 3. I/O處理后,設(shè)置事件對(duì)象為未傳信
BOOL WSAResetEvent( WSAEVENT hEvent );
// 4. 等待網(wǎng)絡(luò)事件來(lái)觸發(fā)事件句柄的工作狀態(tài):
DWORD WSAWaitForMultipleEvents( DWORD cEvents,const WSAEVENT FAR * lphEvents, BOOL fWaitAll,DWORD dwTimeout, BOOLfAlertable );
// 5.  獲取網(wǎng)絡(luò)事件類型
int WSAEnumNetworkEvents( SOCKET s, WSAEVENT hEventObject, LPWSANETWORKEVENTS lpNetworkEvents );

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

sock和Event對(duì)象是對(duì)應(yīng)的,當(dāng)一個(gè)套接字有事件發(fā)生,WSAWaitForMultipleEvents返回相應(yīng)的值,通過(guò)這個(gè)值來(lái)索引這個(gè)套接字。 但它也和select一樣,在Event數(shù)組大小上也有限制,MAXIMUM_WAIT_OBJECTS的值為64。

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

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

Over-Lapped IO模型

它和之前模型不同的是,使用重疊模型的應(yīng)用程序通知緩沖區(qū)收發(fā)系統(tǒng)直接使用數(shù)據(jù),也就是說(shuō),如果應(yīng)用程序投遞了一個(gè)10KB大小的緩沖區(qū)來(lái)接收數(shù)據(jù),且數(shù)據(jù)已經(jīng)到達(dá)套接字,則該數(shù)據(jù)將直接被拷貝到投遞的緩沖區(qū)。之前的模型都是在套接字的緩沖區(qū)中,當(dāng)通知應(yīng)用程序接收后,在把數(shù)據(jù)拷貝到程序的緩沖區(qū)。

這種模型適用于除WindowsCE外的其他Windows平臺(tái),該模型是以Windows的重疊IO機(jī)制為基礎(chǔ),通過(guò)ReadFile和WriteFile,針對(duì)設(shè)備執(zhí)行IO操作。

早先這種機(jī)制是用于文件IO,在Socket IO和文件IO統(tǒng)一接口之后,這種機(jī)制也被引入Socket IO。但這類模型的實(shí)現(xiàn)就相對(duì)復(fù)雜多了。

有兩個(gè)方法可以實(shí)現(xiàn)重疊IO請(qǐng)求的完成情況(接到重疊操作完成的通知):

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

WSAOVERLAPPED

重疊結(jié)構(gòu)是不得不提的,之后的完成端口模型也需要用到。這個(gè)結(jié)構(gòu)等同于OVERLAPPED。

typedef struct _WSAOVERLAPPED {
DWORD Internal;
DWORD InternalHigh;
DWORD Offset;
DWORD OffsetHigh;
WSAEVENT hEvent; // 只關(guān)注這個(gè)參數(shù),用來(lái)關(guān)聯(lián)WSAEvent對(duì)象
} WSAOVERLAPPED, *LPWSAOVERLAPPED;

使用重疊結(jié)構(gòu),我們常用的send, sendto, recv, recvfrom也都要被WSASend, WSASendto, WSARecv, WSARecvFrom替換掉了,是因?yàn)樗鼈兊膮?shù)中都有一個(gè)Overlapped參數(shù)。

int WSARecv(
SOCKET s, // [in] 套接字
LPWSABUF lpBuffers, // [in,out] 接收緩沖區(qū),WSABUF的數(shù)組
DWORD dwBufferCount, // [in] 數(shù)組中WSABUF的數(shù)量
LPDWORD lpNumberOfBytesRecvd, // [out] 此刻函數(shù)所接收到的字節(jié)數(shù)
LPDWORD lpFlags,             // [in,out] 這里設(shè)置為0 即可
LPWSAOVERLAPPED lpOverlapped,  // [in] 綁定重疊結(jié)構(gòu)
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine

// [in] 完成例程中將會(huì)用到的參數(shù)
);

沒(méi)有錯(cuò)誤且收取立刻完成時(shí),返回值為0,否則是SOCKET_ERROR。常見(jiàn)的錯(cuò)誤碼是WSA_IO_PENDING,表示重疊操作正在進(jìn)行。相應(yīng)的其他函數(shù)也是類似參數(shù),具體參考MDSN。

獲取重疊操作的結(jié)果,由WSAWaitForMultipleEvents函數(shù)來(lái)完成。

BOOL WSAGetOverlappedResult(
SOCKET s, // [in] 套接字
LPWSAOVERLAPPED lpOverlapped, // [in] 要查詢的重疊結(jié)構(gòu)的指針
LPDWORD lpcbTransfer,// [out] 本次重疊操作的實(shí)際接收(或發(fā)送)的字節(jié)數(shù)
BOOL fWait, // [in] 設(shè)置為TRUE,除非重疊操作完成,否則函數(shù)不會(huì)返回
// 設(shè)置FALSE,而且操作仍處于掛起狀態(tài),那么函數(shù)就會(huì)返回FALSE,錯(cuò)誤為WSA_IO_INCOMPLETE
LPDWORD lpdwFlags // [out] 負(fù)責(zé)接收結(jié)果標(biāo)志
);

事件通知

事件等待函數(shù)和WaitForMultipleObjects類似。

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

返回值有這么幾個(gè):

WSA_WAIT_TIMEOUT 超時(shí),我們要繼續(xù)Wait
WSA_WAIT_FAILED 出現(xiàn)錯(cuò)誤
WAIT_IO_COMPLETION 一個(gè)或多個(gè)完成例程入隊(duì)列執(zhí)行
WSA_WAIT_EVENT_0 ~ (WSA_WAIT_EVENT_0 + cEvents – 1) 觸發(fā)的事件下標(biāo)

事件通知的重疊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請(qǐng)求
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. 重置事件對(duì)象
WSAResetEvent(EventArray[dwIndex]);

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

dosomething(…);

如果是服務(wù)端使用事件通知模型,則需要再起一個(gè)線程來(lái)循環(huán)Wait事件通知,主線程則接受請(qǐng)求的連接。

實(shí)際編碼過(guò)程中,要注意緩沖區(qū)不要搞錯(cuò),因?yàn)槿夹枰约簛?lái)管理,稍有不慎就容易寫臟數(shù)據(jù)和越界。還要注意WSARecv時(shí),可能立即有數(shù)據(jù)返回的情況,即返回值為0且NumberOfBytesRecvd > 0。

完成例程

完成例程(Completion Routine),不是完成端口。它是使用APC(Asynchronous Procedure Calls)異步回調(diào)函數(shù)來(lái)實(shí)現(xiàn),大致流程和事件通知模型差不多,只不過(guò)WSARecv注冊(cè)時(shí),加上了lpCompletionRoutine參數(shù)。

Void CALLBACK CompletionROUTINE(
DWORD dwError, // [in] 標(biāo)志咱們投遞的重疊操作完成的狀態(tài)
DWORD cbTransferred, // [in] 重疊操作期間,實(shí)際傳輸?shù)淖止?jié)量是多大
LPWSAOVERLAPPED lpOverlapped, // [in] 傳遞到最初IO調(diào)用的重疊結(jié)構(gòu)
DWORD dwFlags  // [in] 返回操作結(jié)束時(shí)可能用的標(biāo)志(一般沒(méi)用)
);

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

APC機(jī)制

ReadFileEx / WriteFileEx在發(fā)出IO請(qǐng)求的同時(shí),提供一個(gè)回調(diào)函數(shù)(APC過(guò)程),當(dāng)IO請(qǐng)求完成后,一旦線程進(jìn)入可告警狀態(tài),回調(diào)函數(shù)將會(huì)執(zhí)行。

以下五個(gè)函數(shù)能夠使線程進(jìn)入告警狀態(tài):

SleepEx

WaitForSingleObjectEx

WaitForMultipleObjectsEx

SignalObjectAndWait

MsgWaitForMultipleObjectsEx

線程進(jìn)入告警狀態(tài)時(shí),內(nèi)核將會(huì)檢查線程的APC隊(duì)列,如果隊(duì)列中有APC,將會(huì)按FIFO方式依次執(zhí)行。如果隊(duì)列為空,線程將會(huì)掛起等待事件對(duì)象。以后的某個(gè)時(shí)刻,一旦APC進(jìn)入隊(duì)列,線程將會(huì)被喚醒執(zhí)行APC,同時(shí)等待函數(shù)返回WAIT_IO_COMPLETION。

回到完成例程的話題上。

需要一個(gè)輔助線程,輔助線程的工作是判斷有沒(méi)有新的客戶端連接被建立,如果有,就為那個(gè)客戶端套接字激活一個(gè)異步的WSARecv操作,然后調(diào)用SleepEx使線程處于一種可警告的等待狀態(tài),以使得I/O完成后 CompletionROUTINE可以被內(nèi)核調(diào)用,而CompletionROUTINE會(huì)在當(dāng)初激活WSARecv異步操作的代碼的同一個(gè)線程之內(nèi)!而且調(diào)用SleepEx時(shí),需要把bAlertable參數(shù)設(shè)為TRUE,這樣當(dāng)有APC喚醒時(shí)立即調(diào)用完成例程,否則例程就不會(huì)被執(zhí)行。當(dāng)然也可以使用WSAWaitForMultipleEvents函數(shù),但這樣就需要一個(gè)事件對(duì)象。

從圖中就能看到CompletionROUTINE是在輔助線程(調(diào)用過(guò)WSARecv)里執(zhí)行的。

Completion Port模型

“完成端口”模型是迄今為止最為復(fù)雜的一種I/O模型。

假若一個(gè)應(yīng)用程序同時(shí)需要管理為數(shù)眾多的套接字,那么采用這種模型,往往可以達(dá)到最佳的系統(tǒng)性能!它能最大限度的減少上下文切換的同時(shí)最大限度的提高系統(tǒng)并發(fā)量。但不幸的是,該模型只適用于Windows NT和Windows 2000操作系統(tǒng)。

因其設(shè)計(jì)的復(fù)雜性,只有在你的應(yīng)用程序需要同時(shí)管理數(shù)百乃至上千個(gè)套接字的時(shí)候,而且希望隨著系統(tǒng)內(nèi)安裝的CPU數(shù)量的增多,應(yīng)用程序的性能也可以線性提升,才應(yīng)考慮采用“完成端口”模型。

要記住的一個(gè)基本準(zhǔn)則是,假如要為Windows NT或Windows 2000開(kāi)發(fā)高性能的服務(wù)器應(yīng)用,同時(shí)希望為大量套接字I/O請(qǐng)求提供服務(wù)(Web服務(wù)器便是這方面的典型例子),那么I/O完成端口模型便是最佳選擇!

完成端口是一種WINDOWS內(nèi)核對(duì)象。完成端口用于異步方式的重疊I/O。簡(jiǎn)單地,可以把完成端口看成系統(tǒng)維護(hù)的一個(gè)隊(duì)列,操作系統(tǒng)把重疊IO操作完成的事件通知放到該隊(duì)列里,由于是暴露 “操作完成”的事件通知,所以命名為“完成端口”(Completion Ports)。

完成端口內(nèi)部提供了線程池的管理,可以避免反復(fù)創(chuàng)建線程的開(kāi)銷,同時(shí)可以根據(jù)CPU的個(gè)數(shù)靈活的決定線程個(gè)數(shù),而且可以讓減少線程調(diào)度的次數(shù)從而提高性能。

它需要以下函數(shù)的支持,CreateIoCompletionPort函數(shù)用于創(chuàng)建和綁定完成端口。

HANDLE CreateIoCompletionPort(
HANDLE FileHandle, // [in] IO句柄對(duì)象,這里是套接字
HANDLE ExistingCompletionPort, // [in] 完成端口
ULONG_PTR CompletionKey, // [in] 自定義數(shù)據(jù)指針
DWORD NumberOfConcurrentThreads // [in] 最大線程數(shù),0為自動(dòng)
);

我們還需要類似WSAGetOverlappedResult的函數(shù)來(lái)獲取完成端口的狀態(tài)。

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

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

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

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

// 1. 參數(shù)設(shè)空,就能創(chuàng)建完成端口
HANDLE CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,NULL,0);
// 2. 創(chuàng)建工作線程
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請(qǐng)求(參照重疊IO)

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

測(cè)試圖例

來(lái)自于《Windows網(wǎng)絡(luò)編程》的數(shù)據(jù)。

  1. 阻塞模型難以應(yīng)對(duì)大規(guī)模的客戶連接,因?yàn)樗趧?chuàng)建線程上耗費(fèi)了太多的系統(tǒng)資源。因此,服務(wù)器創(chuàng)建太多的線程后,再調(diào)用CreateThread函數(shù)時(shí),將返回ERROR_NOT_ENOUGH_MEMORY的錯(cuò)誤,那些發(fā)出連接請(qǐng)求的客戶則收到WSAECONNREFUSED的錯(cuò)誤提示,表示連接的嘗試被拒絕。其并發(fā)處理量是極難突破的。
  2. 非阻塞模型和Select模型的性能要比阻塞模式稍好,但是占用了太多的CPU處理時(shí)間。瓶頸在于,fd_set集合的線性掃描上。還需要注意的一個(gè)問(wèn)題就是,非分頁(yè)池(即直接在物理內(nèi)存中分配的內(nèi)存)的使用極高。這是因?yàn)锳FD(Ancillary Function Driver,由afd.sys提供的支持Windows Sockets應(yīng)用程序的底層驅(qū)動(dòng)程序,其中運(yùn)行在內(nèi)核模式下afd.sys驅(qū)動(dòng)程序主要管理Winsock TCP/IP通信)和TCP都將使用I/O緩存,因?yàn)榉?wù)器讀取數(shù)據(jù)的速度是有限的,相對(duì)于CPU的處理速度而言,I/O基本是零字節(jié)的吞吐量。
  3. 基于Windows消息機(jī)制的WSAAsyncSelect模型能夠處理一定的客戶連接量,但是擴(kuò)展性也不是很好。因?yàn)橄⒈煤芸炀蜁?huì)阻塞,降低了消息處理的速度。在幾次測(cè)試中,服務(wù)器只能處理大約1/3的客戶端連接。過(guò)多的客戶端連接請(qǐng)求都將返回錯(cuò)誤提示碼WSAECONNREFUSED。上表中的數(shù)據(jù)可以發(fā)現(xiàn),對(duì)那些已經(jīng)建立的連接,其平均吞吐量也是極低的。
  4. 基于事件通知的WSAEventSelect模型表現(xiàn)得出奇的不錯(cuò)。在所有的測(cè)試中,大多數(shù)時(shí)候,服務(wù)器基本能夠處理所有的客戶連接,并且保持著較高的數(shù)據(jù)吞吐量。這種模型的缺點(diǎn)是,每當(dāng)有一個(gè)新連接時(shí),需要?jiǎng)討B(tài)管理線程池,因?yàn)槊總€(gè)線程只能夠等待64個(gè)事件對(duì)象。但最后,服務(wù)器不能再接受更多的連接,原因是WSAENOBUFS(無(wú)可用的緩沖區(qū)空間),套接字無(wú)法創(chuàng)建。另外,客戶端程序也達(dá)到了極限,不能維持已經(jīng)建立的連接。
  5. 事件通知的重疊I/O模型和WSAEventSelect模型在伸縮性上差不多。這兩種模型都依賴于等待事件通知的線程池,處理客戶通信時(shí),大量線程上下文的切換是它們共同的制約因素。重疊I/O模型和WSAEventSelect模型的測(cè)試結(jié)果很相似,都表現(xiàn)得不錯(cuò),直到線程數(shù)量超過(guò)極限。
  6. 例程通知的重疊I/O模型,性能和事件通知的重疊I/O模型相同,但因?yàn)橐韵聨讉€(gè)原因,也不是開(kāi)發(fā)高性能服務(wù)器的最佳選擇。首先,許多擴(kuò)展功能不允許使用APC完成通知。其次,由于APC在系統(tǒng)內(nèi)部特有的處理機(jī)制,應(yīng)用程序線程可能無(wú)限等待而得不到完成通知。當(dāng)一個(gè)線程處于“可警告狀態(tài)”時(shí),所有掛起的APC按照先進(jìn)先出的順序(FIFO)接受處理。
  7. 完成端口模型的是所有I/O模型中性能最佳的。內(nèi)存使用率(包括用戶分頁(yè)池和非分頁(yè)池)基本差不多。真正不同的地方,在于對(duì)CPU的占用。完成端口模型只占用了60%的CPU,但是在維持同樣規(guī)模的連接量時(shí),另外兩種模型(基于事件通知的重疊I/O模型和WSAEventSelect模型)占用更多的CPU。完成端口的另外一個(gè)明顯的優(yōu)勢(shì)是,它維持更大的吞吐量。

總結(jié)

客戶端的選擇

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

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

當(dāng)然,如果性能啥的都不需要考慮的,那簡(jiǎn)潔的Select模式值得被考慮。

服務(wù)端的選擇

既然是服務(wù)端,必然要需要性能不錯(cuò)的。

重疊IO模型可以使你在給定的時(shí)間段內(nèi)同時(shí)控制多個(gè)套接字。

但是,如果服務(wù)器在任意時(shí)間里都有大量IO請(qǐng)求,那就用完成端口模型。

參考

[1]      Windows核心編程;

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

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

[3]      手把手教你玩轉(zhuǎn)網(wǎng)絡(luò)編程模型之完成例程(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 非分頁(yè)池被 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


posted on 2010-11-21 12:10 威士忌 閱讀(7492) 評(píng)論(3)  編輯 收藏 引用

Feedback

# re: Windows Socket IO 模型 2010-11-22 08:48 billow
收藏。。。。  回復(fù)  更多評(píng)論
  

# re: Windows Socket IO 模型 2010-11-22 12:38 songsu
不錯(cuò)。  回復(fù)  更多評(píng)論
  

# re: Windows Socket IO 模型 2010-11-26 19:18 Skill
有點(diǎn)深,有時(shí)間慢慢消化吧  回復(fù)  更多評(píng)論
  


只有注冊(cè)用戶登錄后才能發(fā)表評(píng)論。
網(wǎng)站導(dǎo)航: 博客園   IT新聞   BlogJava   博問(wèn)   Chat2DB   管理


青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            国产精品卡一卡二| 欧美激情精品久久久久久变态 | 麻豆精品精华液| 欧美一区二区三区四区高清| 一区二区三区日韩精品视频| 亚洲视频一二| 亚洲免费一在线| 欧美在线观看网站| 久久综合久久综合久久综合| 免费短视频成人日韩| 欧美韩日精品| 国产精品久久久久久久久果冻传媒| 国产精品老女人精品视频| 国产日韩在线一区二区三区| 国产在线精品成人一区二区三区| 一区二区三区在线视频播放| 亚洲国产日韩综合一区| 一区二区三区四区五区精品| 欧美专区第一页| 欧美国产亚洲视频| 一区二区三区久久精品| 欧美一区二区三区在线| 久久久久久九九九九| 久久免费观看视频| 亚洲人成网在线播放| 亚洲黄色尤物视频| 亚洲欧美日韩另类精品一区二区三区| 欧美在线精品免播放器视频| 麻豆成人在线播放| 欧美日韩国产大片| 国产综合第一页| 一区二区电影免费观看| 久久久亚洲影院你懂的| 99精品欧美一区二区三区综合在线| 亚洲欧美国产不卡| 欧美电影在线播放| 国产一区二区精品久久91| 日韩亚洲在线| 免费观看成人www动漫视频| 一区二区三区.www| 欧美成人午夜77777| 国产日韩精品久久久| 亚洲美女诱惑| 毛片精品免费在线观看| 亚洲香蕉伊综合在人在线视看| 麻豆国产va免费精品高清在线| 国产精品区一区二区三区| 日韩视频在线一区二区三区| 久久成人免费日本黄色| 99精品99| 欧美国产视频日韩| 美女日韩在线中文字幕| 欧美高清日韩| 国产亚洲精品激情久久| 亚洲人成在线播放| 日韩午夜电影av| 亚洲一区久久| 日韩小视频在线观看专区| 蘑菇福利视频一区播放| 国产欧美日韩亚洲| 香蕉乱码成人久久天堂爱免费 | 亚洲欧美日韩精品一区二区| 另类综合日韩欧美亚洲| 亚洲一区二区三区在线观看视频| 欧美aaa级| 欧美一级网站| 黄色亚洲免费| 欧美怡红院视频| 亚洲欧美成人在线| 欧美国产日韩一区二区三区| 午夜久久黄色| 国产精品久久久久高潮| 午夜精品久久| 亚洲美女黄网| 国产精品福利久久久| 亚洲精选国产| 一本色道久久综合亚洲精品不 | 久久国产福利| 香蕉av福利精品导航| 欧美三级中文字幕在线观看| 国产偷久久久精品专区| 欧美一区二区在线视频| 亚洲深夜福利| 国产精品白丝黑袜喷水久久久| 99国产精品99久久久久久粉嫩| 欧美国产日韩一区二区三区| 蜜臀av性久久久久蜜臀aⅴ| 一区二区视频在线观看| 久久国产精品久久久久久久久久| 国产日韩精品一区| 欧美一区激情| 欧美一区二区三区在| 国产一区二区三区在线播放免费观看 | aa级大片欧美三级| 久久在线免费观看视频| 欧美自拍偷拍| 黑人一区二区| 蜜臀91精品一区二区三区| 久久婷婷国产综合精品青草| 亚洲春色另类小说| 久久精品在线播放| 久久久久久久综合| 欧美精品久久久久久久免费观看| 99精品免费| 久久人人爽人人爽爽久久| 狠狠色狠狠色综合日日五| 欧美一区二区在线| 久久精品国产综合| 亚洲国产精品一区制服丝袜| 一区二区精品国产| 久久香蕉国产线看观看av| 国产精品国产a级| 亚洲电影免费在线| 欧美激情性爽国产精品17p| 欧美在线视频免费播放| 夜夜嗨av一区二区三区| 国产午夜精品视频免费不卡69堂| 亚洲第一综合天堂另类专| 国产精品久久99| 欧美黄色免费网站| 国产精品一区二区久久国产| 欧美激情精品久久久久久久变态 | 国产精品久久久久高潮| 狠狠综合久久| 欧美在线在线| 国产精品综合色区在线观看| 正在播放亚洲一区| 欧美成人在线免费观看| 亚洲在线第一页| 国产精品对白刺激久久久| 夜夜嗨av一区二区三区网站四季av | 国产精品国产三级国产普通话蜜臀 | 国内精品久久久| 校园激情久久| 蜜月aⅴ免费一区二区三区 | 欧美在线精品免播放器视频| 国产人成精品一区二区三| 亚洲欧美日韩视频二区| 久久精品国产免费| 国产精品一区二区在线| 国产精品视频xxx| 亚洲一区二区三区高清不卡| 一区二区免费在线播放| 亚洲欧洲日产国产综合网| 亚洲第一中文字幕| 99re视频这里只有精品| 亚洲欧美三级在线| 蜜臀久久99精品久久久久久9| 米奇777在线欧美播放| 亚洲成人在线视频播放| 久久综合伊人77777| 亚洲精品一区二区网址 | 亚洲精品日韩欧美| 午夜精品久久久久久久男人的天堂| 欧美一区二区三区在线观看视频 | 亚洲第一搞黄网站| 欧美中文字幕| 亚洲国产欧美不卡在线观看| 雨宫琴音一区二区在线| 亚洲精品韩国| 久久综合久久久| 久久精品人人爽| 99精品视频一区二区三区| 亚洲视频1区2区| 国产精品久久网| 亚洲一区二区三区四区五区午夜| 在线亚洲成人| 亚洲欧美在线观看| 欧美jjzz| 亚洲男女毛片无遮挡| 亚洲精品一区久久久久久| 久久久亚洲高清| 亚洲精品视频免费观看| 欧美国产视频在线观看| 亚洲综合色丁香婷婷六月图片| 日韩午夜精品| 黄色日韩网站| 免费亚洲电影在线观看| 欧美成人精品h版在线观看| 亚洲电影在线播放| 亚洲国产91色在线| 亚洲精品视频在线观看网站 | 久久精品国产亚洲一区二区| 欧美日韩国产123| 一区二区日韩欧美| 亚洲欧美日韩精品久久久久| 亚洲深夜激情| 一本久道久久综合狠狠爱| 国产精品免费一区豆花| 欧美日韩天天操| 一区二区三区**美女毛片| 亚洲国产精品一区在线观看不卡 | 久久中文字幕导航| 国产在线精品一区二区中文| 亚洲第一偷拍| 国产欧美一区二区精品性| 欧美成年网站| 亚洲国产一区视频| 欧美乱在线观看| 久久久噜噜噜久久中文字幕色伊伊|