【网络程序设计】WSAeventselect WINSOCK I / O模型

390 阅读7分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第3天,点击查看活动详情


相关原理

1. WSAEventSelect模型

WSAEventSelect 模型也是 Winsock 提供的异步事件通知I/O模型,与 WSAAsyncSelect 模型类似,允许应用程序在一个或者多个套接字上接收基于事件的网络通知。不过 WSAEventSelect 模型不是依靠 Windows 的消息驱动机制,而是经由事件对象句柄通知,其机理类似于信号量机制。

 

2. 事件通知机制

基于事件通知,事件是一个有操作系统管理的内核对象,当前系统基于事件机制,该模型则可以使用,事件两种状态,有信号和无信号,检测事件,若有信号则进行相应处理。

 

3. 事件内核对象

1)组成

一个使用计数(与所有内核对象一样),

一个用于指明该事件是个自动重置的事件还是一个人工重置的事件的布尔值,

一个用于指明该事件处于已通知状态还是未通知状态的布尔值。

2)类型

一种是人工重置的事件,另一种是自动重置的事件。

当人工重置的事件得到通知时,等待该事件的所有 线程均变为可调度线程。

当自动重置的事件得到通知时, 等待该事件的线程中只有一个线程变为可调度线程。

当调用SetEvent时,可以将事件改为已通知状态:

BOOL **SetEvent (HANDLE hEvent);

当调用ResetEvent函数时,可以将该事件改为未通知状态:

BOOL ResetEvent (HANDLE hEvent);

 

4. 设计套接字数量大于64的服务端程序

Winsock2.h头文件中定义了许多FD_XXX宏,用于将应用程序移植到LUNIX环境中的Windows。这些宏与select和WSAPoll函数一起使用,用于将应用程序移植到Windows。Windows Sockets应用程序可以使用的最大套接字数不受清单常量FD_SETSIZE的影响。 

Winsock2.h文件中定义的此值用于构造与select函数一起使用的FD_SET结构。 

Winsock2.h中的默认值为64。

如果应用程序旨在能够使用select和WSAPoll函数处理超过64个套接字,则实现程序应在包含Winsock2.h头文件之前在每个源文件中定义清单FD_SETSIZE。 

执行此操作的一种方法可能是在生成文件中的编译器选项中包含定义。 例如,可以将“-DFD_SETSIZE=128”作为 Microsoft C++编译器命令行的选项。 必须强调,将FD_SETSIZE定义为特定值不会影响Windows套接字服务提供商提供的实际套接字数。 

此值仅影响 select和WSAPoll 函数使用的FD_XXX宏。

 

5. 绑定事件对象与套接字:

int WSAEventSelect(  
    SOCKET s,  
    WSAEvent hEventObject,  
    long lNetworkEvents//网络事件,就是异步选择中的网络事件,用法完全相同  
    );  

 对于事件来说,他有两种类型,自动事件和人工事件。有两种状态,未触发状态和触发状态。使用WSACreateEvent函数创建的事件默认为人工事件且处于未触发状态。随着网络事件触发了与一个套接字关联在一起的事件对象,工作状态便会从未触发状态转变成触发状态。由于事件对象是在一种人工重设模式中创建的,所以在完成了一个I/O请求的处理之后,我们的应用程序需要负责将工作
状态触发状态更改未触发状态。

6.重置事件为未触发状态:

该函数的功能就是把时间从触发状态重置为未触发状态

BOOL WSAResetEvent(WSAEVENT hEvent);

7.关闭事件对象,释放其所占用的内核资源:

BOOL WSACloseEvent(WSAEVENT hEvent);

8.监视事件对象的状态:

DWORD WSAWaitForMultipleEvents(  
    DWORD cEvents;//事件对象数组中事件的数目  
    const WSAEVENT FAR* lphEvents,//事件对象数组  
    BOOL fWaitAll,//该参数指明了是否要等到所有事件对象变为触发状态函数才返回  
    DWORD dwTimeout,//超时,毫秒为单位超过规定的时间,函数就会立即返回,即使由fWaitAll参数规定的条件尚未满足也如此  
    BOOL fAlertable//忽略,置为FALSE  
    );  
参数:  
    要注意的是,WSAWaitForMultipleEvents只能支持WSA_MAXIMUM_WAIT_EVENTS对象规定的一个最大值,在此定义成64个。

因此,针对发出WSAWaitForMultipleEvents调用的每个线程,该I/O模型一次最多都只能支持64个套接字。假如想让这个模型同时管理不止64个套接字,必须创建额外的工作者线程,以便等待更多的事件对象。fWaitAll 参数指定了指明了是否要等到所有事件对象变为触发状态函数才返回。若设为TRUE,那么只有等lphEvents数组内包含的所有事件对象都已进入触发状态,函数才会返回;但若设为FALSE,任何一个事件对象进入触发状态,函数就会返回。就后一种情况来说,返回值指出了到底是哪个事件对象造成了函数的返回。通常,应用程序应将该参数设为FALSE,通常,dwTimeout被置为0.
一次只为一个套接字事件提供服务  

 
函数解释:
一个套接字同一个事件对象句柄关联在一起后,应用程序便可开始I/O处理;方法是等待网络事件触发事件对象句柄的工作状态。WSAWaitForMultipleEvents函数的设计宗旨便是用来等待一个或多个事件对象句柄,并在事先指定的一个或所有事件对象进入触发状态后,
或在超过了一个规定的时间周期后,立即返回。
9.确定网络事件发生的套接字:若WSAWaitForMultipleEvents收到一个事件对象的网络事件通知,便会返回一个值,指出造成函数返回的事件对象。这样一来,我们的应用程序便可引用事件数组中已传信的事件,并检索与那个事件对应的套接字,判断到底是在哪个套接字上,发生了什么网络事件类型。对事件数组中的事件进行引用时,应该用WSAWaitForMultipleEvents的返回值,减去预定义值WSA_WAIT_EVENT_0,得到具体的引用值(即索引位置)。
Index=WSAWaitForMultipleEvents(...);
myEvent=EventArray[Index-WSA_WAIT_EVENT_0];

10.调查发生的网络事件类型:

int WSAEnumNetworkEvents(  
    SOCKET s,  
    WSAEVENT hEventObjects,//参数可选,对应于打算重设的事件对象,即设置事件为未触发状态。和WSAtResetEvent函数功能相同  
    LPWSANETWORKEVENTS lpNetworkEvents//用来接受发生的网络事件类型以及可能出现的任何错误代码  
    );  
该函数中的第四个参数用来接收发生的网络事件类型

11.WSANETWORKEVENTS结构:

tydef struct _WSANETWORKEVENTS  
{  
    long lNetworkEvents;//网络事件类型  
    long iErrorCode[FD_MAX_EVENTS];//错误代码  
}WSANETWORKEVENTS,FAR* LPWSANETWORKEVENTS;  
iErrorCode参数指定的是一个错误代码数组,同lNetworkEvents中的事件关联在一起。iErrorCode针对每个网络事件类型,都存在着一个特殊的事件索引,名字与事件类型的名字类似,只是要在事件名字后面添加一个“ _BIT”后缀字串即可.

12. 单线程 管理 多个套接字,最大 套接字数量 取决于FD_SETSIZE的大小,Winsock2.h中定义为64,用户也可自行定义,但不能超过1024。


创建过程

1. 创建事件对象

WSAEventSelect 模型的基本思路是为需要响应的一组网络事件创建一个事件对象,创建事件对象的函数是 WSACreateEvent() ,返回值是一个事件对象句柄。

接着再调用 WSAEventSelect() 函数将网络事件和事件对象关联起来。当网络事件发生时,Winsock 使相应的事件对象受信,在事件对象上的等待函数就会返回。

int WSAAPI WSAEventSelect( _In_ SOCKET s, _In_opt_ WSAEVENT hEventObject, _In_ long lNetworkEvents );

image.png

2. 事件授信

网络事件与事件对象关联之后,应用程序就可以在事件对象上等待事件了。WSAWaitForMultipleEvents() 函数用于在一个或多个事件对象上等待,当所等待的事件对象受信或者指定的时间过去时,此函数返回。

 

3. 查看网络事件

一旦事件对象受信,就找到与之对应的套接字,然后调用 WSAEnumNetworkEvents()  函数查看发生了什么网络事件。

// initsock.h

#include <winsock2.h>

#pragma comment(lib, "WS2_32")

class CInitSock

{

public:

CInitSock();

~CInitSock();

};

inline CInitSock::CInitSock()

{

// 初始化WS2_32.dll

WSADATA wsaData;

WORD sockVersion = MAKEWORD(2, 2);

if(::WSAStartup(sockVersion, &wsaData) != 0)

{

exit(0);

}

}

inline CInitSock::~CInitSock()

{

::WSACleanup();

}