十、I/O复用之select

一、I/O复用概念

在TCP网络编程时,我们一般会按照下面这个框架实现TCP:

在这里插入图片描述

从上图我们可以得知:

  1. 红色的循环表示完成多次数据的收发,即一次建立连接,多次收发数据,是一种长连接
  2. 外面蓝色的循环代表着循环接受客户端的连接。即服务端监听套接字一直存在,可以和多个客户端建立连接,连接客户端的个数由系统调用listen函数的backlog参数决定,可以和客户端完成三次握手建立连接的有(backlog+1),处于客户端数据交互的1个。

这种框架的 缺点很明显:

  • 如果我们多个客户端连接,A建立连接后,B再次连接时,程序无法处理,因为只有一个进程。这样是串行处理,A处理完才可以处理B。这样我们就无法并发处理多个客户端的请求,效率低下。 就相当于我们不能一次接听两个人的电话,当有两个电话同时来了,你只能选择一个接听处理,处理完这个再去处理另一个。
  • 客户端程序一直占用服务器,并没有实际的数据交互。相当于你接听了A的电话,但它一直不说话,你还必须等着。这就导致服务器一直被无用客户端占用,没有实际的数据交互,浪费时间,导致其他有业务逻辑额客户端一直等待

现在我们希望你可以同时接听多个电话,哪个电话说话了,你再进行交互,这样就可以解决上述TCP框架的缺陷。

这时我们 引入I/O复用它可以同时监听多个客户端描述符,哪个描述符有业务了再去处理它。 可以将监听套接字和客户端套接字放到一个集合中,进行监听,谁就绪就去处理谁,如下图所示:

在这里插入图片描述

这样就可以 避免一个客户端一直占用,其他客户端得不到连接的情况。在下列情况下需要使用I/O复用技术:

  • TCP服务器同时要处理一个监听套接字和多个连接套接字。
  • 服务器要同时处理TCP请求和UDP请求。
  • 程序要同时处理多个套接字。
  • 客户端程序需要同时处理用户输入和网络连接
  • 服务器要同时监听多个端口。

I/O复用的缺点: I/O复用虽然 可以同时监听多个文件描述符,但它本身是阻塞的,并且 当多个文件描述符同时就绪时,如果不采取额外措施,程序就 只能按顺序处理其中的每一个文件描述符,这使得服务器看起来好像是串行工作的,如果要 提高并发处理的能力,可以配合使用多线程和多进程等方法。如取快递,所有物品在这放着监视着,当有人来取给他取,取的人多了就要排队。

二、I/O复用select函数功能和作用

【1.select描述图:】

I/O复用有很多方式,今天我们来讲述其中的select方式,下图是I/O复用的描述图:
在这里插入图片描述
可以看到收集描述符和事件的集合中有两种类型的socket

  • 服务器的socket, 用来监听TCP,是否有客户端进行连接,服务器关闭,关闭它。
  • 客户端的socket, 如c1,在和服务器建立连接前,创建客户端套接字c1,服务器accept进行三次握手,用来和服务器进行数据交互,全部数据交互结束后,close关闭。

2. select函数

# include<sys/select.h>
# include<sys/time.h>
# include<sys/types.h>
# include<unistd.h>
int select(int maxfd, fd_set *readfds,fd_set*writefds, fd_set*exceptfds, struct timeval *timeout)
              //成功返回大于0的数值,-1失败,0超时

【2-1参数详解:】

1. maxfd:

表示select轮询监听集合中文件描述符的个数,设置为所有监听的文件描述符的最大值+1。目的是提高select底层的执行效率。假如我们文件描述符为4,则maxfd为5,我们在扫描判断事件时,就不需要,从0-1023的遍历,只需要从0-5即可,如下图:
在这里插入图片描述

2. fd_set* readfds,fd_set* writefds, fd_set* exceptfds: 三个参数的结构是一样的,分别记录可读,可写,异常事件。

(1)fd_set类型的结构:
在这里插入图片描述
如果用int/long类型4字节来保存一个文件描述符,那么这个结构最多可以放8个,因为只有32位;但int/long保存一位数据过于浪费,所以它的存储设计的很巧妙:
fd_set的大小:是一个4字节的数组,数组大小为32位,换算成位就是:4*8*32=1024位,所以一个select最多存储1024位描述符。使用每一个比特位记录一个文件描述符,文件描述符的值在位移上表现。
假如现在存储描述符fd=4,如图所示:
在这里插入图片描述

(2) 对fd_set结构体操作的函数:

FD_ZERO(fd_set * set )//清除fdset的所有位
FD_SET (int fd,fd_set* set )//将fd设置到fdset结构体上
FD_CLR (int fd,fd_set * set );//清除fdset的位fd
int FD_ISSET( int fd,fd_set* set );//测试fdset的位fd是否被设置。

(3)三个参数的作用:

  • 在select调用时,将用户关注的可读、可写、异常事件的文件描述符传递给select底层,即内核。比如说:如果用户都关注的是读事件,就将这几个文件描述符都设置到readfds里面去,因为其就代表的是读事件的文件描述符集合。
  • 在线修改:如果readfds结构存储了2个描述符,select返回2,但其实它并不知道具体是哪两个就绪,还是在结构体中表示着。所以它的第二个功能就是当select返回时,将修改的结构体给我们带回来,称为在线修改就是当select调动的时候,这个结构体里面是用户填充的内容,返回时是内核修改的内容,均为就绪文件描述符。如果用户给fd_set传了10个事件描述符,那么返回时可能fd_set只有3个,所以我们在select返回时,不仅要看select的值,我们还要用函数Int FD_ISSET(int fd,fd_set*fdset);来检测结构体中的描述符是否被修改即是否有事件发生,所以传入地址,可以让内核进行修改。在线修改示意图:
    在这里插入图片描述
    在线修改也引发了一个问题,我们select返回的set_fd下次不可以直接用,假如传入10个,最后剩2个,你再继续调用则会出错,这就是问题,所以规定每次调用select之前,都必须重新设置三个结构体变量。 用户程序必须通过某种机制记录所有的文件描述符,以便重新设置,其次在测试描述符是否被修改时需要以此为基准**,可以用数组或其他结构来记录,每次select之间用数组再次初始化。**

3. timeout
指定的一个时间,定时时间 。select是在一定的时间内轮询检测描述符,如果超出此时间,select会返回0,表示时间到了还未检测到描述符就绪。

  • 如果指定为NULL,表示永久阻塞。
  • 也可以按照struct timeval time = {x, y}的方式指定。其结构体表示如下:
    struct timeval
    {
       long tv_sec //秒数
       long tv_usec;//微妙数
    };
    
    struct timeval time = {5, 0}就表示轮询时间为5秒。

3. select实现

下图表示了select实现的一个过程:

在这里插入图片描述

三、I/O复用select的特点

【1.优点:】

  • select是最常见的IO模型,可以对多个客户端进行监听处理。
  • 解决了TCP只能串行处理客户端的问题,实现了并发处理。

【2.缺点:】

  • select能监听的文件描述符个数受限于FD_SET结构体,一般为1024,解决1024以下客户端时使用select是很合适的,但如果链接客户端过多,select采用的是轮询模型,会大大降低服务器响应效率。
  • 每次调用select,需要将fd描述符集合从用户态拷贝到内核态,开销很大。
  • fd_set结构体用户态保存所有文件描述符,调用select后,返回的是就绪文件描述符。即一个结构体保存了两种状态的文件描述符,故需要辅助空间,进行结构体初始状态保存,每一次调用select之前都要重新初始化fd_set。

四、代码实现I/O复用select

首先我们先理清楚 逻辑:

  • 只捕捉可读事件,所以定义fd_set readfds结构体即可.
  • 为了解决在线修改问题,我们用数组来保存用户的描述符,每次select之前都应该用数组重新初始化fd_set readfds的值。
  • 检测到就绪事件,判断为监听套接字(和服务端建立连接)还是连接套接字(收发数据);如果是监听套接字,就需要添加进数组中,一个服务端数据发送完成需要删除这个描述符,所以需要数组初始化,添加,删除函数。如果为连接套接字,那么直接接收回复数据即可。
  • 我们把处理事件的函数单独实现,收到客户端发来的数据,服务端回复个ok。

我们画出具体的流程图(不严谨的)意在说明过程,更好理解:

在这里插入图片描述
处理数据部分 分两种情况

  • 服务端运行时创建的listenfd监听套接字,初始化为服务器的信息,就会将其添加到readfds数组中。
  • 客户端创建的监听套接字,初始化为服务器的信息,那么用connect进行三次握手时,发送的是和服务端一样的listenfd套接字,这时select返回的就绪数组中,listenfd就会就绪,表示新的连接
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值