(一)Windows网络模型之select模型和事件选择模型

1 Select模型
  1. select模型解决的问题是基本的C/S模型中,accept函数recv函数傻等阻塞问题
  2. 另外还使服务器可以与多个客户端连接,与多个客户端分别通信
  3. 除了傻等阻塞还有一种阻塞为执行阻塞,例如send()、recv()等函数执行的过程是执行阻塞的,但是这不是select模型解决的问题
  • 基本的原理是:

    • 首先:服务器这边有两种SOCKET;一种是我们自己主动创建的SOCKET,暂称为服务器SOCKET,另一种是接收到客户端来连接之后又accept()函数返回的SOCKET,暂称为客户端通信SOCKET
    • 通过将服务器SOCKET和客户端通信SOCKET通通装进一个数据结构(数组)中,然后调用select函数,遍历数据结构中的SOCKET,当某个Socket有响应,再做相应的处理
    • 如果是服务器SOCKET---->调用accept
    • 如果是客户端通信SOCKET---->调用recv或send
  • 上面所说的数据结构其实就是fd_set

    typedef struct fd_set {
            u_int fd_count;               	/* how many are SET? */
            SOCKET  fd_array[FD_SETSIZE];   /* an array of SOCKETs */
    } fd_set;
    
服务器:(前4步与基本C/S模式一样)
  1. 打开网络库并校验

  2. 创建服务器SOCKET

  3. 绑定服务器的IP和PORT

  4. 启动监听

  5. 调用select()函数

    • 一般可以放在一个循环之中,循环调用select函数
    int WSAAPI select
    (
      int           nfds,		// 忽略,直接填0
      fd_set        *readfds,	// 这是一个双向参数,将fd_set的副本传址进去,函数执行结束后,该参数代表的数据中仅包含有可读响应的socket(如需要执行recv或accept)
      fd_set        *writefds,	// 同上,返回结果表示有可写响应的socket(进行send),可以填NULL
      fd_set        *exceptfds,	// 通常,表示有异常的socket
      const timeval *timeout	// 最长等待时间,NULL表示完全阻塞(等到客户端有响应才会返回)
    );
    // 返回值:		
    //	0 	表示客户端在等待时间内没有响应
    //	>0	表示客户端有有响应或请求
    // 	SOCKET_ERROR 表示出错
    

结合select()函数的处理逻辑如下(伪代码):

while(1){
	int res = select(...);
    if(res == 0) {
        // 表示客户端无响应
        continue;
    } else if (res > 0) {
        // 表示有响应
 		// 遍历响应select函数第二个参数的fd_set
        for(int i = 0; i < fdset.count; i++){
            // 如果是服务器socket  调用accept 与新的客户端建立连接
            if (fdset.array[i] == socketSever){
                SOCKET newsocket = accept(socketSever, NULL, 0);
                //将新的Socket装进全局的fdset
                FD_SET(newsocket, &allfdset);
            }
            // 如果是客户端通信socket,接收数据recv
            else {
                int nRes = recv(fdset.array[i], buffer, len, 0);
                printf(buffer);
            	// 如果检测到客户端正常下线则从全局fdset中删除以及应关闭全局fdset中的对应socket
            }
        }
    } else {
        // 出错处理
        // 在winsock中 强制下线也是错误的一种
    }
}
客户端:同基本C/S一样
  1. 打开网络库并校验
  2. 创建SOCKET
  3. 连接到服务器
  4. 与服务器进行通信
总结:
  • 所谓网络模型的不断优化,其本质就是在解决函数调用过程中的阻塞问题(傻等阻塞与执行阻塞)
  • select模型解决的是基本C/S模型中的傻等阻塞(accept和recv)
    • accept函数在没有客户端前来连接的时候会一直阻塞等待,也就是傻等阻塞
    • recv函数进行通信,在对端没有发来数据的时候也就一直阻塞等待,也就是傻等阻塞
  • 但是select函数本身也是阻塞的,这属于执行阻塞,recv和send在执行的时候造成的阻塞也是执行阻塞,比如数据有点多,在发送和接收数据时就会执行阻塞
  • 通常在实际应用中用的最多的就是select函数的第2个参数,也即可读的socket响应

场景:

  • ​ 小用户量访问,几十、几百,简单方便。

如何回答对select模型的认识?

首先select模型是用在服务器端的,客户端代码并没有什么改变。

而最基本的服务器端的代码逻辑为①创建socket(socket())②绑定本地IP和端口号(bind)③启动监听(Listen)④接收来自客户端的连接(accept)⑥连接建立成功后使用recv、send函数接收、发送数据(Unix系统上是read和write函数)

但存在一个问题:也即是accept函数以及recv函数都是阻塞的,而且是傻傻等待的那种,只要对方没有连接请求或发送数据,程序就卡死了

为了解决这个阻塞的问题,select模型就出现了:

  • 通过ft_set以及相应的宏去操作fd_set,传递给select函数(参数2:关心可读socket,参数3:关心可写socket,参数4:关心异常socket)
  • 可以通过循环反复调用select函数,根据select的返回参数,确定是否存在需要处理的socket,如果有就进行相应的处理。
  • 如此就解决了accept函数与recv/read函数等存在的阻塞傻等问题,优化了程序

2 事件选择模型(基于事件机制–核心是事件集合)

什么是事件机制?

答:

  • Windows处理用户行为的两种方式之一
  • 事件机制的核心是事件集合,程序员通过为某些行为或动作绑定一个事件(本质可能就是一个ID),我们通过调用相应的API函数创建事件;
  • 然后将事件集合投递给操作系统,操作系统会帮我们监测这些事件,当事件发生时就将其设置为有信号;事件被响应后设置为无信号
  • 事件的响应是无序的,因此不一定会严格按照事件发生的顺序响应,有些最先发生的事件不一定最早被处理;最晚发生的事情也可能更早的被处理。

Windows下的事件选择模型最具有代表性的API函数是WSAEventSelect(),可以简单理解为select()函数的升级版!

事件选择模型的逻辑

  1. 创建事件WSACreateEvent(),目前创建的事件没有绑定任何操作或动作的,是一个单纯的内核对象

  2. 使用WSAEventSelect()绑定socket上的某个行为或操作并投递给操作系统监管,此时我们的程序可以去做其他的事情。因为这是一个异步的操作,就解决了select模型select()函数本身执行阻塞的情况

    通过绑定某个 socket[参数1]某个动作[参数3]某个事件[参数2]上,将事件、行为与socket联系起来

    通常会创建一个结构如下:

    struct fd_es_set
    {
    	unsigned short count;						// 表示事件数目
    	SOCKET sockall[WSA_MAXIMUM_WAIT_EVENTS];	// 表示socket数组
    	WSAEVENT evnetall[WSA_MAXIMUM_WAIT_EVENTS];	// 表示event数组,与socket数组中相同下表处的元素一一对应
    };
    

    服务器如果接收了新的连接,就会产生新的客户端通信Socket,然后创建新的事件并绑定到该socket之后投递给系统

    然后再添加到fd_es_set

    为什么一定要创建fd_es_set呢?

    回答:为了获取有信号的事件,并进一步处理相应的socket,然后做出正确的动作

    int WSAAPI WSAEventSelect
    (
      SOCKET   s,				// 某个socket
      WSAEVENT hEventObject,	// 某个事件对象
      long     lNetworkEvents	// 某个动作  如:ACCEPT  READ  WRITE
    );
    
  3. 查询事件是否有信号—WSAWaitForMultipleEvents()

    一般的用法是循环的去询问~

    函数返回值指明有信号的事件在事件列表中的索引(其实是返回值 - WSA_WAIT_EVENT_0

    事件列表是参数2

    DWORD WSAAPI WSAWaitForMultipleEvents(
      DWORD          cEvents,		// 有效事件数目
      const WSAEVENT *lphEvents,	// 事件列表
      BOOL           fWaitAll,		// true表示等待所有的事件有信号再返回  false表示任意一个事件有信号就返回
      DWORD          dwTimeout,		// 超时时间或者一直等待或者立即返回
      BOOL           fAlertable		// 重叠I/O模型填TRUE 否则填FALSE
    );
    
  4. 有信号时就分类处理WSAEnumNetworkEvents()

    注意:WSAWaitForMultipleEvents()只会返回一个事件索引,如果同时有多个事件有信号,会返回索引最小的事件

    获得有信号的事件以及对应的socket后,还需要知道是什么操作触发了信号,因此要使用WSAEnumNetworkEvents()函数

    该函数通过参数3lpNetworkEvents返回出信号的事件类型,也即具体操作

    int WSAAPI WSAEnumNetworkEvents(
      SOCKET             s,					// 有信号的事件绑定的socket
      WSAEVENT           hEventObject,		// 有信号的事件
      LPWSANETWORKEVENTS lpNetworkEvents	// 这是一个指针,本质是一个传出参数,其中返回了触发信号的具体操作
    );
    

    之后就是基于NetWorkEvents的分类逻辑处理

关于内核对象:

①内核对象由操作系统在内核中申请,由操作系统进行访问,也就是说所有对内核对象的操作都需要经过系统调用

②创建和释放都需要调用相应的函数,如果没有释放,就会造成内核资源泄露,只能重启电脑才能解决

③常见的windows内核对象,socketeventfilethreadMutex信号量


如何回答对事件选择模型的理解?

事件选择模型是基于事件机制的,事件机制的核心是事件集合,该模型是简单的select模型的升级版,解决了select模型中select()执行阻塞的问题。

通过将套接字socket事件event以及相应的行为如,有客户端连接socket可写可读等绑定在一起并投递给操作系统,让操作系统进行监测,如果有相应的行为或事件发生,该事件就会被置为有信号状态。

之后通过相应的API函数,获取到有信号的事件以及对应的socket。然后再调用API函数进一步获知触发事件的具体行为。

最后根据这些行为进行对应的业务处理,是该accept接收连接还是发送或接收数据等。

其中最关键的是将事件交给操作系统监测与程序执行本身是异步的,这就解决了select模型中select函数执行阻塞的问题。
在这里插入图片描述
推荐阅读:(二)Windows网络模型之异步选择模型(基于消息机制)

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

咖啡与乌龙

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值