WSAEVENT hEvent,LPWSANETWORKEVENTS lpNetworkEvents);
该函数可以查找发生在套接字上的网络事件,并清除系统内部的网络事件记录,重置事件对象。s为发生网络事件的套接字句柄。hEvent为被重置的事件对象句柄(可选)。lpNetworkEvents为指向WSANETWORKEVENTS结构指针。如果hEvent不为NULL,则该事件被重置。如果为NULL,需要调用WSAResetEvent函数设置事件为非触发状态。该结构中包含发生网络事件的记录和相关错误代码。调用成功返回0,否则为SOCKETS\_ERROR。WSANETWORKEVENTS结构如下:
typedef struct _WSANETWORKEVENTS
{
long lNetworkEvents,
int iErrorCode[FD_MAX_EVENTS];
}WSANETWORKEVENTS,*LPWSANETWORKEVENTS;
lNetworkEvents指示发生的网络事件。一个对象再变为触发态时,可能在套接字上发生了多个网络事件。iErrorCode为包含网络事件错误代码的数组。错误代码与lNetworkEvents字段中的网络事件对应。在应用程序中,使用网络事件事件错误标识符对iErrorCode数组进行索引,检查是否发生了网络错误。这些标识符的命名规则是对应的网络事件后面添加\_BIT.例如,对于FD\_READ事件的网络事件错误标识符为FD\_READ\_BIT。下面的代码演示了,如何判断FD\_READ网络事件的发生:
SOCKET s;
WSAEVENT hNetworkEvent;
WSANETWORKEVENT networkEvents;
if(0==WSAEnumNetworkEvents(h,hNetworkEvent,&networkEvents);
{
//发生FD_WRITE网络事件。if(networkEvents.lNetworkEvents&FD_READ)
{
if(0==networkEvent.iErrorCode[FD_READ_BIT]){//接收数据。}else{//获取错误代码。int nErrorCode=networkEvents.iErrorCode[FD_READ_BIT];//处理错误。}}
}
本例演示利用WSAEventSelect模型开发一个服务器应用程序的步骤。主要步骤:程序开始时会创建监听套接字,利用WSAEventSelect函数为套接字注册FD\_ACCEPT和FD\_CLOSE事件,然后套接字进入监听状态。在while循环内,循环调用WSAWaitForMultipleEvents函数等待网络事件的发生,当网络事件发生时函数返回,并通过该函数的返回值得到发生网络事件的套接字。调用WSAEnumNetworkEvents函数检查在该套接字上到底发生什么网络事件。如果发生FD\_ACCEPT网络事件,则调用accept函数接受客户端连接。将该套接字加入套接字数组。创建事件对象并加入事件数组。事件对象数量加一。然后调用WSAEventSelect函数为该套接字关联事件对象,注册FD\_READ,FD\_WRITE和FD\_CLOSE网络事件。如果发生FD\_READ网络事件,则调用recv函数接收数据。如果发生FD\_WRITE网络事件,则调用send函数发送数据。如果发生FD\_CLOSE网络事件,将该套接字从套接字数组清除,同时将对应事件从事件数组删除。事件对象数量减一,并关闭该套接字。在应用程序中,对发生的每种网络事件,都首先判断是否发生了网络错误。如果发生错误,则服务器退出。步骤一:定义事件对象数组和套接字数组。这两个数组的最大程度为WSA\_MAXIMUM\_WAIT\_EVENTS。这两个数组的成员存在一一对应关系。DWORD totalEvent;//事件对象数量。WSAEVENT eventArray[WSA\_MAXIMUM\_WAIT\_EVENTS];SOCKETS socketArray[WSA\_MAXIMUM\_WAIT\_EVENTS];步骤二:创建套接字:
WSADATA wsaData;
WSAStartup(MAKEWORD(2,2),&wsaData);
if((sListen==socket(AF_INET,SOCK_STREAM,0))
{//创建失败。
}
步骤三:为监听套接字注册网络事件:
if(eventArray[totalEvent]=WSACreateEvent()==WSA_INVALID_EVENT)
{
//调用失败。}
//为监听套接字注册FD_READ,和FD_CLOSE网络事件。
if(WSAEventSelect(sListen,eventArray[totalEvent],FD_ACCEPT|FD_CLOSE)==SOCKETS_ERROR)
{
//调用失败。
}
步骤四:开始监听:
sockaddr_in addr;
addr.sin_family=AF_INET;
addr.sin_addr.S_addr=htons(INADDR_ANY);
addr.sin_port=htons(4000);
if(bind(sListen,(SOCKADDR*)&addr,sizeof(addr))==SOCKETS_ERROR)
{
//绑定失败。
}
if(!listen(sListen,10))
{
//监听失败。
}
步骤五:等待网络事件。
while(true)
{
if(dwIndex=WSAWaitForMultipleEvents(totalEvent,eventArray,
false,WSA_INFINITE,false)==WSA_WAIT_FAILED)
{
//等待失败。
}
步骤六:获取发生的网络事件。当网络事件发生时WSAWaitForMultipleEvents函数返回。调用WSAEnumNetworkEvents函数获取发生在套接字上的网络事件。socketArray[dwIndex-WSA\_WAIT\_EVENT\_0]为当前发生网络事件的套接字。eventArray[dwIndex-WSA\_WAIT\_EVENT\_0]为当前被投递网络事件的事件对象。当函数返回时networkEvents变量中保存了网络事件的记录。同时事件对象的工作状态,由未触发态变为触发态。WSANETWORKEVENTS networkEvents;if(WSAEnumNetworkEvents(socketArray[dwIndex-WSA\_WAIT\_EVENT\_0],eventArray[dwIndex-WSA\_WAIT\_EVENT\_0],&networkEvents)==SOCKETS\_ERROR){//调用失败。}步骤七:判断是否是各网络事件发生。WSAEnumNetworkEvents函数返回时,首先检查是否发生了FD\_ACCEPT网络事件。如果该网络事件发生,则说明此时客户端的连接请求被等待。检查是否发生了网络错误,如果没有错误发生,则执行下面的步骤:1:调用accept接受客户端请求。2:判断当前套接字数量是否超过了最大值。如果超过则关闭该套接字。3:将客户端套接字介入套接字数组。4:创建套接字事件对象,并将该事件对象加入事件对象数组。5:为该套接字注册FD\_READ,FD\_WRITE和FD\_CLOSE网络事件。6:事件对象数量加一,将该套接字加入管理客户端套接字链表中。使用WSAEventSelect应该注意的问题。1:如果在一个套接字上多次调用WSAEventSelect函数,那么最后一次函数调用将会取消前一次的调用效果。2:一个套接字不要关联多个事件对象 。在一个套接字上为不同网络事件注册不同的事件对象是不可能的。一个套接字关联一个对象,当该对象被触发时,获得对应的套接字,然后调用WSAEnumNetworkEvents来获得发生在此套节字上的事件。3:如果要取消事件对象与网络事件的关联,以及为套接字注册的网络事件。应用程序可以在调用WSAEventSelect时将lNetworkEvent设置为0。另外,调用closesocket关闭套接字时,也会取消这种关联和为套接字注册的网络事件。4:调用accept接受的套接字与监听套接字具有同样的属性。如:在创建监听套接字时为其设置感兴趣的网络事件为FD\_ACCEPT和FD\_CLOSE。那么accept返回的套接字同样具有这些属性。它与监听套接字感兴趣的网络事件相同且使用同一个事件对象。一般情况下,我们都会为新套接字重新调用WSAEventSelect。后面的代码中在accept后会新套接字调用WSAEventSelect函数就不足为奇了!!5:接收FD\_CLOSE网络事件时,错误代码指出套接字是从容关闭还是硬关闭。如果错误代码为0,则为从容关闭;若错误代码为WSAECONNRESET错误,则是硬关闭。当应用程序接收到该网络事件时,说明对方在该套接字上执行了shutdown或者是closesocket函数调用。WSAEventSelect模型的优势和不足。WSAEventSelect模型的优势是可以应用在一个非窗口的Windows sockets程序中,实现对多个套接字的管理。不足是:每个WSAEventSelect模型最多只能管理64个套接字。当应用程序需要管理多于64个套接字时,就需要额外创建线程。由于该模型需要调用多个函数,这增加了开发的难度。以下为详细代码:
#include
#include<windows.h>
#include"winsock2.h"
SOCKET sListen;
#pragma comment(lib,“WS2_32.lib”)
#define MAX_NUM_SOCKET 20
u_int totalEvent=0;
//构造事件对象数组和套接字数组。
WSAEVENT eventArray[MAX_NUM_SOCKET];
SOCKET socketArray[MAX_NUM_SOCKET];
bool InitSocket()
{
WSAData wsa;WSAStartup(MAKEWORD(2,2),&wsa);sListen=socket(AF_INET,SOCK_STREAM,0);if(sListen==INVALID_SOCKET){return false;}WSAEVENT hEvent=WSACreateEvent();eventArray[totalEvent]=hEvent;//可用事件加一。totalEvent++;int ret=WSAEventSelect(sListen,hEvent,FD_CLOSE|FD_ACCEPT);//监听套接字只能收到这两种消息。if(!ret){return false;}sockaddr_in addr;addr.sin_addr.S_un=inet_addr("192.168.1.100");addr.sin_family=AF_INET;addr.sin_port=htons(4000);ret=bind(sListen,(SOCKADDR*)&addr,sizeof(addr));if(ret==SOCKET_ERROR){return false;}ret=listen(sListen,10);if(SOCKET_ERROR==ret){return false;}return true;
}
int main(int argc,char**argv)
{
InitSocket();while(true){//有一个事件被触发等待函数即返回。int dwIndex=WSAWaitForMultipleEvents(totalEvent,eventArray,false,WSA_INFINITE,false);if(dwIndex==WSA_WAIT_FAILED){break;}else {//有网络事件发生。WSANETWORKEVENTS wsanetwork;SOCKET s=socketArray[dwIndex-WSA_WAIT_EVENT_0];//传hEventObject为被触发的套接字,WSAEnumNetworkEvents函数,会将其设置为非触发态。无需手工设置。int ret=WSAEnumNetworkEvents(s,eventArray[dwIndex-WSA_WAIT_EVENT_0],&wsanetwork);if(ret==SOCKET_ERROR)//函数调用失败。{break;}//发生FD_ACCEPT网络事件。else if(wsanetwork.lNetworkEvents&FD_ACCEPT){if(wsanetwork.iErrorCode[FD_ACCEPT_BIT]!=0)//发生网络错误。{break;}else //接受连接请求。{SOCKET sAccept;if((sAccept=accept(socketArray[dwIndex-WSA_WAIT_EVENT_0],NULL,NULL))==INVALID_SOCKET){break;}//超过最大值。if(totalEvent>WSA_MAXIMUM_WAIT_EVENTS){closesocket(sAccept);break;}//将新接受的套接字加入套接字数组。socketArray[totalEvent]=sAccept;//创建套接字事件对象。if((eventArray[totalEvent]=WSACreateEvent())==WSA_INVALID_EVENT){break;}//为新接受的套接字重新注册网络事件,重新关联事件对象。不使用与监听套接字同样的属性,这点要注意!!!!。if(WSAEventSelect(sAccept,eventArray[totalEvent],FD_READ|FD_WRITE|FD_CLOSE)==SOCKET_ERROR)//接受的套接字,用于收发数据。{break;}totalEvent++;//总数加一。//将套接字加入链表。}}//发生FD_CLOSE网络事件。else if(wsanetwork.lNetworkEvents&FD_CLOSE){if(wsanetwork.iErrorCode[FD_CLOSE_BIT]!=0)//发生网络错误。{break;}else //连接关闭。{//删除链表中的该套接字。//关闭网络事件对象。WSACloseEvent(eventArray[dwIndex-WSA_WAIT_EVENT_0]);//将此套节字和事件对象从数组中清除。for(int i=dwIndex-WSA_WAIT_EVENT_0;i<totalEvent-1;i++){eventArray[i]=eventArray[i+1];socketArray[i]=socketArray[i+1];}totalEvent--;//总数减一。}}//发生FD_READ网络事件。else if(wsanetwork.lNetworkEvents&FD_READ){if(wsanetwork.iErrorCode[FD_READ_BIT]!=0)//发生网络错误。{break;}else //套接字可读。{//接收数据。}}//发生FD_WRITE网络事件。else if(wsanetwork.lNetworkEvents&FD_WRITE){if(wsanetwork.iErrorCode[FD_WRITE_BIT]!=0)//发生网络错误。{break;}else //套接字可写。{//发送数据。}