winsock編程WSAEventSelect模型


winsock編程WSAEventSelect模型 

   WSAEventSelect模型和WSAAsyncSelec模型類似,都是用調用WSAXXXXXSelec函數將socket和事件關聯並注冊到系統,並將socket設置成非阻塞模式。二者不同之處在於socket事件的通知方法:WSAAsyncSelec模型利用窗口句柄和消息映射函數通知網絡事件,而WSAEventSelect模型利用WSAEVENT通知網絡事件。完成WSAEventSelect模型需要涉及以下函數或結構:

1:WSAEventSelect

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 );

 Parameters

s

Descriptor identifying the socket.

hEventObject

Handle identifying the event object to be associated with the specified set of FD_XXX network events.與socket關聯的事件對象,同時需要指定事件對象的類型,lNetworkEvents

lNetworkEvents

Bitmask that specifies the combination of FD_XXX network events in which the application has interest.

事件對象的事件集合,FD_ACCEPT等,用|符號指定多個類型。在WSAAsyncSelec相關文章http://www.cnblogs.com/hgwang/p/6093976.html內有介紹。

Return Value

  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

Remarks

  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.

  不管第3個參數是什么,WSAEventSelect 都會設置socket為非阻塞模式。設置socket為阻塞模式,受限需要清除socket的事件和事件類型關聯,即再次調用WSAEventSelect ,將hEventObject 賦值為NULL,將lNetworkEvents 賦值為0。然后,調用ioctlsocket設置socket為阻塞模式。

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 ,以免線程無限期等待。

2:WSAEVENT、WSACreateEvent、WSACloseEvent、WSASetEvent、WSAResetEvent

   和windows的event對象一樣,WSAEVENT實際類型是HANDLE。WSACreateEvent和WSACloseEvent分別用了創建WSAEVENT和銷毀WSAEVENT。函數定義如:

2.1 WSACreateEvent

WSAEVENT WSACreateEvent(void);

Return Value

  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.

Remarks

  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 創建的對象都是可以識別的。

2.2 WSACloseEvent

BOOL WSACloseEvent(  __in          WSAEVENT hEvent);

Return Value

  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

2.3 WSASetEvent

BOOL WSASetEvent(  __in    WSAEVENT hEvent);

 

  設置event為授信狀態。

Return Value

  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.

2.4 WSAResetEvent

BOOL WSAResetEvent(  __in     WSAEVENT hEvent);

  設置event為未授信狀態

Return Value

  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。

WSAWaitForMultipleEvents

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 );

Parameters

cEvents

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個。

lphEvents

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。

fWaitAll

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,則索引為下標最小的索引。

dwTimeout

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編程授信狀態。

fAlertable

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.

Return Value

  如果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. 

4:WSAEnumNetworkEvents

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 );

 

Parameters

s

A descriptor identifying the socket.

hEventObject

An optional handle identifying an associated event object to be reset.

可選,待重置的網絡授信事件(必須是和s關聯的網絡事件)

lpNetworkEvents

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的授信事件標識和錯誤代碼。

Return Value

  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.

4.1 WSANETWORKEVENTS 的結構如下:

1 typedef struct _WSANETWORKEVENTS {  
2 long lNetworkEvents;  
3 int iErrorCode[FD_MAX_EVENTS];
4 } WSANETWORKEVENTS,  *LPWSANETWORKEVENTS;

 

Members

lNetworkEvents

Indicates which of the FD_XXX network events have occurred.

iErrorCode

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.

4.2 常見網絡FD_XX的宏定義

/* 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 {...}

 

 

5:例子

  封裝類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 }

 測試結果:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM