windows网络编程-事件选择

在了解完了select模型之后大家应该意识到了,是微软为我们提供了fd_set和select函数这两个关键部件才有了select模型。而接下来的几个更加高效并且只是稍微复杂了一点的网络模型同样是微软为我们提供了一些别的工具来实现的,所以我们现在所学的内容就是windows网络编程。


在正式介绍模型之前还是要先来了解一下windows的一些其他内容。

事件(event)
windows的一个内核对象,windows本身可以检查一个事件是否发生,并且在发生之后作出回应。

这个机制有用的地方就在于,我们的服务器接收到一个客户端的socket连接申请可以是一个事件,收到一个客户端的消息也可以是一个事件。如果我们把网络中的这些操作都当做是事件,由windows操作系统来帮我们监视这些事件并做出回应,随后再进行处理,是不是很熟悉?这不就是select模型嘛。
我有说过select模型就像是一个更大的阻塞操作,把所有的阻塞都放在一起,哪个发生了就处理哪个。而这次的模型也就象是一个select函数嘛,只不过上一个模型检测的是socket,这一次是一个事件而已。一个socket对应的会有连接,收发消息等等事件,在这个模型中,我们要做的就是更加细致的事件处理罢了。


在大致的了解模型结构之后就是具体的实现方法了。首先还是先了解一下另一个新的数据类型。

typedef void *HANDLE;
#define WSAEVENT                HANDLE
WSAEVENT eventS = WSACreateEvent();;

这就是我们所谓的事件本体了
好像有点不明所以,void*,HANDLE,WSAEVENT。这都什么玩意。
首先,一个void*类型的指针,他是一个通用类型的指针,不需要显式转换就可以转换成任何其它类型的指针。而HANDLE(句柄),WSAEVENT(事件),都是微软封装好内容为我们提供的一个抽象接口,之所以会是void*类型,是保护内核隐藏细节和避免用户可能做出影响操作系统正常运行的事情。

就像创建socket一样,我们通过WSACreateEvent函数创建一个事件,与之对应的,我们同样需要WSACloseEvent函数用来关闭一个事件,在使用结束之后及时的关闭是非常必要的。

就像我们不需要理解为什么SOCKET会是一个无符号整型一样,我们也不需要知道为什么WSAEVENT是void*,但是我们可以使用他做到一些令人激动的事情就可以了。当了解的足够多,足够深入的时候,再对这些问题展开深究也会是一件有趣的事情。

好的,现在我们有了一个事件,一个可以符合windows标准的事件。但是基于逻辑简单想想就会发现。这是个啥事件?客户端连接?服务器收到消息?客户端下线?windows哪知道这些,我们想要windows帮我们检测一个事件也要告诉windows要具体检测的东西嘛。就像让别人等个信号,也是要告诉人家信号是太阳下山,还是一个口哨。接下来就是告诉windows要等的事件什么。

WSAEventSelect(socketS, eventS, FD_ACCEPT);
  • 在这个函数中前两个参数已经非常明确了,一个是我们的服务器socket,另一个就是刚刚创建的event。看到这就应该意识到我们的socket和event已经产生了联系。socket有点什么动静,evnet就会被置为有信号,windows告诉我们event有信号了我们就知道来活了,该要处理对应的的socket了。
  • 在服务器上自然不会只有一个socket也就不会只有一个event,所以要在accept之后额外的加上创建event并和客户端socket通过这个函数绑定这一操作。
  • 至于第三个参数和别的细节等到了接受客户端socket再提。

event和socket也产生链接了,那么我们怎么知道这个event是否被触发了呢?在select模型中我们通过select函数询问一个socket数组来检查是否有socket需要处理,在这个模型中我们也有一个对应的select函数,只不过这次检查的对象就是event了。

res = WSAWaitForMultipleEvents(
    _In_ DWORD cEvents,  //DWORD无符号长整形,select模型fd_set作为参数,在fd_set中有一个count表示数量
    					//而在该模型中使用输入参数cEvents来告知检查的事件数量
    _In_reads_(cEvents) const WSAEVENT FAR * lphEvents,  // 事件数组
    _In_ BOOL fWaitAll,  // 等待全部事件响应返回TRUE还是有事件响应就返回FALSE
    _In_ DWORD dwTimeout,  // 超时时间
    _In_ BOOL fAlertable  // 完成端口相关,暂且置为FALSE
    );
  • 这个函数的返回值就是触发的事件在这个数组中所对应的下标,
  • 我们有了一个和select相似的函数自然而然可以用同样的方式使用它。
while(1)
{
res = WSAWaitForMultipleEvents(count,events, FALSE, INFINITE, FALSE);
int index = res - WSA_WAIT_EVENT_0;  // 获取对应的事件下标
{
	// 进行接下来的处理
}
}

tips:其实官方文档说了还要减去一个宏WSA_WAIT_EVENT_0才是对应的数组下标,看了一下这个宏的值是0,减去0还是原数,也不知道为啥要加这一步。

拿到了触发事件之后就可以顺着找到所对应的绑定的socket,至于怎么找嘛,当时自己绑定的肯定还是要自己来找啊。
搞两个数组嘛,一个装socket另一个装event,分别和对应下标相同的一一绑定,拿到下标之后直接用到另一个数组就能找到对应的socket/event。
在select模型中服务器socket有响应就是有客户端连接,客户端socket有响应就是接收到了消息。而在本次的模型中也是大差不差,并且多了一些额外的定制内容。

#define FD_MAX_EVENTS    10
typedef struct _WSANETWORKEVENTS {
       long lNetworkEvents;
       int iErrorCode[FD_MAX_EVENTS];
} WSANETWORKEVENTS, FAR * LPWSANETWORKEVENTS;

WSAEnumNetworkEvents(
    _In_ SOCKET s,
    _In_ WSAEVENT hEventObject,
    _Out_ LPWSANETWORKEVENTS lpNetworkEvents
    );
  • 前两个参数就很明了了,一个socket一个event,第三个参数是一个输出的结构体。而他就和我们上文WSAEventSelect函数中的第三个参数有关联。看变量名字我们可一大概猜到,一个表示网络事件,另一个是一个数组形式的错误码。
  • 为了理解这些,还需要了解事件选择模型我们提供的“定制内容”。
#define FD_READ_BIT      0
#define FD_READ          (1 << FD_READ_BIT)

#define FD_WRITE_BIT     1
#define FD_WRITE         (1 << FD_WRITE_BIT)

#define FD_ACCEPT_BIT    3
#define FD_ACCEPT        (1 << FD_ACCEPT_BIT)

#define FD_CONNECT_BIT   4
#define FD_CONNECT       (1 << FD_CONNECT_BIT)

#define FD_CLOSE_BIT     5
#define FD_CLOSE         (1 << FD_CLOSE_BIT)
  • 上面的这些宏定义我们已经见过了,就在绑定socket和event的函数中,我使用了一个FD_ACCEPT作为第三个参数。而这个参数的意思就是检查accept,当时间被置为有信号的时候就意味着,我们的服务器可以进行accept接受客户端socket。
  • 而FD_***_BIT形式的宏不过是一个数字罢了,这一段宏定义一共有10个不过我这里只贴出了常用的一部分而已。如果有10个的话,是不是又很熟悉?就在定义WSANETWORKEVENTS结构体的时候里面的errorcode数组大小不就是10个嘛。
  • 现在就可以梳理出来:

    在WSAEventSelect函数中,使用FD_**这样的宏作为参数,来告知绑定的具体事件是一个accept,亦或者read,write等等。再根据这个宏本身定义的形式可以发现,他们是支持与运算来同时绑定多个事件的,而与之对应的,在WSAEnumNetworkEvents函数中返回的结构体内有一个lNetworkEvents数据提供给我们再次和这些宏进行与运算来确定都有哪些事件触发。
    而在结构体的另一个iErrorCode数组中就可以使用形如FD_**_BIT的宏作为下标来得知是否是因为socket发生了错误。如果出现错误,则数组对应的下标值被置1。所以我们每一次在检测到事件激活之后还要加上错误码检查确定不是错误发生。

WSANETWORKEVENTS wsanete = { 0,{0} };
res = WSAEnumNetworkEvents(sockets[index], events[index], &wsanete);
if (wsanete.lNetworkEvents & FD_ACCEPT)
	{
		if (0 == wsanete.iErrorCode[FD_ACCEPT_BIT])  // 判断是否客户端socket出现错误
		{
			sockets[count] = accept(socketS, NULL, NULL);  // 一般不能这么写,因为接受客户端socket可能会出错返回一个INVALID_SOCKET,但是反正测试一般不会出错
			events[count] = WSACreateEvent();
			WSAEventSelect(sockets[count],events[count], FD_READ | FD_WRITE| FD_CLOSE);
			count++;
		}
	}
if(wsanete.lNetworkEvents & FD_READ)
{
// 进行自己的处理,打印客户端发送数据并返回什么的,
}
if(wsanete.lNetworkEvents & FD_CLOSE)
{
// 释放客户端soacket,关闭他对应的event,
// 以及比较关键的,处理自己的sockets和events数组,可以仿照fd_set提供的一些宏,比较简单
}
// ....后续可以添加额外的if判断进行处理
  • 这一段代码的的逻辑是比较简单的,唯一多了一点就是在接受客户端socket之后,我们同样要创建一个event,然后将他和接收到的socket进行绑定,至于具体要使用那几个事件宏就取决于开发者自己的需求了。

  • 好像内容挺多,有点复杂。但其实抛开这里的代码相关的这些东西专注模型本身的话。其实和select模型的区别也就在于一个检查socket,另一个检查event而已。看来来好像差不多的两个模型,但是事件选择明显在进行处理方面还要更加繁琐复杂一点,那为什么还是要选择事件模型呢?
  • 还记得开头我们提到了什么么,windows的事件处理机制,我们借用了windows的处理机制来优化我们的模型,毫无疑问的,使用windows机制会让他在底层上的处理更加高效一点,这就是为什么我们要选择事件模型。
  • 但是事件选择模型也不是我们的终点,虽然它看起来已经非常美好了,选择模型解决了我们的阻塞,再通过windows机制加强我们的效率,但是为了满足现在各种服务器每天成千上万的数据吞吐量只是这样的模型还是远远不够的。
  • 并且这个模型本身依旧有一些机制上带来的不便之处,WSAWaitForMultipleEvents函数检查事件数组中的事件,运行没有问题并且没有超时的话返回的内容其实是关联数组中触发事件下标最小的事件的下标。这就意味着,当同一事件快速的再进行处理的时候再次出发,下一次检查的时候这个时间依旧会处于激活状态,从而一直处理该事件,轮不到后续的事件了。

所以说微软依旧为我们提供额外的更加高效的解决方案,让我们后续一起研究。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值