多路IO复用:select,poll

为什么使用多路IO复用

使用场景:
同时需要对多个低速IO进行读写操作
解决方法与问题:

  1. 最简单低速阻塞IO(pipe,FIFO,socket)会持续阻塞直到有数据时读入,如果有两个读fd,第二个fd只有当前一个fd完成读入操作后,才能读取自己的数据。如果第二个fd数据提前到达也无法处理。
  2. 对每一个阻塞读fd开辟一个线程/进程,除了开辟进程/线程需要分配/切换的资源,还需要考虑进程/线程同步的问题
  3. 非阻塞IO一定程度上可以解决单线程下顺序阻塞等待的问题,但是需要轮询忙等,这段时间进程不处于阻塞状态,轮询占用CPU时间片,造成CPU浪费
  4. 异步IO(还不太了解):asynchronous I/0存在可移植的问题。
  5. 多路IO复用:构造感兴趣fd列表,使用select/poll/pselect/epoll(linux 2.6+)执行多路IO复用,进程会告知哪些fd准备好进行IO

select

Linux man page

函数签名

/* According to POSIX.1-2001 */
#include <sys/select.h>

/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

int select(int nfds, fd_set *readfds, fd_set *writefds,
           fd_set *exceptfds, struct timeval *timeout);
输入参数
ndfs3类fd中感兴趣的最大fd值+1,避免后面对所有fd集合全部遍历,或者全都要FD_SETSIZE(1024)
readfdsfd_set 类型,可读fd标志位集合 ,NULL忽略
writefdsfd_set类型,可写标志位集合,NULL忽略
exceptdfsfd_set类型,处于异常标志位集合,NULL忽略
timeout传递一个微秒精度时间结构指针,控制select的最大等待时间
返回值
n准备好fd数目(3个fd_set之和),不同fd_set同一位fd都准备好计数多次,此时fd_set已准备好的fd位置位
0超时,没有描述符准备好,所有fd_set清空
-1出错,如在一个fd都没准备好捕获到信号,此时所有fd_set不会修改

timeout

timeout由于是微秒精度的,可以当作一个定时器来使用。:客户端socket使用connect非阻塞建立连接时,设置自定义的超时等待时间(阻塞socket fd connect超时等待时间75s?)。根据timeout传入指针不同,可以指定不同的等待效果:

  • NULL: 永远等待,感兴趣fd准备好返回。如果捕捉到信号,select返回-1,errno返回EINTR
  • 全0:不等待,等价于轮询
  • 不为0:在定时时间内,有一个fd准备好返回。超时没有fd准备好,返回0

fd_set

fd_set相当于一个大数组,每一个fd对应同编号一位01标志:

在这里插入图片描述
UNIX中专门定义了对于fd_set操作的函数:

/* According to POSIX.1-2001 */
#include <sys/select.h>

/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

void FD_CLR(int fd, fd_set *set);		// 清除fd位上标志
int  FD_ISSET(int fd, fd_set *set);		// 测试fd位标志,在fd_set中返回非0,不在返回0
void FD_SET(int fd, fd_set *set);		// 设置fd位上标志
void FD_ZERO(fd_set *set);				// 清空整个fd,在初始化一个fd_set必做

对于select中每一类型的fd_set,准备好的含义是如下的:

fd_set
readfds对应位fd进行读操作不阻塞,则是准备好的
writefds对应位fd进行写操作不阻塞,则是准备好的
exceptfds描述符有一个未决异常条件,则是准备好的(没太了解)

注意点:

  • 普通文件的IO,readfdswritedfsexcepdfs总是准备好的
  • fd是否阻塞不会影响select的定时阻塞,如果超时5s的select读一个非阻塞IO,select最多阻塞5s
  • 文件到达尾端,select会认为是可读的,read调用后返回0,可以用来判断到达尾端。

具体针对socket,下面的情况是读准备好:

  1. socket内核接收缓存区字节数>=低水位标记SO_RCVLOWAT,可以无阻塞读,recv返回>0
  2. scoket对方关闭连接,scoket的recv返回0
  3. socket有新的连接请求(对于listen socket fd调用accept
  4. socket有上未处理错误,getsockopt读取清除该错误

具体针对socket,下面情况写准备好:

  1. scoket内核发送缓冲区可用字节>=低水位标记SO_SNDLOWAT,可以无阻塞写,send返回>0
  2. socket写操作被shutdown,对写关闭的socket再执行send会触发SIGPIPE信号
  3. socket使用非阻塞connect连接成功/失败后
  4. socket有上未处理错误,getsockopt读取清除该错误

具体针对socket,下面情况异常条件准备好:

  1. socket接收到外带数据,即TCP中紧急数据urgent data,TCP URG控制位=1,send时设置MSG_OOB标志位,接收时发送SIGURG信号

pselect

#include <sys/select.h>

int pselect(int nfds, fd_set *readfds, fd_set *writefds,
            fd_set *exceptfds, const struct timespec *timeout,
            const sigset_t *sigmask);

select威力加强版,主要是提供了精度更高纳秒级的超时timeout,以及信号屏蔽字sigmask,如果是NULL等价于select

使用过程

以最简单的多路读IO为例:

  1. 创建多个低速系统调用读IO的fd
  2. 创建fd_set并且FD_ZERO清空,fd使用FD_SET置位
  3. 调用select阻塞等待
  4. 退出阻塞后,遍历前ndfs个fd,使用FD_ISSET判断是否准备好,准备好的fd调用read
  5. 处理好所有fd后,循环回到2

书上代码实例:Github

存在问题

  1. fd_set默认1024位,最多就处理这么多fd
  2. fd_set是不可重用的,就是说在一次select后,之前设置的感兴趣fd位都被重置,只有准备好的fd被保存,下次要IO时,select之前需要重新设置
  3. select只知道有一个fd准备好,但不知道具体是哪些,需要O(n)遍历

poll

Linux man page

函数签名

#include <poll.h>

int poll(struct pollfd *fds, nfds_t nfds, int timeout);
参数
fdspollfd结构体的数组,每一个结构指定了fd,以及对应感兴趣的条件
nfdspollfd注册的事件数量
timeoutpoll愿意等待时间,毫秒为单位

pollfd

poll不像select为每一个fd都有提供标志位,而是构造pollfd数组,结构如下:

struct pollfd {
    int   fd;         /* 监控的文件描述符 */
    short events;     /* 请求监控的事件请求 */
    short revents;    /* 内核设置,说明fd发生了哪种事件 */
};

可以看到,不同于select,设置监控的事件标志events和返回事件标志revents分离定义开来,poll返回时只用设置reventsevents保持不需要重置。
下面是具体事件的标志:
在这里插入图片描述

特点与问题

  1. 解决了select中固定长度fd_set的问题
  2. 解决了selectfd_set不可重用的问题
  3. 对于设置的所有pollfd,依然需要顺序遍历并判断感兴趣事件是否触发

参考资料

[1]. UNIX环境高级编程
[2]. Linux高性能服务器编程
[3]. 图解TCP/IP

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值