I/O多路复用 ---- select、poll、epoll

一、什么是I/O多路复用

当我们需要同时处理多个描述符时,使用单一描述符读写循环阻塞方式,会造成另一个描述符无法读写,造成大量数据无法处理丢失的状况。这时候就需要采用一些方法来处理多个描述符。

  • 方法一:多进程
    设置多个进程,每个进程处理一个描述的通路数据。这样则每个描述符都可以单独进行读写阻塞,并且不会对其他描述符读写造成影响。
    但缺点是,事实上处理的描述符是不同的,但是事务却是同一事务,进程也只是父子进程关系,当该事务的一个部分出现问题或者需要终止时,需要与其他亲子进程进行通信,同时终止,程序实现较为复杂。
  • 方法二:非阻塞I/O
    仍然使用单一进程,但是调用非阻塞I/O读取数据,将多个描述符都设为非阻塞,例如对第一个描述符进行read,该描述符上如果有数据,那么进行read,如果没有数据,则read立即返回,并对下一个描述符进行处理,知道遍历完成所有描述符,等待若干时间在进行下一次循环遍历,这种循环机制也被称作轮训机制。
    这种方法的缺点显而易见是会造成CPU时间浪费,因为每个描述符可能很少会有数据,而却要不停地去循环读取,造成CPU大量空转。并且,循环一遍的时间也并不容易确定,可能需要根据实际需求去指定这个时间,也难以避免会产生阻塞时间碎片,还是浪费时间。
  • 方法三:异步I/O
    该方法的思想是,当一个描述符准备好进行I/O时,那么发送一个信号告知内核,我准备好了,快调用我。
    该方法的问题有两个,一是并不是所有系统都支持该方法。其次计算系统支持,每个进程都只对应一个该信号,当该信号发出时,内核也很难去确定到底是哪个描述符准备好进行I/O了。
  • 方法四:I/O多路复用
    I/O复用的基本思想是,先构造一张有关描述符的表,然后调用一个函数,该函数会根据需要去遍历这张表,知道这些描述符中有一个描述符准备好进行I/O时,它才返回,并且会告诉进程,是哪一个描述符准备好进行I/O了。
    该方法用了两个重要的函数select和poll。

二、select函数

1.select函数的工作机制

首先传向select的参数会告诉内核:

  • 我们所关心的描述符集
  • 对于每个描述符的关心条件(是否读、写、异常)
  • 希望等待多长时间
    从select返回时,我们会得到:
  • 已经准备好的描述符的数量
  • 哪一个描述符已经准备好我们所关心的条件
    使用返回值,我们就可以对返回的描述符进行I/O操作,并且确认该函数不会阻塞。

2.select函数的原型

#include <sys/types.h>/* fd_set data type */
#include <sys/time.h> /* struct timeval */
#include <unistd.h> /* function prototype might be here */
int select (int maxfdp1, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *tvptr)
  • 第一个参数maxfdp1的意思是“最大fd加1(max fd plus 1)”。在三个描述符集中找出最高描述符编号值,然后加 1,这就是第一个参数值。也可将第一个参数设置为FD_SETSIZE,这是一个< sys/types.h>中的常数,它说明了最大的描述符数(经常是256或1024)。但是对大多数应用程序而言,此值太大了。确实,大多数应用程序只应用3 ~ 10个描述符。如果将第三个参数设置为最高描述符编号值加 1,内核就只需在此范围内寻找打开的位,而不必在数百位的大范围内搜索。
  • 中间三个参数readfds、writefds和exceptfds是指向描述符集的指针。这三个描述符集说明了我们关心的可读、可写或处于异常条件的各个描述符。每个描述符集存放在一个fd_set数据类型中。
    在这里插入图片描述
    关于fd_set类型变量有四个重要的宏:
FD_ZERO(fd_set, *fdset);			//给所有描述符位清零
FD_SET(int fd, fd_set *fdset);		//设置为我们关心的描述符
FD_CLR(int fd, fd_set *fdset);		//关闭之前的关心设置
FD_ISSET(int fd, fd_set *fdset);	//测试指定描述符位是否依旧为关心
  • 最后一个参数,它指的是愿意等待的时间。
struct timeval {
	long tv_sec;
	long tv_usec;
}

当tvptr == NULL时,表示永远等待。当所有指定的描述符中有一个准备好,或者捕捉到一个中断信号时返回。如果是信号返回,则函数返回值为-1,errno设置为EINTR。

当tvptr->tv_sec == 0 && tvptr->tv_usec == 0 时,表示完全不等待。测试所有指定的描述符并立即返回。这是得到多个描述符的状态而不阻塞select函数的轮询方法。

当tvptr->tv_sec != 0 || tvptr->tv_usec != 0 时,表示等待指定的秒数或者微秒数,这种等待也可能捕捉到中断信号。

三、poll函数

poll函数和select类似,但是调用形式有所不同。

#include <stropts.h>
#include <poll.h>
int poll(struct pollfd fdarray[],unsigned long nfds,int timeout);

与select不同,poll不是为每个条件构造一个描述符集,而是构造一个pollfd结构数组,每个数组元素指定一个描述符编号以及对其所关心的条件。

struct pollfd {
int fd; /* file descriptor to check, or < 0 to ignore */
short events; /* events of interest on fd */
short revents ; /* events that occurred on fd */
}

nfds表示描述符数组fdarray的元素数量。
将events成员设置为下表中所示值的一个或几个。通过这些值告诉内核我们对该描述符关心的是什么。返回时,内核设置revents成员,以说明对该描述符发生了什么事件。(注意,poll没有更改events成员,这与select不同,select修改其参数以指示哪一个描述符已准备好了。)
在这里插入图片描述表中头四行测试可读性,接着三行测试可写性,最后三行则是异常条件。最后三行是由内核在返回时设置的。即使在events字段中没有指定这三个值,如果相应条件发生,则在revents中也返回它们。
当一个描述符被挂断后(POLLHUP),就不能再写向该描述符。但是仍可能从该描述符读取到数据。

poll的最后一个参数说明我们想要等待多少时间。如同select一样,有三种不同的情形:
- timeout == INFTIM,表示永远等待;
- timeout == 0,表示不等待;
- timeout > 0,表示等待多少毫秒;

四、epoll函数

待更新。。。

参考材料《UNIX环境高级编程》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值