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