高级IO】select实现IO多路转接
该类型的IO是以系统提供的函数select
为核心工作的
认识select函数
1 |
|
功能
select
系统调用能够同时监测多个文件描述符的状态变化,这个系统调用是阻塞式的,退出阻塞等待的条件是被监视的文件描述符中有一个或多个发生了变化。
参数解释
nfds
: 文件描述符数组长度,值为最大的文件描述符值+1。因为文件描述符从0开始readfds
: 本质上是位图
,表示待监视的可读文件描述符的集合,返回时标记发生变化的fdwritefds
: 本质上是位图
,表示待监视的可写文件描述符集合,返回时标记发生变化的fdexset
: 本质上是位图
,表示待监视的异常文件描述符的集合,返回时标记发生变化的fdtimeout
: 用于设置select()
的等待时间
struct timeval
1 | struct timeval |
struct timeval
是一个储存时间的结构体,它提供了两个成员变量用于分别存储秒和毫秒,因此该参数有三种传参类型
NULL
,表示禁用select
的超时功能,若没有文件描述符发生变化,将永久阻塞
{0,0}
,表示阻塞时间为0
,即不等待也不会阻塞,仅用于检测文件描述符状态变化非零
,若在给定的时间内无文件描述符的变化,就会发生超时返回
fd_set
1 | /* fd_set for select and pselect. */ |
虽然在形式上看fd_set
内部存储了一个数组,但本质来讲就是一个位图
,用相应位置的比特位
的0/1
状态表示该文件描述符是否在集合中
同时select.h
也提供了一系列接口来帮助操作这些封装在fd_set
里的位图
把它们按C接口的风格表示如下.实际上它们都是封装过的宏函数
1 | void FD_CLR(int fd, fd_set *set); // 用来清除描述词组set中相关fd 的位 |
返回值分析
返回非零
: 执行成功,返回文件描述符状态改变的个数返回0
: 代表超时返回,没有检测到状态改变返回-1
: 发生错误,错误的原因会设置在errno,此时输出型参数readfds
,writefds
,exceptfds
,timeout
的值变得不可预测
errno
对应的错误为:
EADF
文件描述词为无效的或该文件已关闭EINTR
此调用被信号所中断EINVAL
参数n 为负值。ENOMEM
核心内存不足
select就绪条件
读就绪
- socket内核中, 接收缓冲区中的字节数, 大于等于低水位标记SO_RCVLOWAT. 此时可以无阻塞的读该文件描述符, 并且返回值大于0;
- socket TCP通信中, 对端关闭连接, 此时对该socket读, 则返回0;
- 监听的socket上有新的连接请求;
- socket上有未处理的错误;
写就绪
- socket内核中, 发送缓冲区中的可用字节数(发送缓冲区的空闲位置大小), 大于等于低水位标记
SO_SNDLOWAT
, 此时可以无阻塞的写, 并且返回值大于0;- socket的写操作被关闭(close或者shutdown). 对一个写操作被关闭的socket进行写操作, 会触发SIGPIPE信号;
- socket使用
非阻塞connect连接
成功或失败之后; - socket上有未读取的错误;
特点/使用要点
- 可监控的文件描述符个数取决于
sizeof(fd_set)
- 需要额外的数据结构
array
保存放到select
监控集中的fd
,原因如下- 传入的
fd_set
是输出型参数,返回后待监控的发生变化的fd
对应的标志位会置1
,而未发生变化的fd
对应的标志位会清空,导致原本传入的参数信息丢失,所以需要额外存储
- 传入的
缺点
由此我们可以总结出select
的一些缺点
- 每次调用前都要设置待监测的
fd
集合 - 每次调用
select
都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大 - 同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大
select
支持的文件描述符数量太小
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 supdriver的博客!
评论