三、事件選擇模型(WSAEventSelect)

█ 事件選擇(WSAEventSelect)模型是另一個有用的異步 I/O 模型。和 WSAAsyncSelect 模型類似的是,
它也允許應用程序在一個或多個套接字上,接收以事件爲基礎的網絡事件通知,最主要的差別在於網絡事件會投

遞至一個事件對象句柄,而非投遞到一個窗口例程。


█ 事件通知模型要求我們的應用程序針對使用的每一個套接字,首先創建一個事件對象。
創建方法是調用 WSACreateEvent 函數,它的定義如下:
WSAEVENT WSACreateEvent(void);


█ WSACreateEvent 函數的返回值很簡單,就是一個創建好的事件對象句柄,接下來必須將其與某個套接字關聯在一起,

同時註冊自己感興趣的網絡事件類型(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(
  __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(...);
MyEvent = EventArray[Index - WSA_WAIT_EVENT_0];

█ 知道了造成網絡事件的套接字後,接下來可調用 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]);
	}
}

完成了對 WSANETWORKEVENTS 結構中的事件的處理之後,我們的應用程序應在所有可用的套接字上,繼續等待更多的網絡事件。

用例:

UINT CServerDlg::ThreadProc(LPVOID pParam)
{
	ASSERT(pParam);
	sockaddr_in clientAddr = {0};
	///////////////////////////////////////
	TCHAR szBuf[MAX_BUF_SIZE] = {0};
	SOCKET ArrSocket[WSA_MAXIMUM_WAIT_EVENTS] = {0};
	WSAEVENT ArrEvent[WSA_MAXIMUM_WAIT_EVENTS] = {0};
	DWORD dwTotal = 0, dwIndex = 0;
	WSANETWORKEVENTS m_NetWorkEvents = {0};
	/////////////////////////////////////////////////////
	CServerDlg *pThis = (CServerDlg *)pParam;
	pThis->WinSockInit();
	pThis->m_SockListen = INVALID_SOCKET;
	pThis->m_SockListen = socket(AF_INET , SOCK_STREAM , IPPROTO_TCP);
	if ( pThis->m_SockListen == INVALID_SOCKET ) {
		AfxMessageBox(_T("新建Socket失敗!"));
		goto __Error_End;
	}

	sockaddr_in service;
	service.sin_family = AF_INET;
	service.sin_addr.s_addr = INADDR_ANY;
	service.sin_port = htons(9527);

	if ( bind(pThis->m_SockListen, (sockaddr*)&service, sizeof(sockaddr_in)) == SOCKET_ERROR ) {
		AfxMessageBox(_T("綁定端口失敗!"));
		goto __Error_End;
	}

	WSAEVENT m_ListenEvent = WSACreateEvent();

	WSAEventSelect(pThis->m_SockListen, m_ListenEvent, FD_ACCEPT | FD_CLOSE);

	if( listen(pThis->m_SockListen, SOMAXCONN) == SOCKET_ERROR ) {
		AfxMessageBox(_T("監聽失敗!"));
		goto __Error_End;
	}

	ArrSocket[dwTotal] = pThis->m_SockListen;
	ArrEvent[dwTotal] = m_ListenEvent;
	++dwTotal;

	while (TRUE) {
		dwIndex = WSAWaitForMultipleEvents(dwTotal, ArrEvent, FALSE, 100, FALSE);
		if (dwIndex == WSA_WAIT_TIMEOUT) {
			continue;
		}
		WSAEnumNetworkEvents(ArrSocket[dwIndex-WSA_WAIT_EVENT_0], ArrEvent[dwIndex-WSA_WAIT_EVENT_0], &m_NetWorkEvents);

		// accept the client;
		if (m_NetWorkEvents.lNetworkEvents & FD_ACCEPT) {
			if (m_NetWorkEvents.iErrorCode[FD_ACCEPT_BIT] != 0) {
				continue;
			}
			pThis->m_SockClient = accept(ArrSocket[dwIndex-WSA_WAIT_EVENT_0], NULL, NULL);
			if (pThis->m_SockClient == INVALID_SOCKET) {
				continue;
			}
			WSAEVENT newEvent = WSACreateEvent();
			WSAEventSelect(pThis->m_SockClient, newEvent, FD_READ | FD_WRITE | FD_CLOSE);

			ArrSocket[dwTotal] = pThis->m_SockClient;
			ArrEvent[dwTotal] = newEvent;
			++dwTotal;
		}

		// recv the data;
		if (m_NetWorkEvents.lNetworkEvents & FD_READ) {
			if (m_NetWorkEvents.iErrorCode[FD_READ_BIT] != 0) {
				continue;
			}
			recv(ArrSocket[dwIndex-WSA_WAIT_EVENT_0], (char *)szBuf, MAX_BUF_SIZE, 0);
			pThis->ShowMsg(szBuf);
		}

		// send the data;
		if (m_NetWorkEvents.lNetworkEvents & FD_WRITE) {
			if (m_NetWorkEvents.iErrorCode[FD_WRITE_BIT] != 0) {
				continue;
			}
		}

		// close the socket;
		if (m_NetWorkEvents.lNetworkEvents & FD_CLOSE) {
			if (m_NetWorkEvents.iErrorCode[FD_CLOSE_BIT] != 0) {
				continue;
			}
			closesocket(ArrSocket[dwIndex-WSA_WAIT_EVENT_0]);
			//more works;
			//remove the socket and the event form the array, decrement the dwTotal;
			//your works;
		}
	}

__Error_End:
	if (pThis->m_SockListen != INVALID_SOCKET) {
		closesocket(pThis->m_SockListen);
	}

	WSACleanup();
	return TRUE;
}


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