IO多路转接select

多路转接其实叫做就绪时间通知机制。

功能

select只负责等待fd,等到fd就绪,就通知上层进行读取或者写入,并且select可以等待多个fd。select没有所谓的读取和写入数据的功能。

特点

与read, write, recv, send本身的等待功能不同的是,这些函数的等待只能传入一个fd,而select可以传入多个fd,也就是能够同时等待多个fd。

函数原型

int select(int nfds, fd_set *readfds, fd_set *writefds,
           fd_set *exceptfds, struct timeval *timeout);

参数

  1. nfds是等待的最大的fd值加一;

  2. fd_set是一个位图结构,可以理解为一个由比特位作下标(第几个比特位就代表第几个fd)组成的数组,如果取fd_set的长度为1字节,那么这个结构可以表示8个比特位,也就是8个fd。参数列表中有三个这样的结构,分别代表只关心读事件就绪,写事件就绪和异常事件就绪。

既然是数组,那么大小肯定有限制,其内部实现就是取决于一个FD_SETSIZE,通常是1024,然后换算过来位图的大小通常是128

所以这三个参数就代表着:用户要等待哪些事件(读、写、异常),就传入进去,它们分别是读、写、异常的文件描述符集合。

这个结构是一个输入输出型参数,输入的时候代表等待哪些fd,输出的时候代表哪些fd已经就绪:

例如我要等待0-6的文件描述符,除了fd=2:

输入的时候:
在这里插入图片描述

输出的时候(假如除了2,3其他都已经就绪,3的标志位被清空):

在这里插入图片描述

所以每次等待的时候,都要重新对需关心的描述符重新设置。(因为报文不一定读完,或者此时未就绪的fd下一次还要继续等待) 这也是select最大的缺点。

并且需要将需等待的fd另外保存起来,因为fd_set会对全部fd进行清空。

等待哪几个fd,是由用户来告知内核的

以读事件为例,select的核心功能就是:

  • 用户告知内核,需要帮我关心哪些fd上的读事件就绪
  • 内核告知用户,哪些你所关心的fd上的读事件已经就绪

这就是readfds位图的功能,其他两个位图同理。

设置fd_set的时候不能自己手动设置1,0,而是要通过系列接口:

在这里插入图片描述

阻塞与非阻塞

  1. select的等待时间也可以设置:非阻塞(0),阻塞(NULL),设置deadline,这个时间取决于最后一个参数timeout。这也是一个输入输出型参数,例如设置等待5s,5s过后还未就绪,就返回0s;若3s的时候就绪,就返回2s。

timeval是一个结构体:

在这里插入图片描述

  1. 阻塞等待:如果希望 select 函数一直阻塞等待直到有文件描述符就绪,可以将 timeout 设置为 NULL,即不设置超时时间。这样,select 将一直阻塞等待,直到有就绪的文件描述符。
struct timeval *timeout = NULL;  // 不设置超时时间
  1. 非阻塞等待:
struct timeval timeout = {2 , 0};
// 等待2秒
// 微秒部分设置为0

需要注意的是,这个timeout也会在一轮等待之后被清零,所以timeout的设置需要放在循环体内,从而每次循环开始都进行一次时间的设置。

使用

accept如果没有新链接到来,会进行阻塞式等待。那我们可以让select去等待,直到新链接到来以后,再让accept等函数去进行真正的读写。

select的优缺点

优点:

  1. 高效的多路复用:select 允许同时监视多个文件描述符的可读、可写或异常事件,可以在一个线程中管理多个 I/O 任务,提高系统的并发处理能力,避免了单个线程阻塞等待 I/O 完成的情况。
  2. 跨平台支持:select 是 POSIX 标准中定义的函数,几乎在所有主流的操作系统上都可以使用,包括 Linux、Windows、MacOS 等。
  3. 简单易用:使用 select 的编程接口相对简单,不需要繁琐的线程和进程管理,只需要一个主循环对事件进行监听,并根据事件类型执行相应的操作。
  4. 节省资源:相比每个连接使用一个线程或进程的模型,select 能够节省系统资源,根据事件的到达情况来分配处理的线程或进程,减少了线程或进程的创建和销毁开销。

缺点:

  1. 执行效率有限:select 采用轮询方式依次检查所有注册的文件描述符,当文件描述符数量较大时,效率会受到影响。
  2. 文件描述符数量限制:由于select是通过位图的方式传递fd,那么位图的大小是有限制的,在操作系统里一般是限制为1024。
  3. 编程模型限制:使用 select 进行开发时,需要编写较为复杂的状态机逻辑,处理多个文件描述符的事件,代码可读性较差,容易出错。
  4. 不支持扩展性:select 采用同步阻塞方式,当任意一个文件描述符就绪时,整个 select 调用将返回并处理事件,无法支持异步、非阻塞的特性。

总体而言,select 在一些简单的网络应用中是一种可行的选择,但在面对大规模并发连接和高性能要求的场景下,可能会选择其他更为高效的 I/O 复用机制,例如 epoll。

select和recvfrom的关系

由于recvfrom适用于从无连接的套接字里接受数据的,所以select常用于帮助recvfrom提前等待好fd。

本来没有select的话,recvfrom会一直处于阻塞等待状态,或者说设置了非阻塞,如果内核还未将数据准备好, 系统调用仍然会直接返回, 并且返回EWOULDBLOCK错误码;这种情况下只能等待一个fd。

更重要的是:非阻塞IO往往需要程序员循环的方式反复尝试读写文件描述符, 这个过程称为轮询. 这对CPU来说是较大的浪费, 一般只有特定场景下才使用。

而有了select以后,相当于在recvfrom之前给你将你想等待的fd等待好了,并且是多个fd同时在等待;此时的recvfrom就不存在阻塞的问题了。

select和accept的关系

accept通常是将就绪的连接请求拿到服务端,通常情况下它会阻塞式等待。

如果有了select,使用 select 函数来监听多个文件描述符,包括服务器的监听套接字和已连接的客户端套接字。当有新的连接请求到达时,select 函数会返回监听套接字的就绪状态,然后可以使用 accept 函数接受连接请求并创建新的套接字来处理该连接。

select和listen的关系

当你在家里准备举办一个派对时,你告诉家人和朋友们你的家是一个开放的场所,别人可以来参加派对。这就类似于 listen 函数的作用,它准备了一个场所来接受客人的到来。

现在,你希望在派对期间同时处理不同的事情,例如和不同的人聊天、玩游戏或准备食物。你不想一次只处理一个任务,而是想同时处理多个任务,以提供更好的体验。这时就类似于 select 函数的作用,它允许你同时监控多个任务的“就绪”状态,以便在有需要时可以切换到不同的任务。

这样通俗的理解就可以看的出来他们的区别:listen是将端口设置为监听状态,而select是在这个基础上进行优化。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

久菜

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

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

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

打赏作者

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

抵扣说明:

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

余额充值