WSAEventSelect模型和WSAAsyncSelec模型相似,都是用調用WSAXXXXXSelec函數將socket和事件關聯並註冊到系統,並將socket設置成非阻塞模式。兩者不一樣之處在於socket事件的通知方法:WSAAsyncSelec模型利用窗口句柄和消息映射函數通知網絡事件,而WSAEventSelect模型利用WSAEVENT通知網絡事件。完成WSAEventSelect模型須要涉及如下函數或結構:html
Description:The WSAEventSelect function specifies an event object to be associated with the specified set of FD_XXX network events.編程
1 int WSAEventSelect( 2 __in SOCKET s, 3 __in WSAEVENT hEventObject, 4 __in long lNetworkEvents 5 );
Parameterswindows
Descriptor identifying the socket.數組
Handle identifying the event object to be associated with the specified set of FD_XXX network events.與socket關聯的事件對象,同時須要指定事件對象的類型,lNetworkEvents網絡
Bitmask that specifies the combination of FD_XXX network events in which the application has interest.app
事件對象的事件集合,FD_ACCEPT等,用|符號指定多個類型。在WSAAsyncSelec相關文章http://www.cnblogs.com/hgwang/p/6093976.html內有介紹。less
The return value is zero if the application's specification of the network events and the associated event object was successful. Otherwise, the value SOCKET_ERROR is returned, and a specific error number can be retrieved by calling WSAGetLastError. socket
The WSAEventSelect function automatically sets socket s to nonblocking mode, regardless of the value of lNetworkEvents. To set socket s back to blocking mode, it is first necessary to clear the event record associated with socket s via a call to WSAEventSelect with lNetworkEvents set to zero and the hEventObject parameter set to NULL. You can then call ioctlsocket or WSAIoctl to set the socket back to blocking mode.async
無論第3個參數是什麼,WSAEventSelect 都會設置socket爲非阻塞模式。設置socket爲阻塞模式,受限須要清除socket的事件和事件類型關聯,即再次調用WSAEventSelect ,將hEventObject 賦值爲NULL,將lNetworkEvents 賦值爲0。而後,調用ioctlsocket設置socket爲阻塞模式。ide
rc = WSAEventSelect(s, hEventObject, 0);
上例中,事件集合被設置爲0,這將取消socket與事件的關聯。MSDN給出解釋,第三個參數爲0,事件句柄hEventObject將被忽略。
對同一個socket調用兩次WSAEventSelect ,第二次傳入的參數將覆蓋第一次傳入參數的效果。
1 rc = WSAEventSelect(s, hEventObject1, FD_READ); 2 rc = WSAEventSelect(s, hEventObject2, FD_WRITE); //bad
上例中,s將只接收FD_WRITE事件消息。若是想要FD_WRITE和FD_READ都生效,須要改爲FD_WRITE|FD_READ。
Issuing a WSAAsyncSelect for a socket cancels any previous WSAAsyncSelect or WSAEventSelect for the same socket。
Issuing a WSAEventSelect for a socket cancels any previous WSAAsyncSelect or WSAEventSelect for the same socket and clears the internal network event record.
WSAAsyncSelect 和WSAEventSelect 可以互相取消對方的網絡事件狀態,因此,一個網絡事件event,不可能同時被WSAAsyncSelect 和WSAEventSelect 觸發,必有一個永遠沒法的到消息。也就是說,最好不要同時使用WSAAsyncSelect 和WSAEventSelect ,以避免線程無限期等待。
和windows的event對象同樣,WSAEVENT實際類型是HANDLE。WSACreateEvent和WSACloseEvent分別用了建立WSAEVENT和銷燬WSAEVENT。函數定義如:
WSAEVENT WSACreateEvent(void);
If no error occurs, WSACreateEvent returns the handle of the event object. Otherwise, the return value is WSA_INVALID_EVENT. To get extended error information, call WSAGetLastError.
The WSACreateEvent function creates an event object that is manually reset with an initial state of nonsignaled. Windows Sockets 2 event objects are system objects in Windows environments. Therefore, if a Windows application desires auto reset events, it can call the native CreateEvent Windows function directly. The scope of an event object is limited to the process in which it is created.
須要注意的是,WSACreateEvent 建立的event是須要人工重置的事件對象,即須要手動reset釋放event。想要建立一個自動重置的event,則能夠直接調用CreateEvent 。也就是說,在windows網絡編程內,WSACreateEvent 和CreateEvent 建立的對象都是能夠識別的。
BOOL WSACloseEvent( __in WSAEVENT hEvent);
If the function succeeds, the return value is TRUE.
If the function fails, the return value is FALSE. To get extended error information, call WSAGetLastError.
用WSACloseEvent關閉event對象後,全部對event的引用都將返回WSA_INVALID_HANDLE。
BOOL WSASetEvent( __in WSAEVENT hEvent);
設置event爲授信狀態。
If the function succeeds, the return value is TRUE.If the function fails, the return value is FALSE. To get extended error information, call WSAGetLastError.
BOOL WSAResetEvent( __in WSAEVENT hEvent);
設置event爲未授信狀態
If the WSAResetEvent function succeeds, the return value is TRUE. If the function fails, the return value is FALSE. To get extended error information, call WSAGetLastError.
WSASetEvent、WSAResetEvent都是須要在建立event的時候,將event設置成manually人工操做類型,WSACreateEvent 建立的event是須要人工重置的事件對象,即須要手動reset釋放event。
Description:The WSAWaitForMultipleEvents function returns when one or all of the specified event objects are in the signaled state, when the time-out interval expires, or when an I/O completion routine has executed
WSAWaitForMultipleEvents 返回由如下幾種狀況形成:1,一個或多個event編程授信狀態 ;2,時間到;3,IO完成例程開始執行
1 DWORD WSAWaitForMultipleEvents( 2 __in DWORD cEvents, 3 __in const WSAEVENT* lphEvents, 4 __in BOOL fWaitAll, 5 __in DWORD dwTimeout, 6 __in BOOL fAlertable 7 );
The number of event object handles in the array pointed to by lphEvents. The maximum number of event object handles is WSA_MAXIMUM_WAIT_EVENTS. One or more events must be specified.
指定lphEvents內events的數量,傳遞給lphEvents的事件不能超過WSA_MAXIMUM_WAIT_EVENTS,也就是64個。
A pointer to an array of event object handles. The array can contain handles of objects of different types. It may not contain multiple copies of the same handle if the fWaitAll parameter is set to TRUE. If one of these handles is closed while the wait is still pending, the behavior of WSAWaitForMultipleEvents is undefined. The handles must have the SYNCHRONIZE access right. For more information, see Standard Access Rights.
event對象數組,可以包含多個不一樣類型的對象句柄。若是在等待期間有事件對象被close,那WSAWaitForMultipleEvents 的行爲將沒法預期。也就是說,不要在WSAWaitForMultipleEvents 期間close。
A value that specifies the wait type. If TRUE, the function returns when the state of all objects in the lphEvents array is signaled. If FALSE, the function returns when any of the event objects is signaled. In the latter case, the return value minus WSA_WAIT_EVENT_0 indicates the index of the event object whose state caused the function to return. If more than one event object became signaled during the call, this is the array index to the signaled event object with the smallest index value of all the signaled event objects.
TRUE:在lphEvents 全部的event都授信後返回。
FALSE:用返回值減去WSA_WAIT_EVENT_0 等於事件對象的索引值。若是lphEvents存在多個event,則索引爲下標最小的索引。
The time-out interval, in milliseconds. WSAWaitForMultipleEvents returns if the time-out interval expires, even if conditions specified by the fWaitAll parameter are not satisfied. If the dwTimeout parameter is zero, WSAWaitForMultipleEvents tests the state of the specified event objects and returns immediately. If dwTimeout is WSA_INFINITE, WSAWaitForMultipleEvents waits forever; that is, the time-out interval never expires.
若是設置了dwTimeout,無論event是否授信,在時間到達時,WSAWaitForMultipleEvents 都將返回。若是dwTimeout 設置爲0,WSAWaitForMultipleEvents 將檢測lphEvents的狀態並當即返回,若是設置成WSA_INFINITE,WSAWaitForMultipleEvents 將無限等待直到有event編程授信狀態。
A value that specifies whether the thread is placed in an alertable wait state so the system can execute I/O completion routines. If TRUE, the thread is placed in an altertable wait state and WSAWaitForMultipleEvents can return when the system executes an I/O completion routine. In this case, WSA_WAIT_IO_COMPLETION is returned and the event that was being waited on is not signaled yet. The application must call the WSAWaitForMultipleEvents function again. If FALSE, the thread is not placed in an altertable wait state and I/O completion routines are not executed.
若是WSAWaitForMultipleEvents 調用成功,將返回如下數值之一:
1:WSA_WAIT_EVENT_0 to (WSA_WAIT_EVENT_0 + cEvents - 1)。若是fWaitAll是true,指示全部的事件都已授信。若是是false,則返回值減去WSA_WAIT_EVENT_0表示lphEvents裏面最小授信事件的下標。
2:WSA_WAIT_IO_COMPLETION。
The wait was ended by one or more I/O completion routines that were executed. The event that was being waited on is not signaled yet. The application must call the WSAWaitForMultipleEvents function again. This return value can only be returned if the fAlertable parameter is TRUE.
3:WSA_WAIT_TIMEOUT。超出dwTimeout設置的時間,且沒有事件授信。或者沒有IO完成例程執行。
If the WSAWaitForMultipleEvents function fails, the return value is WSA_WAIT_FAILED.
Description:The WSAEnumNetworkEvents function discovers occurrences of network events for the indicated socket, clear internal network event records, and reset event objects (optional).
WSAEnumNetworkEvents 函數查找出給定socket的已授信事件,清楚網絡事件記錄,而且重置網絡事件對象event(可選)。
1 int WSAEnumNetworkEvents( 2 __in SOCKET s, 3 __in WSAEVENT hEventObject, 4 __out LPWSANETWORKEVENTS lpNetworkEvents 5 );
A descriptor identifying the socket.
An optional handle identifying an associated event object to be reset.
可選,待重置的網絡授信事件(必須是和s關聯的網絡事件)
A pointer to a WSANETWORKEVENTS structure that is filled with a record of network events that occurred and any associated error codes.
指向WSANETWORKEVENTS 的指針,WSANETWORKEVENTS 裏面存儲了當前socket的授信事件標識和錯誤代碼。
The return value is zero if the operation was successful. Otherwise, the value SOCKET_ERROR is returned, and a specific error number can be retrieved by calling WSAGetLastError.
1 typedef struct _WSANETWORKEVENTS { 2 long lNetworkEvents; 3 int iErrorCode[FD_MAX_EVENTS]; 4 } WSANETWORKEVENTS, *LPWSANETWORKEVENTS;
Indicates which of the FD_XXX network events have occurred.
Array that contains any associated error codes, with an array index that corresponds to the position of event bits in lNetworkEvents. The identifiers FD_READ_BIT, FD_WRITE_BIT and others can be used to index the iErrorCode array.
/* WinSock 2 extension -- bit values and indices for FD_XXX network events*/ #define FD_READ_BIT 0 #define FD_READ (1 << FD_READ_BIT) #define FD_WRITE_BIT 1 #define FD_WRITE (1 << FD_WRITE_BIT) #define FD_OOB_BIT 2 #define FD_OOB (1 << FD_OOB_BIT) #define FD_ACCEPT_BIT 3 #define FD_ACCEPT (1 << FD_ACCEPT_BIT) #define FD_CONNECT_BIT 4 #define FD_CONNECT (1 << FD_CONNECT_BIT) #define FD_CLOSE_BIT 5 #define FD_CLOSE (1 << FD_CLOSE_BIT) #define FD_QOS_BIT 6 #define FD_QOS (1 << FD_QOS_BIT) #define FD_GROUP_QOS_BIT 7 #define FD_GROUP_QOS (1 << FD_GROUP_QOS_BIT)
從4.2能夠看出,FD_XXX事件位偏移各不相同,這也是FD_AAA|FD_BBB生效的緣由。WSANETWORKEVENTS內的lNetworkEvents取出網絡事件狀態操做爲以下:
1 WSANETWORKEVENTS nwevents; 2 WSAEnumNetworkEvents(m_socks[i],m_events[i],&nwevents); 3 if(nwevents.lNetworkEvents & FD_ACCEPT) 4 {... 5 }
而WSANETWORKEVENTS內的iErrorCode是socket狀態數組,0表示當前socket狀態正常,非0指示socket錯誤代碼,取出來對應授信事件的操做是:
1 if (nwevents.iErrorCode[FD_ACCEPT_BIT] == 0) 2 {...}
封裝類CEventSelect,基類CTaskSvc提供線程函數,見http://www.cnblogs.com/hgwang/p/6094444.html。
EventSelect.h
1 #pragma once 2 #include "TaskSvc.h" 3 class CEventSelect : public CTaskSvc 4 { 5 public: 6 CEventSelect(void); 7 ~CEventSelect(void); 8 9 private: 10 void svc(); 11 12 WSAData m_wsa; 13 bool m_bRes; 14 SOCKET m_listensocket; 15 16 private: 17 WSAEVENT m_events[WSA_MAXIMUM_WAIT_EVENTS]; 18 SOCKET m_socks[WSA_MAXIMUM_WAIT_EVENTS]; 19 int m_eventsNum; 20 };
EventSelect.cpp
1 #include "StdAfx.h" 2 #include "EventSelect.h" 3 4 CEventSelect::CEventSelect(void) 5 { 6 m_bRes = true; 7 WSAStartup(MAKEWORD(2,3),&m_wsa); 8 m_listensocket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); 9 if (m_listensocket == INVALID_SOCKET ) 10 { 11 m_bRes = false; 12 } 13 sockaddr_in m_server; 14 m_server.sin_family = AF_INET; 15 m_server.sin_port = htons(8828); 16 m_server.sin_addr.s_addr = inet_addr("127.0.0.1"); 17 if (m_bRes && (bind(m_listensocket,(sockaddr*)&m_server,sizeof(sockaddr_in)) == SOCKET_ERROR)) 18 { 19 DWORD dw = WSAGetLastError(); 20 m_bRes = false; 21 } 22 if (m_bRes && (listen(m_listensocket,SOMAXCONN) == SOCKET_ERROR)) 23 { 24 m_bRes = false; 25 } 26 WSAEVENT e = WSACreateEvent(); 27 if (m_bRes && WSAEventSelect(m_listensocket,e,FD_ACCEPT|FD_CLOSE)) 28 { 29 m_bRes = false; 30 } 31 m_eventsNum = 0; 32 m_events[m_eventsNum] = e; 33 m_socks[m_eventsNum] = m_listensocket; 34 m_eventsNum++; 35 Activate(); 36 } 37 38 CEventSelect::~CEventSelect(void) 39 { 40 for(int i = 0; i < m_eventsNum; i++) 41 { 42 //關閉event和socket,首先應當使用WSAEventSelect設置事件0,接觸socket和event的關聯 43 WSAEventSelect(m_socks[i], m_events[i], 0); 44 //關閉socket 45 closesocket(m_socks[i]); 46 //關機event 47 WSACloseEvent(m_events[i]); 48 } 49 } 50 51 52 void CEventSelect::svc() 53 { 54 while (true) 55 { 56 DWORD dw = WSAWaitForMultipleEvents(m_eventsNum,m_events,FALSE,WSA_INFINITE,FALSE); 57 if (dw == WSA_WAIT_TIMEOUT) 58 { 59 continue; 60 } 61 //若是WSAWaitForMultipleEvents第三項是true,則返回值標識全部的事件都已授信 62 //若是WSAWaitForMultipleEvents第三項是false,則返回值標識已授信事件的最小索引 63 DWORD index = dw - WSA_WAIT_EVENT_0; 64 //獲取授信事件的最小索引,後面全部事件的狀態都不知道,因此要從最小索引開始,輪詢一遍 65 //不然,下次進入svc調用WSAWaitForMultipleEvents又返回最小索引 66 for (int i=index;i<m_eventsNum;i++) 67 { 68 //循環調用WSAWaitForMultipleEvents 69 //注意:這次調用參數不一樣, 70 //1:須要查詢的事件個數爲1,第一個參數爲1,第二個參數爲待查詢的事件地址 71 //2:因爲事件個數只有一個,第3個參數可改爲true 72 //3:不能設置等待時間爲WSA_INFINITE,若是當前事件無限期等待,程序將阻塞在此 73 DWORD dw = WSAWaitForMultipleEvents(1,&m_events[i],TRUE,1000,FALSE); 74 if (dw == WSA_WAIT_TIMEOUT || dw == WSA_WAIT_FAILED) 75 { 76 //超時,下一個事件查詢 77 continue; 78 } 79 //使用WSAEnumNetworkEvents 獲取授信事件 80 WSANETWORKEVENTS nwevents; 81 //獲取socket[i]的事件集合,並將重置m_events[i] 82 WSAEnumNetworkEvents(m_socks[i],m_events[i],&nwevents); 83 //accept請求到達 84 if(nwevents.lNetworkEvents & FD_ACCEPT) 85 { 86 //驗證當前網絡狀態,非0對應錯誤碼 87 if (nwevents.iErrorCode[FD_ACCEPT_BIT] == 0) 88 { 89 sockaddr_in m_client; 90 int sz = sizeof(sockaddr_in); 91 SOCKET acp = accept(m_socks[i],(sockaddr*)&m_client,&sz); 92 if (acp == INVALID_SOCKET) 93 { 94 continue; 95 } 96 //爲新鏈接的socket關聯事件 97 WSAEVENT e = WSACreateEvent(); 98 WSAEventSelect(acp,e,FD_READ|FD_WRITE|FD_CLOSE); 99 m_events[m_eventsNum] = e; 100 m_socks[m_eventsNum] = acp; 101 m_eventsNum++; 102 } 103 } 104 else if (nwevents.lNetworkEvents & FD_READ) 105 { 106 if (nwevents.iErrorCode[FD_READ_BIT] == 0) 107 { 108 char buf[1024]; 109 int res = recv(m_socks[i],buf,1024,0); 110 if (res == 0) 111 { 112 closesocket(m_socks[i]); 113 break; 114 } 115 buf[res] = 0; 116 cout<<buf<<endl; 117 } 118 } 119 else if (nwevents.lNetworkEvents & FD_WRITE) 120 { 121 if (nwevents.iErrorCode[FD_WRITE_BIT] == 0) 122 { 123 std::string str = "send data from service"; 124 int sz = send(m_socks[i],str.c_str(),str.length(),0); 125 if (sz == SOCKET_ERROR) 126 { 127 if (WSAGetLastError() == WSAEWOULDBLOCK) 128 { 129 continue; 130 } 131 } 132 } 133 } 134 else if (nwevents.lNetworkEvents & FD_CLOSE) 135 { 136 //此處不該再判斷結束狀態,非正常終止的鏈接將返回錯誤碼10053或10054,而非0 137 //if (nwevents.iErrorCode[FD_CLOSE_BIT] == 0) 138 { 139 closesocket(m_socks[i]); 140 //從socket數組中移除當前socket 141 //此過程省略 142 } 143 } 144 } 145 } 146 }
測試結果: