I/O模型(非同步事件)
非同步事件
WSAEventSelect模型是WindowsSockets提供的另外一個有用的非同步I/O模型。該模型允許一個或多個套接字上接收以事件為基礎的網路事件通知。Windows Sockets應用程式在建立套接字後,呼叫WSAEventSlect()函式,將一個事件物件與網路事件集合關聯在一起。當網路事件發生時,應用程式以事件的形式接收網路事件通知。和 WSAAsyncSelect 模型類似的是,它也允許應用程式在一個或多個套接字上,接收以事件為基礎的網路事件通知; 最主要的差別在於網路事件會投遞至一個事件物件控制代碼,而非投遞到一個視窗例程。 從應用程式接收網路事件通知的方式來說,WSAEventSelect模型與WSAAsyncSelect模型都是被動的,當網路事件發生時,系統通知應用程式。然而select模型是主動的,應用程式主動呼叫該函式看是否發生了網路事件。
WSAEventSelect模型的實現
WASEventSelect模型的核心是WSAEventSelect()函式。在應用程式中,呼叫該函式為套接字註冊感興趣的網路事件。當網路事件發生時,應用程式以事件的形式接收網路事件通知。 應用WSAEventSelect模型開發Windows Sockets應用程式時,需要用到 WSAEventSelect()
WSACreateEvent()
WSAResetEvent()
WSAWaitForMultipleEvents() 函式。
WSAEventSelect 函式的返回值很簡單,就是一個建立好的事件物件控制代碼,接下來必須將其與某個套接字關聯在一起,同時註冊自己感興趣的網路事件型別(FD_READ、FD_WRITE、FD_ACCEPT、FD_CONNECT、FD_CLOSE等),
方法是呼叫 WSAEventSelect 函式,其定義如下: int WSAEventSelect( __in SOCKET s, __in WSAEVENT hEventObject, __in long lNetworkEvents );
● s 引數代表感興趣的套接字;
● hEventObject 引數指定要與套接字關聯在一起的事件物件—用WSACreateEvent 取得的那一個;
● lNetworkEvents 引數則對應一個“位掩碼”,用於指定應用程式感興趣的各種網路事件型別的一個組合。
WSACreateEvent 建立的事件有兩種工作狀態,以及兩種工作模式。
●工作狀態分別是“已傳信”(signaled)和“未傳信”(nonsignaled)。
●工作模式則包括“人工重設”(manual reset)和“自動重設”(auto reset)。
WSACreateEvent 開始是在一種未傳信的工作狀態,並用一種人工重設模式,來建立事件控制代碼。隨著網路事件觸發了與一個套接字關聯在一起的事件物件,工作狀態便會從“未傳信”轉變成“已傳信”。 由於事件物件是在一種人工重設模式中建立的,所以在完成了一個I/O請求的處理之後,我們的應用程式需要負責將工作狀態從已傳信更改為未傳信。要做到這一點,可呼叫 WSAResetEvent 函式,對它的定義如下:
BOOL WSAResetEvent WSAResetEvent( __in WSAEVENT hEvent );
● 該函式唯一的引數便是一個事件控制代碼;基於呼叫是成功還是失敗,會分別返回TRUE或FALSE。 應用程式完成了對一個事件物件的處理後,便應呼叫WSACloseEvent函式,釋放由事件控制代碼使用的系統資源。
對 WSACloseEvent 函式的定義如下: BOOL WSACloseEvent( __in WSAEVENT hEvent );
●該函式也要拿一個事件控制代碼作為自己唯一的引數,並會在成功後返回TRUE,失敗後返回FALSE。
一個套接字同一個事件物件控制代碼關聯在一起後,應用程式便可開始I/O處理;
方法是等待網路事件觸發事件物件控制代碼的工作狀態。WSAWaitForMultipleEvents 函式的設計宗旨便是用來等待一個或多個事件物件控制代碼,並在事先指定的一個或所有控制代碼進入“已傳信”狀態後,或在超過了一個規定的時間週期後,立即返回。
下面是 WSAWaitForMultipleEvents 函式的定義: DWORD WSAWaitForMultipleEvents( __in DWORD cEvents, __in const WSAEVENT* lphEvents, __in BOOL fWaitAll, __in DWORD dwTimeout, __in BOOL fAlertable );
● cEvents 和 lphEvents 引數定義了由 WSAEVENT 物件構成的一個數組。在這個陣列中,cEvents指定的是事件物件的數量,而lphEvents對應的是一個指標,用於直接引用該陣列。
●要注意的是,WSAWaitForMultipleEvents 只能支援由 WSA_MAXIMUM_WAIT_EVENTS 物件規定的一個最大值,在此定義成64個。因此,針對發出 WSAWaitForMultipleEvents 呼叫的每個執行緒,該 I/O 模型一次最多都只能支援64個套接字。假如想讓這個模型同時管理不止64個套接字,必須建立額外的工作者執行緒,以便等待更多的事件物件。
● fWaitAll 引數指定了 WSAWaitForMultipleEvents 如何等待在事件陣列中的物件。若設為TRUE,那麼只有等 lphEvents 陣列內包含的所有事件物件都已進入“已傳信”狀態,函式才會返回;但若設為FALSE,任何一個事件物件進入“已傳信”狀態,函式就會返回。就後一種情況來說,返回值指出了到底是哪個事件物件造成了函式的返回。通常,應用程式應將該引數設為 FALSE,一次只為一個套接字事件提供服務。
● dwTimeout引數規定了 WSAWaitForMultipleEvents 最多可等待一個網路事件發生有多長時間,以毫秒為單位,這是一項“超時”設定。超過規定的時間,函式就會立即返回,即使由 fWaitAll 引數規定的條件尚未滿足也如此。考慮到它對效能造成的影響,應儘量避免將超時值設為0。假如沒有等待處理的事件,WSAWaitForMultipleEvents 便會返回 WSA_WAIT_TIMEOUT。如 dwTimeout 設為 WSAINFINITE(永遠等待),那麼只有在一個網路事件傳信了一個事件物件後,函式才會返回。
● fAlertable 引數,在我們使用 WSAEventSelect 模型的時候,它是可以忽略的,且應設為 FALSE。該引數主要用於在重疊式 I/O 模型中,在完成例程的處理過程中使用。 若 WSAWaitForMultipleEvents 收到一個事件物件的網路事件通知,便會返回一個值,指出造成函式返回的事件物件。這樣一來,我們的應用程式便可引用事件陣列中已傳信的事件,並檢索與那個事件對應的套接字,判斷到底是在哪個套接字上,發生了什麼網路事件型別。對事件陣列中的事件進行引用時,應該用 WSAWaitForMultipleEvents 的返回值,減去預定義的值 WSA_WAIT_EVENT_0,得到具體的引用值(即索引位置)。 如下例所示: Index = WSAWaitForMultipleEvents(...);
知道了造成網路事件的套接字後,接下來可呼叫 WSAEnumNetworkEvents 函式,調查發生了什麼型別的網路事件。
該函式定義如下: int WSAEnumNetworkEvents( __in SOCKET s, __in WSAEVENT hEventObject, __out LPWSANETWORKEVENTS lpNetworkEvents );
● s 引數對應於造成了網路事件的套接字。
● hEventObject 引數則是可選的;它指定了一個事件控制代碼,對應於打算重設的那個事件物件。由於我們的事件物件處在一個“已傳信”狀態,所以可將它傳入,令其自動成為“未傳信”狀態。如果不想用hEventObject引數來重設事件,那麼可使用WSAResetEvent 函式,該函式之前已經討論過了。
● 引數 lpNetworkEvents,代表一個指標,指向 WSANETWORKEVENTS 結構,用於接收套接字上發生的網路事件型別以及可能出現的任何錯誤程式碼。 WSANETWORKEVENTS 結構的定義如下: typedef struct _WSANETWORKEVENTS { long lNetworkEvents; int iErrorCode[FD_MAX_EVENTS]; } WSANETWORKEVENTS, *LPWSANETWORKEVENTS; ● lNetworkEvents 引數指定了一個值,對應於套接字上發生的所有網路事件型別(FD_READ、FD_WRITE 等)。 注意:一個事件進入傳信狀態時,可能會同時發生多個網路事件型別。例如,一個繁忙的伺服器應用可能同時收到 FD_READ 和 FD_WRITE 通知。
● iErrorCode 引數指定的是一個錯誤程式碼陣列,同 lNetworkEvents 中的事件關聯在一起。 針對每個網路事件型別,都存在著一個特殊的事件索引,名字與事件型別的名字類似,只是要在事件名字後面新增一個“_BIT”字尾字串即可。
例如,對 FD_READ 事件型別來說,iErrorCode 陣列的索引識別符號便是 FD_READ_BIT。下述程式碼片斷對此進行了闡釋(針對FD_READ事件): if (NetwordEvents.lNetworkEvents & FD_READ) { if (NetworkEvents.iErrorCode[FD_READ_BIT] != 0) { printf("FD_READ failed with error %d\n", NetworkEvents.iErrorCode[FD_READ_BIT]); continue; } RECV } 完成了對 WSANETWORKEVENTS 結構中的事件的處理之後,我們的應用程式應在所有可用的套接字上,繼續等待更多的網路事件。
WSAEventSelect模型的優勢和不足
優勢:可以在一個非視窗的Windows Sockets程式中,實現多個套接字的管理。
不足:
1.每個WSAEventSelect模型最多隻能管理64個套接字。當應用程式中需要管理多於64個套接字時,就需要額外建立執行緒。
2.由於使用該模型開發套接字應用程式需要呼叫幾個相關函式才能完成。因此,該模型增加了開發的難度,增加了開發人員的編碼量。從這個角度講,該模型不如WSAAysnceSelect模型方便。
程式碼示例:
#include "stdafx.h"
#include "UDPNet.h"
UDPNet::UDPNet(IMediator *pMediator)
{
m_sockListen = NULL;
m_hThread = NULL;
m_bFlagQuit = true;
m_pMediator = pMediator;
m_nEventNum = 0;
}
UDPNet::~UDPNet()
{}
bool UDPNet::InitNetWork()
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
/* Use the MAKEWORD(lowbyte, highbyte) macro declared in Windef.h */
wVersionRequested = MAKEWORD(2, 2);
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0) {
/* Tell the user that we could not find a usable */
/* Winsock DLL. */
return false;
}
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
UnInitNetWork();
return false;
}
//2.僱個人 -- 建立套接字(與外界通訊介面)-
m_sockListen = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
m_sockbak = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
if(m_sockListen == INVALID_SOCKET || INVALID_SOCKET == m_sockbak)
{
UnInitNetWork();
return false;
}
//改變socket 廣播屬性
BOOL bval = TRUE;
setsockopt(m_sockListen,SOL_SOCKET,SO_BROADCAST,(const char*)&bval,sizeof(bval));
//改變socket 屬性
u_long argp = true;
ioctlsocket(m_sockListen,FIONBIO,&argp);
ioctlsocket(m_sockbak,FIONBIO,&argp);
//3.選個地方 -- 繫結--(IP,埠號)
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(_DEF_PORT);
addr.sin_addr.S_un.S_addr =INADDR_ANY /*inet_addr("127.0.0.1")*/;
if(SOCKET_ERROR == bind(m_sockListen,(const sockaddr *)&addr,sizeof(addr)))
{
UnInitNetWork();
return false;
}
sockaddr_in addrbak;
addrbak.sin_family = AF_INET;
addrbak.sin_port = htons(1245);
addrbak.sin_addr.S_un.S_addr =INADDR_ANY /*inet_addr("127.0.0.1")*/;
if(SOCKET_ERROR == bind(m_sockbak,(const sockaddr *)&addrbak,sizeof(addrbak)))
{
UnInitNetWork();
return false;
}
//向Windows註冊
WSAEVENT wEvent = WSACreateEvent(); //人工事件 --無訊號
WSAEVENT wEvent2 = WSACreateEvent(); //人工事件 --無訊號
if(!WSAEventSelect(m_sockListen,wEvent,FD_READ|FD_WRITE))
{
m_arySocket[m_nEventNum] = m_sockListen;
m_aryEvent[m _nEventNum] = wEvent;
m_nEventNum++;
}
if(!WSAEventSelect(m_sockbak,wEvent2,FD_READ|FD_WRITE))
{
m_arySocket[m_nEventNum] = m_sockbak;
m_aryEvent[m_nEventNum] = wEvent2;
m_nEventNum++;
}
//recvfrom --建立執行緒
m_hThread = (HANDLE)_beginthreadex(NULL,0,&ThreadProc,this,0,0);
return true;
}
unsigned __stdcall UDPNet::ThreadProc( void * lpvoid)
{
UDPNet *pthis = (UDPNet*)lpvoid;
char szbuf[_DEF_SIZE] = {0};
sockaddr_in addrClient;
int nSize = sizeof(addrClient);
int nRelRecvNum;
int nIndex;
WSANETWORKEVENTS we;
while(pthis->m_bFlagQuit)
{
//觀察事件狀態
nIndex = WSAWaitForMultipleEvents(pthis->m_nEventNum,pthis->m_aryEvent,FALSE,100,TRUE);
if(nIndex ==WSA_WAIT_TIMEOUT )
continue;
nIndex -= WSA_WAIT_EVENT_0;
//判斷什麼事件
if( WSAEnumNetworkEvents(pthis->m_arySocket[nIndex],pthis->m_aryEvent[nIndex],&we))
continue;
if(we.lNetworkEvents & FD_READ)
{
nRelRecvNum = recvfrom(pthis->fdsets.fd_array[i],szbuf,_DEF_SIZE,0,(sockaddr*)&addrClient,&nSize);
if(nRelRecvNum >0)
{
//交給中介者去處理
pthis->m_pMediator->DealData(szbuf,addrClient.sin_addr.S_un.S_addr);
}
}
if(we.lNetworkEvents & FD_WRITE)
}
return 0;
}
//bool UDPNet::SelectSocket()
//{
//
//
//
// if(!FD_ISSET(sock,&fdsets))
// return false;
//
// return true;
//
//}
void UDPNet::UnInitNetWork()
{
m_bFlagQuit = false;
if(m_hThread)
{
if(WAIT_TIMEOUT == WaitForSingleObject(m_hThread,100))
TerminateThread(m_hThread,-1);
CloseHandle(m_hThread);
m_hThread = NULL;
}
WSACleanup();
if(m_sockListen)
{
closesocket(m_sockListen);
m_sockListen = NULL;
}
if(m_sockbak)
{
closesocket(m_sockbak);
m_sockbak = NULL;
}
CMyWnd::DeleteMyWnd();
}
bool UDPNet::SendData(long lSendIp,char *szbuf,int nlen)
{
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(_DEF_PORT);
addr.sin_addr.S_un.S_addr = lSendIp;
if(!szbuf || nlen <=0)
return false;
if(sendto(m_sockListen,szbuf,nlen,0,(const sockaddr*)&addr,sizeof(addr))<=0)
return false;
return true;
}