【網路程式設計】之九、事件選擇WSAEventSelect
WSAEventSelect模型是類似於WSAAsyncSelect模型的另一個有用的非同步I/O模型。它允許應用程式在一個或者多個套接字上接收以事件為基礎的網路事件。 在這裡,最主要的差別是在於網路事件會投遞到一個事件物件控制代碼。並不是投遞到一個視窗。
我們使用事件模型前,我們的應用程式針對使用的每一個套接字首先要建立一個事件物件:
- WSAEVENT WSACreateEvent(void);
下面是註冊自己感興趣的網路事件型別:
- int WSAEventSelect(
- _In_ SOCKET s, //代表感興趣的套接字
-
_In_ WSAEVENT hEventObject,//指定要與套接字關聯在一起的事件物件。用WSACreateEvent取得。
- _In_ long lNetworkEvents );//對應一個 位掩碼 用於指定應用程式感興趣的各種網路事件型別的一個組合。
當我們用WSACreateEvent建立了事件,他有兩種工作狀態和兩種工作模式:
1、工作狀態:已傳信signaled 和 未傳信nonsignaled。
2、工作模式:人工重置manual reset 和 自動重置auto reset。
剛開始的時候,wsacreateevent是一種未傳信的工作狀態,並且是人工重置的模式來建立一個控制代碼。 但是我們網路事件觸發了與一個套接字關聯在一起的事件物件,工作狀態便會從“未傳信”轉變為“已傳信”,由於事件物件是 一種人工重置模式建立的,所以在完成了衣蛾I/O請求處理後,我們的應用程式需要負責將工作狀態改變為“未傳信”。 要這樣我們需要一個函式:
- BOOL WSAResetEvent(
- __in WSAEVENT hEvent
- );
當我們完成一個事件物件的處理後,我們要呼叫WSACloseEvent函式,釋放由事件控制代碼使用的系統資源:
- BOOL WSACloseEvent(
- __in WSAEVENT hEvent
- );
這個函式也是隻有一個引數:事件控制代碼。 成功返回TRUE,失敗返回FALSE;
當我們的一個套接字和一個事件控制代碼關聯在一起後,應用程式就開始I/O處理,通過WSAWaitForMultipleEvents函式等待網路事件來出發事件物件控制代碼的工作狀態。(這個函式用來等待一個或者多個事件物件控制代碼,並在事先指定的一個或所有控制代碼進入“已傳信”狀態後,並在超過一個規定的事件週期後立即返回)
- DWORD WSAWaitForMultipleEvents(
- __in DWORD cEvents,//和下面的引數一起頂一個了由WSAEVENT物件構成的一個數組,陣列中cEvents指定的是事件物件的數量,lphEvents對應的是一個指標,用於直接引用該陣列
- __in const WSAEVENT* lphEvents,
- __in BOOL fWaitAll,//指定了這個函式如何等待在事件陣列中的物件
- __in DWORD dwTimeout,//規定了這個函式最多可等待一個網路事件發生有多少時間,毫秒為單位,超時規定的事件會立即返回函式。
- __in BOOL fAlertable//在用WSAEventSelect模型的時候,可以忽略它,要設為FALSE。 這個引數主要用於在重疊I/O模型中。
- );
來看一個第三個引數fWaitAll。 如果這個引數你設為TRUE,那麼直有等待lphEvents陣列內包含的所有事件物件都進入“已傳信”狀態函式才返回。 如果你設為FALSE,那麼任何一個事件物件進入“已傳信”狀態,函式就會返回。
通常應用程式應將這個引數設為FALSE,一次只為一個套接字事件提供服務。
第四個引數dwTimeout要注意的是儘量避免將超時值設為0, 加入沒有等待處理的事件,WSAWaitForMultipleEvents便會返回WSA_WAIT_TIMEOUT。
你將這個引數設為:WSAINFINITE(永久等待),那麼只有當在一個網路事件傳信了一個事件物件後函式才會返回。
當WSAWaitForMultipleEvents 收到一個事件物件的網路事件通知,便會返回一個值。指出造成函式返回的事件物件。 我們的應用程式就可以引用事件陣列中已傳信的事件,並檢索與那個事件對應的套接字,判斷到底是在那個套接字上,發生了什麼網路事件型別。對於事件陣列中的事件進行引用時,應該用WSAWaitForMultipleEvents的返回值 減去預定義的WSA_WAIT_EVENT_0得到具體的引用值:
- Index = WSAWaitForMultipleEvents(...);
- MyEvent = EventArray[Index - WSA_WAIT_EVENT_0];
當我們知道了造成網路事件的套接字以後,下面可以呼叫WSAEnumNetworkEvents函式,調查發生了什麼型別的網路事件:
- int WSAEnumNetworkEvents(
- __in SOCKET s,//對應於造成網路時間的套接字
- __in WSAEVENT hEventObject,//可選引數,制定了一個事件控制代碼,對應於打算重設的那個事件物件。 我們的事件物件本來是"已傳信“狀態,你將它傳入,令其自動成為”未傳信“狀態。 如果你不想用這個引數來重設事件,你可以用函式WSAResetEvent。
- __out LPWSANETWORKEVENTS lpNetworkEvents//一個指標,指向WSANETWORKEVENTS結構。接收套接字上發生的網路事件型別以及可能出現的任何錯誤程式碼。
- );
- typedefstruct _WSANETWORKEVENTS {
- long lNetworkEvents; //指定一個值,對應於套接字上發生的所有網路事件型別。
- int iErrorCode[FD_MAX_EVENTS];//制定一個錯誤程式碼陣列。與lNetworkEvents中的事件關聯在一起,對每個網路事件型別都存在著一個特殊的事件索引,於事件型別的名字類似,只要在事件名字後面新增一個”_BIT"字尾字串就好。
- } WSANETWORKEVENTS, *LPWSANETWORKEVENTS;
看下面的例項:
- 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結構中的事件的處理之後,我們的應用程式應在所有可用的套接字上繼續等待更多的網路事件。
下面來看一下使用步驟:
1、建立一個socket。
2、建立一個event。
3、用WSAEventSelect將socket,和event關聯起來。lNetworkEvents可以為 FD_ACCEPT ,FD_READ ,FD_WRITE,FD_CLOSE 等等.
4、等待事件控制代碼:index = WSAWaitForMultipleEvents(EventTotal,EventArray,FAlSE,100/*WSA_INFINITE*/,FALSE);
5、查詢網路事件: int WSAEnumNetworkEvents(SOCKET s, WSAEVENT hEventObject,LPWSANETWORKEVENTS lpNetworkEvents)
6、用lpNetworkEvents.lNetworkEvents & FD_ACCEPT ,lpNetworkEvents.lNetworkEvents & FD_CLOSE找到各個事件處理。
- #include <WinSock2.h>
- #include <iostream>
- usingnamespace std;
- #pragma comment(lib, "WS2_32.lib")
- int main()
- {
- WSAEVENT eventArray[1024];
- SOCKET sockArray[1024];
- int nEventTotal = 0;
- WSADATA wsadata;
- WORD sockVersion = MAKEWORD(2, 0);
- WSAStartup(sockVersion, &wsadata);
- SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
- sockaddr_in sin;
- sin.sin_family = AF_INET;
- sin.sin_port = htons(8888);
- sin.sin_addr.S_un.S_addr = INADDR_ANY;
- if (bind(s, (sockaddr*)&sin, sizeof(sin)) == SOCKET_ERROR)
- {
- cout << "error" << endl;
- return 0;
- }
- listen(s, 5);
- WSAEVENT event = ::WSACreateEvent();
- ::WSAEventSelect(s, event, FD_ACCEPT|FD_CLOSE);
- eventArray[nEventTotal] = event;
- sockArray[nEventTotal] = s;
- nEventTotal++;
- while(TRUE)
- {
- // 在所有事件物件上等待
- int nIndex = ::WSAWaitForMultipleEvents(nEventTotal, eventArray, FALSE, WSA_INFINITE, FALSE);
- // 對每個事件呼叫WSAWaitForMultipleEvents函式,以便確定它的狀態
- nIndex = nIndex - WSA_WAIT_EVENT_0;
- for(int i=nIndex; i<nEventTotal; i++)
- {
- nIndex = ::WSAWaitForMultipleEvents(1, &eventArray[i], TRUE, 1000, FALSE);
- if(nIndex == WSA_WAIT_FAILED || nIndex == WSA_WAIT_TIMEOUT)
- {
- continue;
- }
- else
- {
- // 獲取到來的通知訊息,WSAEnumNetworkEvents函式會自動重置受信事件
- WSANETWORKEVENTS event;
- ::WSAEnumNetworkEvents(sockArray[i], eventArray[i], &event);
- if(event.lNetworkEvents & FD_ACCEPT) // 處理FD_ACCEPT通知訊息
- {
- if(event.iErrorCode[FD_ACCEPT_BIT] == 0)
- {
- if(nEventTotal > WSA_MAXIMUM_WAIT_EVENTS)
- {
- cout << " Too many connections! " << endl;
- continue;
- }
- SOCKET sNew = accept(sockArray[i], NULL, NULL);
- WSAEVENT event = ::WSACreateEvent();
- ::WSAEventSelect(sNew, event, FD_READ|FD_CLOSE|FD_WRITE);
- // 新增到表中
- eventArray[nEventTotal] = event;
- sockArray[nEventTotal] = sNew;
- nEventTotal++;
- }
- }
- elseif(event.lNetworkEvents & FD_READ) // 處理FD_READ通知訊息
- {
- if(event.iErrorCode[FD_READ_BIT] == 0)
- {
- char szText[256];
- int nRecv = ::recv(sockArray[i], szText, 256, 0);
- if(nRecv > 0)
- {
- szText[nRecv] = 0;
- cout << "接收到資料:" << szText << endl;
- }
- }
- }
- elseif(event.lNetworkEvents & FD_CLOSE) // 處理FD_CLOSE通知訊息
- {
- if(event.iErrorCode[FD_CLOSE_BIT] == 0)
- {
- ::closesocket(sockArray[i]);
- for(int j=i; j<nEventTotal-1; j++)
- {
- eventArray[j] = eventArray[j+1];
- sockArray[j] = sockArray[j+1];
- }
- nEventTotal--;
- i--;
- }
- }
- elseif(event.lNetworkEvents & FD_WRITE) // 處理FD_WRITE通知訊息
- {
- }
- }
- }
- }
- return 0;
- }
2012/9/2
jofranks 於南昌