【网络编程】之9、事件选择WSAEventSelect

【网络编程】之九、事件选择WSAEventSelect

WSAEventSelect模型是类似于WSAAsyncSelect模型的另一个有用的异步I/O模型。它允许应用程序在一个或者多个套接字上接收以事件为基础的网络事件。 在这里,最主要的差别是在于网络事件会投递到一个事件对象句柄。并不是投递到一个窗口。

我们使用事件模型前,我们的应用程序针对使用的每一个套接字首先要创建一个事件对象:

WSAEVENT WSACreateEvent(void);

下面是注册自己感兴趣的网络事件类型:

int WSAEventSelect( 
   _In_  SOCKET s,   //代表感兴趣的套接字
   _In_  WSAEVENT hEventObject,//指定要与套接字关联在一起的事件对象。用WSACreateEvent取得。
   _In_  long lNetworkEvents );//对应一个  位掩码   用于指定应用程序感兴趣的各种网络事件类型的一个组合。

当我们用WSACreateEvent创建了事件,他有两种工作状态和两种工作模式:

1、工作状态:已传信signaled 和 未传信nonsignaled。

2、工作模式:人工重置manual reset  和  自动重置auto reset。


刚开始的时候,wsacreateevent是一种未传信的工作状态,并且是人工重置的模式来创建一个句柄。   但是我们网络事件触发了与一个套接字关联在一起的事件对象,工作状态便会从“未传信”转变为“已传信”,由于事件对象是 一种人工重置模式创建的,所以在完成了衣蛾I/O请求处理后,我们的应用程序需要负责将工作状态改变为“未传信”。   要这样我们需要一个函数:

BOOL WSAResetEvent(
  __in          WSAEVENT hEvent
);
上面这个函数唯一的参数是一个事件句柄,成功返回TRUE,失败返回FALSE;


当我们完成一个事件对象的处理后,我们要调用WSACloseEvent函数,释放由事件句柄使用的系统资源:

BOOL WSACloseEvent(
  __in          WSAEVENT hEvent
);

这个函数也是只有一个参数:事件句柄。 成功返回TRUE,失败返回FALSE;


当我们的一个套接字和一个事件句柄关联在一起后,应用程序就开始I/O处理,通过WSAWaitForMultipleEvents函数等待网络事件来出发事件对象句柄的工作状态。(这个函数用来等待一个或者多个事件对象句柄,并在事先指定的一个或所有句柄进入“已传信”状态后,并在超过一个规定的事件周期后立即返回)

DWORD WSAWaitForMultipleEvents(
  __in          DWORD cEvents,//和下面的参数一起顶一个了由WSAEVENT对象构成的一个数组,数组中cEvents指定的是事件对象的数量,lphEvents对应的是一个指针,用于直接引用该数组
  __in          const WSAEVENT* lphEvents,
  __in          BOOL fWaitAll,//指定了这个函数如何等待在事件数组中的对象
  __in          DWORD dwTimeout,//规定了这个函数最多可等待一个网络事件发生有多少时间,毫秒为单位,超时规定的事件会立即返回函数。
  __in          BOOL fAlertable//在用WSAEventSelect模型的时候,可以忽略它,要设为FALSE。 这个参数主要用于在重叠I/O模型中。
);

来看一个第三个参数fWaitAll。  如果这个参数你设为TRUE,那么直有等待lphEvents数组内包含的所有事件对象都进入“已传信”状态函数才返回。   如果你设为FALSE,那么任何一个事件对象进入“已传信”状态,函数就会返回。

通常应用程序应将这个参数设为FALSE,一次只为一个套接字事件提供服务。


第四个参数dwTimeout要注意的是尽量避免将超时值设为0, 加入没有等待处理的事件,WSAWaitForMultipleEvents便会返回WSA_WAIT_TIMEOUT。

你将这个参数设为:WSAINFINITE(永久等待),那么只有当在一个网络事件传信了一个事件对象后函数才会返回。


当WSAWaitForMultipleEvents 收到一个事件对象的网络事件通知,便会返回一个值。指出造成函数返回的事件对象。  我们的应用程序就可以引用事件数组中已传信的事件,并检索与那个事件对应的套接字,判断到底是在那个套接字上,发生了什么网络事件类型。对于事件数组中的事件进行引用时,应该用WSAWaitForMultipleEvents的返回值 减去预定义的WSA_WAIT_EVENT_0得到具体的引用值:

Index = WSAWaitForMultipleEvents(...);
MyEvent = EventArray[Index - WSA_WAIT_EVENT_0];

当我们知道了造成网络事件的套接字以后,下面可以调用WSAEnumNetworkEvents函数,调查发生了什么类型的网络事件:

int WSAEnumNetworkEvents(
  __in          SOCKET s,//对应于造成网络时间的套接字
  __in          WSAEVENT hEventObject,//可选参数,制定了一个事件句柄,对应于打算重设的那个事件对象。  我们的事件对象本来是"已传信“状态,你将它传入,令其自动成为”未传信“状态。   如果你不想用这个参数来重设事件,你可以用函数WSAResetEvent。
  __out         LPWSANETWORKEVENTS lpNetworkEvents//一个指针,指向WSANETWORKEVENTS结构。接收套接字上发生的网络事件类型以及可能出现的任何错误代码。
);
typedef struct _WSANETWORKEVENTS {
  long lNetworkEvents; //指定一个值,对应于套接字上发生的所有网络事件类型。
  int iErrorCode[FD_MAX_EVENTS];//制定一个错误代码数组。与lNetworkEvents中的事件关联在一起,对每个网络事件类型都存在着一个特殊的事件索引,妹子于事件类型的名字类似,只要在事件名字后面添加一个”_BIT"后缀字串就好。
} WSANETWORKEVENTS,  *LPWSANETWORKEVENTS;

看下面的实例:

if (NetwordEvents.lNetworkEvents & FD_READ)
{
	if (NetworkEvents.iErrorCode[FD_READ_BIT] != 0)
	{
		printf("FD_READ failed with error %d\n", NetworkEvents.iErrorCode[FD_READ_BIT]);
	}
}

当完成了对WSANETWORKEVENTS结构中的事件的处理之后,我们的应用程序应在所有可用的套接字上继续等待更多的网络事件。

下面来看一下使用步骤:

1、建立一个socket。

2、建立一个event。

3、用WSAEventSelect将socket,和event关联起来。lNetworkEvents可以为 FD_ACCEPT ,FD_READ ,FD_WRITE,FD_CLOSE 等等.

4、等待事件句柄:index = WSAWaitForMultipleEvents(EventTotal,EventArray,FAlSE,100/*WSA_INFINITE*/,FALSE);

5、查询网络事件: int WSAEnumNetworkEvents(SOCKET s, WSAEVENT hEventObject,LPWSANETWORKEVENTS lpNetworkEvents)

6、用lpNetworkEvents.lNetworkEvents & FD_ACCEPT ,lpNetworkEvents.lNetworkEvents & FD_CLOSE找到各个事件处理。



#include <WinSock2.h>
#include <iostream>
using namespace std;

#pragma comment(lib, "WS2_32.lib")

int main()
{
	WSAEVENT eventArray[1024];
	SOCKET sockArray[1024];
	int nEventTotal = 0;

	WSADATA wsadata;
	WORD sockVersion = MAKEWORD(2, 0);
	WSAStartup(sockVersion, &wsadata);

	SOCKET s  = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

	sockaddr_in sin;
	sin.sin_family = AF_INET;
	sin.sin_port = htons(8888);
	sin.sin_addr.S_un.S_addr = INADDR_ANY;
	if (bind(s, (sockaddr*)&sin, sizeof(sin)) == SOCKET_ERROR)
	{
		cout << "error" << endl;
		return 0;
	}

	listen(s, 5);

	WSAEVENT event = ::WSACreateEvent();
	::WSAEventSelect(s, event, FD_ACCEPT|FD_CLOSE);

	eventArray[nEventTotal] = event;
	sockArray[nEventTotal] = s;

	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, 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)
						{
							cout << " Too many connections! " << endl;
							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++;
					}
				}
				else if(event.lNetworkEvents & FD_READ)            // 处理FD_READ通知消息
				{
					if(event.iErrorCode[FD_READ_BIT] == 0)
					{
						char szText[256];
						int nRecv = ::recv(sockArray[i], szText, 256, 0);
						if(nRecv > 0)                
						{
							szText[nRecv] = 0;
							cout << "接收到数据:" << szText << endl;
						}
					}
				}
				else if(event.lNetworkEvents & FD_CLOSE)        // 处理FD_CLOSE通知消息
				{
					if(event.iErrorCode[FD_CLOSE_BIT] == 0)
					{
						::closesocket(sockArray[i]);
						for(int j=i; j<nEventTotal-1; j++)
						{
							eventArray[j] = eventArray[j+1];
							sockArray[j] = sockArray[j+1];    
						}
						nEventTotal--;
						i--;
					}
				}
				else if(event.lNetworkEvents & FD_WRITE)        // 处理FD_WRITE通知消息
				{
				}
			}
		}
	}

	return 0;
}

2012/9/2

jofranks 于南昌