在網(wǎng)絡(luò)通訊中,由于網(wǎng)絡(luò)擁擠或一次發(fā)送的數(shù)據(jù)量過(guò)大等原因,經(jīng)常會(huì)發(fā)生交換的數(shù)據(jù)在短時(shí)間內(nèi)不能傳送完,收發(fā)數(shù)據(jù)的函數(shù)因此不能返回,這種現(xiàn)象叫做阻塞。 Winsock對(duì)有可能阻塞的函數(shù)提供了兩種處理方式:阻塞和非阻塞方式。
阻塞模式
在阻塞方式下,收發(fā)數(shù)據(jù)的函數(shù)在被調(diào)用后一直要到傳送完畢或者出錯(cuò)才能返回。在阻塞期間,被阻的函數(shù)不會(huì)斷調(diào)用系統(tǒng)函數(shù)GetMessage()來(lái)保持消息循環(huán)的正常進(jìn)行。
非阻塞模式
將一個(gè)套接字置為非阻塞模式之后, Winsock API調(diào)用會(huì)立即返回。一般這些調(diào)用都會(huì)“失敗”,并返回一個(gè)WSAEWOULDBLOCK。表明其操作在調(diào)用期間沒(méi)有時(shí)間完成。如在系統(tǒng)的輸入緩沖區(qū)中,并不存在等待的數(shù)據(jù),那recv調(diào)用就會(huì)返回WSAEWOULDBLOCK錯(cuò)誤。通常,我們需要重復(fù)調(diào)用同一個(gè)函數(shù),直至獲得一個(gè)成功返回代碼。這不是一個(gè)好的方法。通常采用Winsock的套接字I/O模型去處理。
套接字I/O模型共有五種類型,如下:
select(選擇)
WSAAsyncSelect(異步選擇)
WSAEventSelect(事件選擇)
overlapped(重疊)
completion port(完成端口)
*WSAAsyncSelect
Winsock通過(guò)WSAAsyncSelect()自動(dòng)地設(shè)置套接字處于非阻塞方式。使用WindowsSockets實(shí)現(xiàn)Windows網(wǎng)絡(luò)程序設(shè)計(jì)的關(guān)鍵就是它提供了對(duì)網(wǎng)絡(luò)事件基于消息的異步存取,用于注冊(cè)應(yīng)用程序感興趣的網(wǎng)絡(luò)事件。它請(qǐng)求Windows Sockets DLL在檢測(cè)到套接字上發(fā)生的網(wǎng)絡(luò)事件時(shí),向窗口發(fā)送一個(gè)消息。
int PASCAL FAR WSAAsyncSelect(SOCKET s,HWND hWnd,unsigned int wMsg,long lEvent);
hWnd:窗口句柄
wMsg:需要發(fā)送的消息
lEvent:事件(以下為事件的內(nèi)容)
值: 含義:
FD_READ 期望在套接字上收到數(shù)據(jù)(即讀準(zhǔn)備好)時(shí)接到通知
FD_WRITE 期望在套接字上可發(fā)送數(shù)據(jù)(即寫準(zhǔn)備好)時(shí)接到通知
FD_OOB 期望在套接字上有帶外數(shù)據(jù)到達(dá)時(shí)接到通知
FD_ACCEPT 期望在套接字上有外來(lái)連接時(shí)接到通知
FD_CONNECT 期望在套接字連接建立完成時(shí)接到通知
FD_CLOSE 期望在套接字關(guān)閉時(shí)接到通知
進(jìn)行異步選擇使用WSAAsyncSelect()函數(shù)時(shí),有以下幾點(diǎn)需要引起特別的注意:
?。B續(xù)使用兩次WSAAsyncSelect()函數(shù)時(shí),只有第二次設(shè)置的事件有效,如:
WSAAsyncSelect(s,hwnd,wMsg1,FD_READ);
WSAAsyncSelect(s,hwnd,wMsg2,FD_CLOSE);
這樣只有當(dāng)FD_CLOSE事件發(fā)生時(shí)才會(huì)發(fā)送wMsg2消息。
?。梢栽谠O(shè)置過(guò)異步選擇后通過(guò)再次調(diào)用WSAAsyncSelect(s,hwnd,0,0);的形式取消在套接字上所設(shè)置的異步事件。
?。甒indows Sockets DLL在一個(gè)網(wǎng)絡(luò)事件發(fā)生后,通常只會(huì)給相應(yīng)的應(yīng)用程序發(fā)送一個(gè)消息,而不能發(fā)送多個(gè)消息。但通過(guò)使用一些函數(shù)隱式地允許重發(fā)此事件的消息,這樣就可能再次接收到相應(yīng)的消息。
.在調(diào)用過(guò)closesocket()函數(shù)關(guān)閉套接字之后不會(huì)再發(fā)生FD_CLOSE事件。
對(duì)UDP協(xié)議,這些網(wǎng)絡(luò)事件主要為:
FD_READ 期望在套接字收到數(shù)據(jù)(即讀準(zhǔn)備好)時(shí)接收通知;
FD_WRITE 期望在套接字可發(fā)送數(shù)(即寫準(zhǔn)備好)時(shí)接收通知;
FD_CLOSE 期望在套接字關(guān)閉時(shí)接電通知
消息變量wParam指示發(fā)生網(wǎng)絡(luò)事件的套接字,變量1Param的低字節(jié)描述發(fā)生的網(wǎng)絡(luò)事件,高字包含錯(cuò)誤碼。如在窗口函數(shù)的消息循環(huán)中均加一個(gè)分支:
int ok=sizeof(SOCKADDR);
case wMsg;
switch(1Param)
{
case FD_READ: //套接字上讀數(shù)據(jù)
if(recvfrom(sr.lpPlayData[j],dwDataSize,0,(struct sockaddr FAR*)&there1,
(int FAR*)&ok)==SOCKET_ERROR0) {
MessageBox(hwnd,“數(shù)據(jù)接收失敗!”,“”,MB_OK);
return(FALSE);
}
case FD_WRITE: //套接字上寫數(shù)據(jù)
}
break;
*WSAEventSelect
事件通知模型要求在程序中針對(duì)使用的每個(gè)套接字創(chuàng)建一個(gè)事件對(duì)象,然后通過(guò)事件模式通知程序其套接字是否收到或發(fā)送的信息。一般來(lái)說(shuō)這種模式,一般就是通過(guò)類似調(diào)用waitformultipleObject一樣在一個(gè)線程中等待信號(hào)事件來(lái),來(lái)了就處理。具體調(diào)用的函數(shù)如下:
創(chuàng)建WSACreateEvent函數(shù).該函數(shù)的返回值是一個(gè)創(chuàng)建好的事件對(duì)象句柄。事件對(duì)象句柄完后,接下來(lái)將其與某個(gè)套接字關(guān)聯(lián)在一起,同時(shí)注冊(cè)自己感興趣的網(wǎng)絡(luò)事件類型,方法是調(diào)用WSAEventSelect函數(shù),對(duì)它的定義如下:
int WSAEventSelect (
SOCKET s, //需要非阻塞處理的套接字
WSAEVENT hEventObject, //WSACreateEvent 創(chuàng)建來(lái)的,關(guān)聯(lián)到socket
long lNetworkEvents
);
lNetworkEvents,對(duì)應(yīng)一個(gè)“位掩碼”,用于指定應(yīng)用程序感興趣的各種網(wǎng)絡(luò)事件類型的一個(gè)組合。要想獲知對(duì)這些事件類型的詳細(xì)說(shuō)明,請(qǐng)參考早先討論過(guò)的WSAAsyncSelect I/O模型。
為WSAEventSelect創(chuàng)建的事件擁有兩種工作狀態(tài),以及兩種工作模式。
兩種工作狀態(tài)分別是“已傳信”(signaled)和 “未傳信”(nonsignaled)。
工作模式則包括“人工”(manual reset)和“自動(dòng)”(auto reset)。
WSACreateEvent缺省時(shí)其信號(hào)狀態(tài)為0,且為人工設(shè)置,當(dāng)網(wǎng)絡(luò)事件觸發(fā)了與一個(gè)套接字關(guān)聯(lián)在一起的事件對(duì)象,其事件信號(hào)置1。在完成了一個(gè)I/O請(qǐng)求的處理之后,需要調(diào)用WSAResetEvent復(fù)位處理(置信號(hào)為0)。
一個(gè)套接字同一個(gè)事件對(duì)象句柄關(guān)聯(lián)在一起后,應(yīng)用程序便可開始I/O處理;方法是等待網(wǎng)絡(luò)事件觸發(fā)事件對(duì)象句柄的工作狀態(tài)。
一般而言,在等待網(wǎng)絡(luò)傳來(lái)事件時(shí),類似WaitforMultipleObject,其WSAWaitForMultipleEvents函數(shù)的設(shè)計(jì)宗旨便是用來(lái)等待一個(gè)或多個(gè)事件對(duì)象句柄,并在事先指定的一個(gè)或所有句柄進(jìn)入有信號(hào)狀態(tài)后,或在超過(guò)了一個(gè)規(guī)定的時(shí)間周期后,立即返回(線程往往在這里死等)。
下面是 WSAWaitForMultipleEvents函數(shù)的定義:
DWORD WSAWaitForMultipleEvents(
DWORD cEvents,
const WSAEVENT FAR *lphEvents,
BOOL fWaitAll,
DWORD dwTimeOUT,
BOOL fAlertable
);
其用法和WaitForMultipleObject類似。
cEvents和lphEvents參數(shù)定義了由WSAEVENT對(duì)象構(gòu)成的一個(gè)數(shù)組。在這個(gè)數(shù)組中,cEvents指定的是事件對(duì)象的數(shù)量,而lphEvents對(duì)應(yīng)的是一個(gè)指針,用于直接引用該數(shù)組。
要注意的是, WSAWaitForMultipleEvents只能支持由WSA_MAXIMUM_WAIT_EVENTS對(duì)象規(guī)定的一個(gè)最大值,在此定義成64個(gè)。故該I/O模型一次最多都只能支持64個(gè)套接字。假如想讓這個(gè)模型同時(shí)管理不止64個(gè)套接字,必須創(chuàng)建更多的工作者線程,以便等待更多的事件對(duì)象。
fWaitAl l 參數(shù)指定了WSAWaitForMultiple Events如何等待在事件數(shù)組中的對(duì)象。
=TRUE,那么只有等lphEvents數(shù)組內(nèi)包含的所有事件對(duì)象都處于有信號(hào)狀態(tài),函數(shù)才會(huì)返回;
=FALSE,任一個(gè)事件對(duì)象進(jìn)入有信號(hào)時(shí),函數(shù)就會(huì)返回。
dwTimeout參數(shù)規(guī)定了 WSAWaitForMultipleEvents最多可等待一個(gè)網(wǎng)絡(luò)事件發(fā)生有多長(zhǎng)時(shí)間。超過(guò)規(guī)定的時(shí)間,函數(shù)就會(huì)立即返回。并返回WSA_WAIT_TIMEOUT。如dwsTimeout設(shè)為WSA_INFIN ITE(永遠(yuǎn)等待),那么根據(jù)fWaiiAll或等待一個(gè)網(wǎng)絡(luò)事件或所有網(wǎng)絡(luò)事件都傳信號(hào)后,才能從該函數(shù)退出。
fAlertable,缺省設(shè)為FALSE。主要用于在重疊式I/O模型中.
當(dāng)設(shè)置fWaiAll=false,WaitForMultipleObject再有網(wǎng)絡(luò)事件時(shí),會(huì)返回一個(gè)值,指出造成函數(shù)返回的事件對(duì)象。根據(jù)WSAWaitForMultipleEvents的返回值,減去預(yù)定義值WSA_WAIT_EVENT_0,得到具體的引用值(即索引位置),程序便可用事件數(shù)組中已發(fā)信號(hào)的事件,檢索與那個(gè)事件對(duì)應(yīng)的套接字,知道了造成網(wǎng)絡(luò)事件的套接字后,調(diào)用 WSAEnumNetworkEvents函數(shù),調(diào)查發(fā)生了什么類型的網(wǎng)絡(luò)事件。該函數(shù)定義如下:
int WSAEnumNetworkEvents (
SOCKET s, //檢索該套接字
WSAEVENT hEventObject,
LPWSANETWORKEVENTS lpNetworkEvents
);
hEventObject參數(shù)則是可選的;它指定了一個(gè)事件句柄,對(duì)應(yīng)于打算重設(shè)的那個(gè)事件對(duì)象。由于我們的事件對(duì)象處在一個(gè)有信號(hào)狀態(tài),所以可將它傳入,令其自動(dòng)成為無(wú)信號(hào)狀態(tài)。
也可以采用使用 WSAResetEvent 函數(shù)復(fù)位事件信號(hào)。
lpNetworkEvents,就是返回的結(jié)果信息,它是一個(gè)指向WSANETWORKEVENTS結(jié)構(gòu)的指針,用于接收套接字上發(fā)生的網(wǎng)絡(luò)事件類型以及可能出現(xiàn)的任何錯(cuò)誤代碼。
其WSANETWORKEVENTS結(jié)構(gòu)的定義:
typedef struct _WSANETWORKEVENTS
{
long lNetworkEvents;
int iErrorCode[FD_MAX_EVENTS];
} WSANETWORKEVENTS, FAR * LPWSANETWORKEVENTS;
lNetworkEvents參數(shù)指定了一個(gè)值,對(duì)應(yīng)于套接字上發(fā)生的所有網(wǎng)絡(luò)事件類型。
注意一個(gè)事件進(jìn)入置1(有信號(hào))狀態(tài)時(shí),可能會(huì)同時(shí)發(fā)生多個(gè)網(wǎng)絡(luò)事件類型。如,一個(gè)忙的服務(wù)器可能同時(shí)收到FD_READ和FD_WRITE通知。 iErrorCode參數(shù)指定的是一個(gè)錯(cuò)誤代碼數(shù)組,同lNetworkEvents中的事件關(guān)聯(lián)在一起。針對(duì)每個(gè)網(wǎng)絡(luò)事件類型,都存在著一個(gè)特殊的事件索引,名字與事件類型的名字類似,只是要在事件名字后面添加一個(gè)“ _BIT”后綴字串即可。如,對(duì)FD_READ事件類型來(lái)說(shuō),iErrorCode數(shù)組的索引標(biāo)識(shí)符便是FD_READ_BIT。