select系统调用可参见此博客
poll
1、原型:
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
2、fds:用户数组的首地址,记录所有文件描述符以及关注的事件类型,将用户空间的数据传递给内核,在poll返回时,也用于将内核检测到就绪事件通知给应用程序。
- struct pollfd结构如下:
struct pollfd
{
int fd; //文件描述符
short events; //用户关注的事件类型能够关注的事件类型相对较多
short revents; //返回的事件 由内核填充,在poll返回时,将就绪事件类型通知给应用程序
};
3、nfds:要监视的描述符的数目。也就是第一个参数数组的长度。
4、timeout:是一个用毫秒表示的时间,是指定poll在返回前没有接收事件时应该等待的时间
5、返回值:
- 大于 0就绪文件描述符的个数
- 等于 0 超时
- == -1出错
epoll(Linux上独有的I/O复用方式)
#include <sys/epoll.h>
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
1、epoll_create
int epoll_create(int size);
- 创建一个内核事件表:用于记录用户关注的文件描述符以及其上的事件类型
- size用来告诉内核这个监听的数目一共有多大。不同于select()中的第一个参数。
- 当创建好epoll句柄后,它会占用一个fd值,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。
- 返回内核事件表的文件描述符
2、epoll_ctl
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
- 注册要监听的事件类型。
- 第一个参数是epoll_create()的返回值
- op 用三个宏来表示:
EPOLL_CTL_ADD
在文件描述符epfd引用的epoll实例上注册目标文件描述符fd。
EPOLL_CTL_MOD
修改已注册描述符fd关联的事件。
EPOLL_CTL_DEL
从epfd引用的epoll实例中删除(取消注册)目标文件描述符fd。该事件将被忽略,并且可以是NULL - 第三个参数:需要监听的fd
- 第四个参数:告诉内核需要监听什么事
struct epoll_event
{
__uint32_t events; /* Epoll 事件类型*/
epoll_data_t data; /* 用户数据*/
};
typedef union epoll_data
{
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
- events可以是以下几个宏的集合:
3、epoll_wait
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
- 等待事件的产生
- events : 用户数组,在epoll wait返回时,记录内核检测到的文件描述符上发生的事件
- 分配好的epoll_event结构体数组,epoll将会把发生的事件赋值到events数组中(events不可以是空指针,内核只负责把数据复制到这个events数组中,不会去帮助我们在用户态中分配内存)
- 只记录就绪的文件描述符
- maxevents : 告知内核这个events有多大
- timeout : 超时时间
三种I/O复用方式的比较总结
1、3组系统调用都能同时监听多个文件描述符。它们将等待由timeout参数指定的超时时间,直到一个或者多个文件描述符上有事件发生时返回,返回值是就绪的文件描述符的数量。返回0表示没有事件发生。
事件集方面
select
的参数类型fd_set 没有将文件描述符和事件绑定
,因此select需要提供3个这种类型的参数来分别传输可读、可写及异常等事件。这使得seleet 不能处理更多类型的事件
,另一方面由于内核对fd_set 集合的在线修改
,应用程序下次调用select前需要重置这3个fd_set集合
。poll
的参数类型pollfd则把文件描述符和事件都定义其中
,任何事件都被统一处理,从而使得编程接口简洁得多。并且内核每次修改的是pollfd结构体的revents成员,而events成员保持不变
,因此下次调用poll时应用程序无须重置pollfd类型的事件集参数。- 而
epoll
不同的是,它在内核中维护一个事件表
, 并提供了一个独立的系统调用epoll _ctl
来控制往其中添加、删除、修改事件。
这样,每次epoll_wait
调用都直接从该内核事件表中取得用户注册的事件,而无须反复从用户空间读人这些事件
。
最大支持文件描述符数
- poll和epoll wait分别用nfds和maxevents参数指定最多监听多少个文件描述符和事件。这两个数值都能达到系统允许打开的最大文件描述符数目,即65 535 。而select允许监听的最大文件描述符数量通常有限制,虽然用户可以修改这个限制,但这可能导致不可预期的后果。
实现原理
select 和poll采用的都是轮询的方式
,即每次调用都要扫描整个注册文件描述符集合,并将其中就绪的文件描述符返回给用户程序,因此它们检测就绪事件的算法的时间复杂度是0 (n)。epoll_wait则不同,它采用的是回调的方式
。内核检测到就绪的文件描述符时,将触发回调函数,回调函数就将该文件描述符上对应的事件插入内核就绪事件队列。内核最后在适当的时机将该就绪事件队列中的内容拷贝到用户空间。因此epoll_wait无须轮询整个文件描述符集合来检测哪些事件已经就绪,其算法时间复杂度是0 (1)。