WSAEventSelect模型和WSAAsyncSelect模型区别
相同点:
1.都是由系统通知应用程序处理网络事件
2.都是异步的
不同点:通知机制不一样,WSAAsyncSelect模型是以windows的消息机制来通知应用程序的。WSAEventSelect模型是以事件形式通知的。所以一个需要窗口,一个则不需要。
模型过程
相关函数详解
WSACreateEvent
WSACreateEvent 函数的返回值很简单,就是一个创建好的事件对象句柄,接下来必须将其与某个套接字关联在一起,同时注册自己感兴趣的网络事件类型
1 int WSAEventSelect( 2 3 SOCKET s, //代表感兴趣的套接字 4 5 WSAEVENT hEventObject, //指定要与套接字关联在一起的事件对象,即用 WSACreateEvent 创建的那一个 6 7 long lNetworkEvents //对应一个“位掩码”,用于指定应用程序感兴趣的各种网络事件类型的一个组合。 8 9 );
其中参数 lNetworkEvents可以用以下数值进行OR操作:
FD_READ 应用程序想要接收有关是否可读的通知,以便读入数据
FD_WRITE 应用程序想要接收有关是否可写的通知,以便写入数据
FD_ACCEPT 应用程序想接收与进入连接有关的通知
FD_CONNECT 应用程序想接收与一次连接完成的通知
FD_CLOSE 应用程序想接收与套接字关闭的通知
WSAWaitForMultipleEvents
DWORD WSAAPI WSAWaitForMultipleEvents( DWORD cEvents, //等待的事件数 const WSAEVENT FAR* lphEvents,//事件对象数组 BOOL fWaitAll, //是否等待全部socket都有事件才返回 DWORD dwTimeout, //等待时间,毫秒为单位,WSA_INFINE表示无限期等待 BOOL fAlertable //先置为FALSE );
cEvents 和 lphEvents 参数定义了由 WSAEVENT 对象构成的一个数组。在这个数组中,cEvents指定的是事件对象的数量,而lphEvents对应的是一个指针,用于
直接引用该数组。要注意的是,WSAWaitForMultipleEvents 只能支持由 WSA_MAXIMUM_WAIT_EVENTS 对象规定的一个最大值,在此定义成64个。因此,针对
发出 WSAWaitForMultipleEvents 调用的每个线程,该 I/O 模型一次最多都只能支持64个套接字。假如想让这个模型同时管理不止64个套接字,必须创建额外的工作
者线程,以便等待更多的事件对象。
fWaitAll 参数指定了 WSAWaitForMultipleEvents 如何等待在事件数组中的对象。若设为TRUE,那么只有等 lphEvents 数组内包含的所有事件对象都已进入“已
传信”状态,函数才会返回;但若设为FALSE,任何一个事件对象进入“已传信”状态,函数就会返回。就后一种情况来说,返回值指出了到底是哪个事件对象造成了函数的
返回。通常,应用程序应将该参数设为 FALSE,一次只为一个套接字事件提供服务。
dwTimeout参数规定了 WSAWaitForMultipleEvents 最多可等待一个网络事件发生有多长时间,以毫秒为单位,这是一项“超时”设定。超过规定的时间,函数就会
立即返回,即使由 fWaitAll 参数规定的条件尚未满足也如此。考虑到它对性能造成的影响,应尽量避免将超时值设为0。假如没有等待处理的事件,
WSAWaitForMultipleEvents 便会返回 WSA_WAIT_TIMEOUT。如 dwTimeout 设为 WSAINFINITE(永远等待),那么只有在一个网络事件传信了一个事件对象
后,函数才会返回。
fAlertable 参数,在我们使用 WSAEventSelect 模型的时候,它是可以忽略的,且应设为 FALSE。该参数主要用于在重叠式 I/O 模型中,在完成例程的处理过程中
使用。
这个函数会返回一个index,表示是第几个网络事件对象,根据这个index也可以索引到socket,然后下面调用WSAEnumNetworkEvents来判断该socket上发生了什么事件。这样一来,我们的应用程序便可引用事件数组
中已传信的事件,并检索与那个事件对应的套接字,判断到底是在哪个套接字上,发生了什么网络事件类型。对事件数组中的事件进行引用时,应该用
WSAWaitForMultipleEvents 的返回值,减去预定义的值 WSA_WAIT_EVENT_0,得到具体的引用值(即索引位置)
WSAEnumNetworkEvents
1 int WSAEnumNetworkEvents( //成功返回0 2 SOCKET s, 3 WSAEVENT hEventObject, //该socket的网络事件对象 4 LPWSANETWORKEVENTS lpNetworkEvents //网络结构体指针 5 );
s 参数对应于造成了网络事件的套接字。
hEventObject 参数则是可选的;它指定了一个事件句柄,对应于打算重设的那个事件对象。由于我们的事件对象处在一个“已传信”状态,所以可将它传入,令其自动
成为“未传信”状态。如果不想用 hEventObject 参数来重设事件,那么可使用 WSAResetEvent 函数,该函数之前已经讨论过了。
lpNetworkEvents参数,代表一个指针,指向 WSANETWORKEVENTS 结构,用于接收套接字上发生的网络事件类型以及可能出现的任何错误代码。
案例代码
#include<iostream> #include"../common/initsock.h" using namespace std; CInitSock initSock; int main() { WSAEVENT eventArray[WSA_MAXIMUM_WAIT_EVENTS];//创建事件数组 SOCKET socketArray[WSA_MAXIMUM_WAIT_EVENTS];//创建套接字数组 int EventSum = 0; USHORT port = 4567; SOCKET Listen = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); sockaddr_in address; address.sin_family = AF_INET; address.sin_port = htons(port); address.sin_addr.S_un.S_addr = INADDR_ANY; if (::bind(Listen, (sockaddr*)&address, sizeof(address)) == SOCKET_ERROR)//绑定 { cout << "绑定套接字失败!" << endl; return -1; } ::listen(Listen, 5); WSAEVENT event = WSACreateEvent(); ::WSAEventSelect(Listen, event, FD_ACCEPT | FD_CLOSE); eventArray[EventSum] = event; socketArray[EventSum] = Listen; EventSum++; while (true) { int index = WSAWaitForMultipleEvents(EventSum, eventArray, false, WSA_INFINITE, false); for (int i = index; i < EventSum; i++) { index = ::WSAWaitForMultipleEvents(1, &eventArray[i], TRUE, 1000, FALSE); if (index == WSA_WAIT_FAILED || index == WSA_WAIT_TIMEOUT) { continue; } else { WSANETWORKEVENTS event; ::WSAEnumNetworkEvents(socketArray[i], eventArray[i], &event); if (event.lNetworkEvents & FD_ACCEPT) { if (event.iErrorCode[FD_ACCEPT_BIT] == 0) { if (EventSum > WSA_MAXIMUM_WAIT_EVENTS) { cout << "连接过多!" << endl; continue; } SOCKET NewSocket = ::accept(socketArray[i], NULL, NULL); WSAEVENT event = ::WSACreateEvent(); ::WSAEventSelect(NewSocket, event, FD_READ | FD_CLOSE | FD_WRITE); eventArray[EventSum] = event; socketArray[EventSum] = NewSocket; EventSum++; } } else if (event.lNetworkEvents & FD_READ) { sockaddr_in remoteAddress; if (event.iErrorCode[FD_READ_BIT] == 0) { char message[1000]; int ind = ::recv(socketArray[i], message, strlen(message), 0); if (ind > 0) { message[ind] = '