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.
int WSAEventSelect(
__in SOCKET s,
__in WSAEVENT hEventObject,
__in long lNetworkEvents
);
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,这将取消socket与事件的关联。MSDN给出解释,第三个参数为0,事件句柄hEventObject将被忽略。
对同一个socket调用两次WSAEventSelect ,第二次传入的参数将覆盖第一次传入参数的效果。
rc = WSAEventSelect(s, hEventObject1, FD_READ);
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。
3 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完成例程开始执行
DWORD WSAWaitForMultipleEvents(
__in DWORD cEvents,
__in const WSAEVENT* lphEvents,
__in BOOL fWaitAll,
__in DWORD dwTimeout,
__in BOOL fAlertable
);
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(可选)。
int WSAEnumNetworkEvents(
__in SOCKET s,
__in WSAEVENT hEventObject,
__out LPWSANETWORKEVENTS lpNetworkEvents
);
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 的结构如下:
typedef struct _WSANETWORKEVENTS {
long lNetworkEvents;
int iErrorCode[FD_MAX_EVENTS];
} 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取出网络事件状态操作为如下:
WSANETWORKEVENTS nwevents;
WSAEnumNetworkEvents(m_socks[i],m_events[i],&nwevents);
if(nwevents.lNetworkEvents & FD_ACCEPT)
{...
}
而WSANETWORKEVENTS内的iErrorCode是socket状态数组,0表示当前socket状态正常,非0指示socket错误代码,取出来对应授信事件的操作是:
if (nwevents.iErrorCode[FD_ACCEPT_BIT] == )
{...}
5:例子
封装类CEventSelect,基类CTaskSvc提供线程函数,见http://www.cnblogs.com/hgwang/p/6094444.html。
EventSelect.h
#pragma once
#include "TaskSvc.h"
class CEventSelect : public CTaskSvc
{
public:
CEventSelect(void);
~CEventSelect(void); private:
void svc(); WSAData m_wsa;
bool m_bRes;
SOCKET m_listensocket; private:
WSAEVENT m_events[WSA_MAXIMUM_WAIT_EVENTS];
SOCKET m_socks[WSA_MAXIMUM_WAIT_EVENTS];
int m_eventsNum;
};
EventSelect.cpp
#include "StdAfx.h"
#include "EventSelect.h" CEventSelect::CEventSelect(void)
{
m_bRes = true;
WSAStartup(MAKEWORD(,),&m_wsa);
m_listensocket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if (m_listensocket == INVALID_SOCKET )
{
m_bRes = false;
}
sockaddr_in m_server;
m_server.sin_family = AF_INET;
m_server.sin_port = htons();
m_server.sin_addr.s_addr = inet_addr("127.0.0.1");
if (m_bRes && (bind(m_listensocket,(sockaddr*)&m_server,sizeof(sockaddr_in)) == SOCKET_ERROR))
{
DWORD dw = WSAGetLastError();
m_bRes = false;
}
if (m_bRes && (listen(m_listensocket,SOMAXCONN) == SOCKET_ERROR))
{
m_bRes = false;
}
WSAEVENT e = WSACreateEvent();
if (m_bRes && WSAEventSelect(m_listensocket,e,FD_ACCEPT|FD_CLOSE))
{
m_bRes = false;
}
m_eventsNum = ;
m_events[m_eventsNum] = e;
m_socks[m_eventsNum] = m_listensocket;
m_eventsNum++;
Activate();
} CEventSelect::~CEventSelect(void)
{
for(int i = ; i < m_eventsNum; i++)
{
//关闭event和socket,首先应当使用WSAEventSelect设置事件0,接触socket和event的关联
WSAEventSelect(m_socks[i], m_events[i], );
//关闭socket
closesocket(m_socks[i]);
//关机event
WSACloseEvent(m_events[i]);
}
} void CEventSelect::svc()
{
while (true)
{
DWORD dw = WSAWaitForMultipleEvents(m_eventsNum,m_events,FALSE,WSA_INFINITE,FALSE);
if (dw == WSA_WAIT_TIMEOUT)
{
continue;
}
//如果WSAWaitForMultipleEvents第三项是true,则返回值标识所有的事件都已授信
//如果WSAWaitForMultipleEvents第三项是false,则返回值标识已授信事件的最小索引
DWORD index = dw - WSA_WAIT_EVENT_0;
//获取授信事件的最小索引,后面所有事件的状态都不知道,所以要从最小索引开始,轮询一遍
//否则,下次进入svc调用WSAWaitForMultipleEvents又返回最小索引
for (int i=index;i<m_eventsNum;i++)
{
//循环调用WSAWaitForMultipleEvents
//注意:此次调用参数不同,
//1:需要查询的事件个数为1,第一个参数为1,第二个参数为待查询的事件地址
//2:由于事件个数只有一个,第3个参数可改成true
//3:不能设置等待时间为WSA_INFINITE,如果当前事件无限期等待,程序将阻塞在此
DWORD dw = WSAWaitForMultipleEvents(,&m_events[i],TRUE,,FALSE);
if (dw == WSA_WAIT_TIMEOUT || dw == WSA_WAIT_FAILED)
{
//超时,下一个事件查询
continue;
}
//使用WSAEnumNetworkEvents 获取授信事件
WSANETWORKEVENTS nwevents;
//获取socket[i]的事件集合,并将重置m_events[i]
WSAEnumNetworkEvents(m_socks[i],m_events[i],&nwevents);
//accept请求到达
if(nwevents.lNetworkEvents & FD_ACCEPT)
{
//验证当前网络状态,非0对应错误码
if (nwevents.iErrorCode[FD_ACCEPT_BIT] == )
{
sockaddr_in m_client;
int sz = sizeof(sockaddr_in);
SOCKET acp = accept(m_socks[i],(sockaddr*)&m_client,&sz);
if (acp == INVALID_SOCKET)
{
continue;
}
//为新连接的socket关联事件
WSAEVENT e = WSACreateEvent();
WSAEventSelect(acp,e,FD_READ|FD_WRITE|FD_CLOSE);
m_events[m_eventsNum] = e;
m_socks[m_eventsNum] = acp;
m_eventsNum++;
}
}
else if (nwevents.lNetworkEvents & FD_READ)
{
if (nwevents.iErrorCode[FD_READ_BIT] == )
{
char buf[];
int res = recv(m_socks[i],buf,,);
if (res == )
{
closesocket(m_socks[i]);
break;
}
buf[res] = ;
cout<<buf<<endl;
}
}
else if (nwevents.lNetworkEvents & FD_WRITE)
{
if (nwevents.iErrorCode[FD_WRITE_BIT] == )
{
std::string str = "send data from service";
int sz = send(m_socks[i],str.c_str(),str.length(),);
if (sz == SOCKET_ERROR)
{
if (WSAGetLastError() == WSAEWOULDBLOCK)
{
continue;
}
}
}
}
else if (nwevents.lNetworkEvents & FD_CLOSE)
{
//此处不应再判断结束状态,非正常终止的连接将返回错误码10053或10054,而非0
//if (nwevents.iErrorCode[FD_CLOSE_BIT] == 0)
{
closesocket(m_socks[i]);
//从socket数组中移除当前socket
//此过程省略
}
}
}
}
}
测试结果: