select,poll,epoll


什么是select

1
2
int select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset, 
const struct timeval *timeout);
  • maxfdp1:select函数需要传入程序监听的最大的文件描述符+1的值
  • readset,writeset,exceptset:读,写,异常文件描述符数组,通常是int数组,例如数组的每个元素占64位,第一个int数的各个位代表0-63号文件描述符,第二个数代表64-127,如果位为0,则表示不监听,如果位为1,则代表监听.该数组是一种value-result的形式,也就是我们将感兴趣的文件描述符传入进去,如果文件描述符处于就绪状态,那么结果也是在这个数组体现.内核只能从set中从头遍历才能知道我们要监听哪些文件描述符.调用方需要主动传入maxfdp1可以让内核知道判断到哪个位之前结束,也就是客户所需要的文件描述符的最大值,内核不会判断超出这个数字的其他描述符.
1
2
3
4
struct timeval { 
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};
  • timeval:超时参数

    • 一直等待:如果调用select时不传入这个参数,则代表只有当描述符可以准备进行IO,select才返回.
    • 等待固定时间:如果传入参数且时间步为0,表示如果超出这个时间,则不再等待文件描述符处于准备状态.
    • 立即返回:传入参数,但时间设置成0,表示调用函数去检查是否描述符可用,检查完之后函数会立即返回,这种方式被称作轮询.
  • 返回值:

    • 正常情况:readset,writeset,exceptset中打开的位数量的总和,函数返回后,我们需要再次遍历各个集合,如果位被设置为1,则代表描述符可用.
    • 0:代表超时
    • -1:代表发生错误

什么是poll

1
int poll(struct pollfd *fdarray, unsigned long nfds, int timeout);
  • fdarray:监听的描述符结构体数组
  • nfds:监听的描述符数量
  • timeout:超时参数
  • 返回值为可用的文件描述符数量,既revents累加的所有发生的事件的总量.此时我们需要遍历nfds个文件描述符去判断某个文件描述符是否可用.这里不像select的位图实现,而是明确传入一个结构,内核可以直接遍历去判断,并且内核判断的结果在revents上面体现,这种方式比起select已经把值和结果分开了.
1
2
3
4
5
struct pollfd {
int fd; //要检查的描述符
short events; //感兴趣的事件
short revents; //实际发生的事件
};

我们需要要检查的描述符传入到poll中,具体events和revents如何表示可读,可写,异常情况由具体的实现声明.

epoll

epoll的运行流程

  1. 使用epoll在内核空间创建了epoll实例
  2. 我们可以通过epoll_ctl去添加自己感兴趣的文件描述符,称为interest_list,这个支持我们动态的去添加描述符.
  3. 如果epoll实例发现任何感兴趣的文件描述符变得可用,然后把这些文件描述符放入一个称为ready_list的结构,
  4. 当我们调用epoll_wait去获取可用的描述符时,如果ready_list不为空,那么就会将可用的文件描述符立即返回,此时可以通过文件描述符去获取数据

我们只需要注册好epoll实例,之后再去获取可用的描述符,这个过程不像select和poll一样是异步的.

select,poll,epoll的区别

  • select和poll的区别是select通过位图传递监听的描述符,而poll直接传递结构来监听描述符
  • select和poll每次获取可用的描述符时,都需要传入监听的描述符列表,并且每次进程都得扫描哪些描述符可用. 而epoll通过epoll实例进行注册,获取可用的描述符列表时,不需要再传入监听的描述符列表,并且进程可以直接从epoll实例获取到哪些描述符可用.select和poll采用的是多路复用模型,而epoll采用的更像是事件驱动IO模型.epoll还可以动态的添加想要监听的描述符,而select,poll需要修改参数去重新调用.

参考资料


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!