IO多路复用是一种同步IO模型,在IO操作中,阻塞IO在等待IO操作时如果不满足条件会阻塞挂起,导致一个线程在同一时间只能处理一个IO,而非阻塞方式在进行轮训操作时,当有多个IO需要同时监听时,处理就会很麻烦。IO多路复用同一时间多个不同的IO操作,调用线程在调用时阻塞等待,当有任意监听的IO操作时就会返回。
select模型
采用Reactor模式实现
select 函数原形
#include <sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
其中 nfds是监听的最大描述符
fd_set *readfds, fd_set *writefds,fd_set *exceptfds这三个分别为需要监听读事件、写事件、异常条件对应的描述符集合,对其不敢兴趣可以设置为空。
fd_set定义
typedef struct
{
#ifdef__USE_XOPEN
__fd_mask fds_bits[__FD_SETSIZE/__NFDBITS];
#define__FDS_BITS(set)((set)->fds_bits)
#else
__fd_mask __fds_bits[__FD_SETSIZE/__NFDBITS];
#define__FDS_BITS(set)((set)->__fds_bits)
#endif
}fd_set;
fd_set其实现采用了位图,即文件描述符对应的那一位如过为1则监听此文件描述符,__FD_SETSIZE的值为1024即最大可以监听1024个字节,
操作fd_set采用以下宏函数
void FD_CLR(int fd, fd_set *set); // 用来清除描述词组set中相关fd 的位
int FD_ISSET(int fd, fd_set *set); // 用来测试描述词组set中相关fd 的位是否为真
void FD_SET(int fd, fd_set *set); // 用来设置描述词组set中相关fd的位
void FD_ZERO(fd_set *set); // 用来清除描述词组set的全部位
最后一个参数设置select()的等待时间,如果填空则表示一直阻塞直到出现被监听的时间触发,传入一个struct timeval并且将时间设置为0则表示只检查一次直接返回, 时间设置为一个大于0的数表示需要等待固定的时间,如果在这段时间内无时间触发则直接返回。
返回值
执行成功则返回文件描述符状态已改变的个数
如果返回0代表在描述符状态改变前已超过timeout时间,没有返回
当有错误发生时则返回-1,错误原因存于errno,此时参数readfds,writefds, exceptfds的值变成不可预测。
select执行过程,先通过提供的宏函数设置相应位的文件描述符,如建设select最多只有8位,则定义一个set 通过FD_ZERO设置为0000 0000 当需要监听的fd = 5 则通过FD_SET(fd, &set)设置为0001 0000如果需要监听的fd还有 3则通过同样的办法设置为0001 0100此时通过select监听需要监听的事件,如文件描述符5事件触发则返回,此时set为 0001 0000 3的位置会被清空,可以通过FD_ISSET判断被触发的事件。
select缺点:
select可以监听的描述符是由限制的,
其需要遍历添加的所有描述符,当fd数量特别多时开销非常大
其会清除没有触发的描述符,所以每次都需要将fd 重现添加到set中,而且需要在外面将所有添加的描述符保存起来,以供重新添加和返回时寻找触发的描述符。
poll
poll的函数原形int poll(struct pollfd *fds, nfds_t nfds, int timeout);
其中fds为监听的事件集合,用结构体数组存储,nfds为这个结构体数组元素个数,timeout是监听时间。
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};
pollfd中fd为描述符,events为需要监听的事件,revents为返回的事件,
常量值 | events | revents | 说明 |
---|---|---|---|
POLLIN | Y | Y | 普通或优先带数据可读 |
POLLRDNORM | Y | Y | 普通数据可读 |
POLLRDBAND | Y | Y | 优先级带数据可读 |
POLLPRI | Y | Y | 高优先级数据可读 |
POLLOUT | Y | Y | 普通数据可写 |
POLLWRNORM | Y | Y | 普通数据可写 |
POLLWRBAND | Y | Y | 优先级带数据可写 |
POLLERR | N | Y | 发生错误 |
POLLHUP | N | Y | 发生挂起 |
POLLNVAL | N | Y | 描述符不是一个打开的文件 |
poll返回值和select相同
poll和select相比,其可以监听的描述符数量没有上限,但是poll也是一种轮训的方式,当数量特别多时,其效率会比较低,
另外poll不能跨平台使用select 可以
最后还有每次调用poll都需要把大量的pollfd结构从⽤用户态拷贝到内核中,select需要拷贝的字节数小于poll,当监听数量特别大时poll需要拷贝的数量也会增加会导致其效率线性下降。