WSAEventSelect允许应用程序在一个或者多个套接字上接收基于事件的网络通知,不过并不是依靠Windows 的消息驱动机制,而是经由事件对象句柄通知。
使用这个模型的基本思路是为感兴趣的一组网络事件创建一个事件对象,再调用WSAEventSelect 函数将网络事件和事件对象关联起来。当网络事件发生时,Winsock 使相应的事件对象受信,在事件对象上的等待函数就会返回。之后,调用WSAEnumNetworkEvents函数便可获取到底发生了什么网络事件。
WSAEVENT WSACreateEvent(void); // 返回一个手工重置的事件对象句柄
int WSAEventSelect(
SOCKET s, // 套接字句柄
WSAEVENT hEventObject, // 事件对象句柄
long lNetworkEvents // 感兴趣的FD_XXX 网络事件的组合
);
网络事件与事件对象关联之后,应用程序便可以在事件对象上等待了。Winsock 提供了WSAWaitForMultipleEvents 函数在一个或多个事件对象上等待,当所等待的事件对象受信,或者指定的时间过去时,此函数返回。
DWORD WSAWaitForMultipleEvents(
DWORD cEvents, // 指定下面lphEvents 所指的数组中事件对象句柄的个数
const WSAEVENT* lphEvents, // 指向一个事件对象句柄数组
BOOL fWaitAll, // 指定是否等待所有事件对象都变成受信状态
DWORD dwTimeout, // 指定要等待的时间,WSA_INFINITE 为无穷大, 0立即返回
BOOL fAlertable // 在使用WSAEventSelect 模型时可以忽略,应设为FALSE
);
WSAWaitForMultipleEvents 最多支持WSA_MAXIMUM_WAIT_EVENTS 个对象,WSA_MAXIMUM_WAIT_EVENTS 被定义为64。因此,这个I/O 模型在一个线程中同一时间最多能支持64 个套接字,如果需要使用这个模型管理更多套接字,就需要创建额外的工作线程了。WSAWaitForMultipleEvents 函数会等待网络事件的发生。如果过了指定的时间,函数返回WSA_WAIT_TIMEOUT;如果在指定时间内有网络事件发生,函数的返回值会指明是哪一个事件对象促使函数返回的;函数调用失败时返回值是WSA_WAIT_FAILED。
也可以将dwTimeout 的值设为0,这时函数测试指定事件对象的状态,并立即返回,通过函数的返回值便可知道事件对象是否受信。
注意,将fWaitAll 参数设为FALSE 以后,如果同时有几个事件对象受信,WSAWaitForMultipleEvents 函数的返回值也仅能指明一个,就是句柄数组中最前面的那个。如果指明的这个事件对象总有网络时间发生,那么后面其他事件对象所关联的网络事件就得不到处理了。解决办法是,WSAWaitForMultipleEvents 函数返回后,对每个事件都再次调用WSAWaitForMultipleEvents 函数,以便确定其状态。
一旦事件对象受信,那么找到与之对应的套接字,然后调用WSAEnumNetworkEvents 函数即可查看发生了什么网络事件。
int WSAEnumNetworkEvents(
SOCKET s, // 套接字句柄
WSAEVENT hEventObject, // 对应的事件对象句柄。如果提供了此参数,本函数会重置这个事件对象的状态
LPWSANETWORKEVENTS lpNetworkEvents // 指向一个WSANETWORKEVENTS 结构
);
typedef struct _WSANETWORKEVENTS {
long lNetworkEvents; // 指定已发生的网络事件(如FD_ACCEPT、FD_READ 等)
int iErrorCode[FD_MAX_EVENTS]; // 与lNetworkEvents 相关的出错代码
} WSANETWORKEVENTS, *LPWSANETWORKEVENTS;
iErrorCode 参数是一个数组,数组的每个成员对应着一个网络事件的出错代码。可以用预定义标识FD_READ_BIT、FD_WRITE_BIT 等来索引FD_READ、FD_WRITE 等事件发生时的出错代码。如下面代码片段所示。
if(event.lNetworkEvents & FD_READ) // 处理FD_READ 通知消息
{
if(event.iErrorCode[FD_READ_BIT] != 0)
{
…… // FD_READ 出错,错误代码为event.iErrorCode[FD_READ_BIT]
}
}
#include <WINSOCK2.H>
#include <STDIO.H>
#pragma comment(lib, "Ws2_32.lib")
int main()
{
// 事件句柄和套节字句柄表
WSAEVENT eventArray[WSA_MAXIMUM_WAIT_EVENTS];
SOCKET sockArray[WSA_MAXIMUM_WAIT_EVENTS];
int nEventTotal = 0;
WSADATA wsaData;
WSAStartup(MAKEWORD(2,2), &wsaData);
USHORT nPort = 9000; // 此服务器监听的端口号
// 创建监听套节字
SOCKET sListen = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(nPort);
sin.sin_addr.S_un.S_addr = INADDR_ANY;
::bind(sListen, (sockaddr*)&sin, sizeof(sin));
::listen(sListen, 5);
printf("server port %d is listenning ... \n", nPort);
// 创建事件对象,并关联到新的套节字
WSAEVENT wsaevent = ::WSACreateEvent();
::WSAEventSelect(sListen, wsaevent, FD_ACCEPT|FD_CLOSE);
// 添加到表中(一个套接字对应一个网络事件)
eventArray[nEventTotal] = wsaevent;
sockArray[nEventTotal] = sListen;
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, 0/*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)
{
printf(" Too many connections! total %d \n", nEventTotal);
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++;
printf("[sock %d] client connect, current clients count %d\n", sNew, nEventTotal-1);
}
else
{
printf("[sock %d] accept error, error code %d\n", sockArray[i], event.iErrorCode[FD_ACCEPT_BIT]);
}
}
else if(event.lNetworkEvents & FD_READ) // 处理FD_READ通知消息
{
if(event.iErrorCode[FD_READ_BIT] == 0)
{
char szText[256];
int nRecv = ::recv(sockArray[i], szText, strlen(szText), 0);
if(nRecv > 0)
{
szText[nRecv] = '\0';
printf("[sock %d] recv data: %s \n", sockArray[i], szText);
}
}
else
{
printf("[sock %d] read error, error code %d\n", sockArray[i], event.iErrorCode[FD_ACCEPT_BIT]);
}
}
else if(event.lNetworkEvents & FD_CLOSE) // 处理FD_CLOSE通知消息
{
if(event.iErrorCode[FD_CLOSE_BIT] == 0)
{
::closesocket(sockArray[i]);
printf("[sock %d] close socket, current clients %d\n", sockArray[i], nEventTotal-1);
for(int j=i; j<nEventTotal-1; j++)
{
sockArray[j] = sockArray[j+1];
sockArray[j] = sockArray[j+1];
}
nEventTotal--;
}
else
{
printf("[sock %d] close error, error code %d\n", sockArray[i], event.iErrorCode[FD_CLOSE_BIT]);
}
}
else if(event.lNetworkEvents & FD_WRITE) // 处理FD_WRITE通知消息
{
}
}
}
}
WSACleanup();
return 0;
}