• 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;
      
      • 1
      • 2
      • 3
      • 4
    服务器:(前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 表示出错
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12

    结合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中 强制下线也是错误的一种
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    客户端:同基本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和send函数)

    但存在一个问题:也即是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数组中相同下表处的元素一一对应
      };
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6

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

      然后再添加到fd_es_set

      为什么一定要创建fd_es_set呢?

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

      int WSAAPI WSAEventSelect
      (
        SOCKET   s,				// 某个socket
        WSAEVENT hEventObject,	// 某个事件对象
        long     lNetworkEvents	// 某个动作  如:ACCEPT  READ  WRITE
      );
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
    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
      );
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
    4. 有信号时就分类处理WSAEnumNetworkEvents()

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

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

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

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

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

    关于内核对象:

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

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

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


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

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

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

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

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

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

  • 相关阅读:
    springcloud
    拷贝构造函数(深拷贝+浅拷贝)
    Redis入门
    excel数据分析模块
    Unity 动画知识点
    RabbitMQ 基本介绍
    基于Java电动车上牌管理系统计实现(源码+lw+部署文档+讲解等)
    《第一堂棒球课》:王牌三垒手·棒球5号位
    Python正则表达式操作(re模块使用篇)
    数据结构系列学习(三) - 单链表详解(Linked_List)
  • 原文地址:https://blog.csdn.net/qq_40459977/article/details/126151786