select、poll、epoll三种I/O复用函数的对比
select
select函数原型为:
int select( int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, struct timeval* timeout);
- nfds参数指定被监听的文件描述符总数,通常被设置为select监听的所有文件描述符的最大值加1。
- readfds、writefds、exceptfds参数分别指向可读、可写、异常等事件对应的描述符集合。应用程序调用select函数时通过这三个参数传入自己感兴趣的文件描述符,select调用返回时,内核将修改他们来通知应用程序哪些文件描述符已就绪。
fd_set实质上是一个整型数组,该数组每一个元素的每一位(bit)标记一个文件描述符,通常能容纳的文件描述符数量为1024,但用户可以自己修改。 - timeout参数设置select的超时时间,timeval结构定义如下:
struct timeval{
long tv_sec; /* 秒数 */
long tv_usec; /* 微秒数 */
};
如果tv_sec和tv_usec都为0,则select将立即返回,若timeout为NULL,则select将阻塞,直到某个文件描述符就绪。
select函数执行成功时返回就绪文件描述符的总数,超时返回0,失败返回-1并设置errno。
poll
poll调用和select类似,也是在指定时间内轮询一定数量的文件描述符,以测试其中是否有就绪者。poll函数原型如下:
int poll( struct pollfd* fds, nfds_t nfds, int timeout);
//pollfd结构如下:
struct pollfd{
int fd; //文件描述符
short events; //注册的事件
short revents; //实际发生的事件,由内核填充
};
- fd成员指定文件描述符;events成员告诉poll监听fd上的什么事件,他是一系列事件的按位或;revents成员由内核修改,以通知应用程序fd上实际发生了哪些事件。
常见的poll事件类型:
事件 | 描述 |
---|---|
POLLIN | 数据可读 |
POLLOUT | 数据可写 |
POLLERR | 错误(不能用作输入,只能用作输出) |
- nfds指定被监听事件集合fds的大小
- timeout指定poll的超时时间,单位是毫秒。
- poll返回值含义与select相同。
通常而言poll没有监听文件数量的限制。
epoll
epoll是Linux特有的I/O复用函数。他使用一组函数来完成功能。epoll把用户关心的文件描述符上的事件放在内核的一个事件表中,因此无需像select和poll一样每次调用都要重复传入文件描述符集或事件集,但epoll需要一个额外的文件描述符来唯一标识内核中的这个事件表。
epoll的三个函数如下:
epoll_create
int epoll_create( int size );
/*
size给内核一个提示,告诉它事件表需要多大。
该函数返回的文件描述符将作为其他epoll函数的第一个参数
*/
epoll_ctl
int epoll_ctl( int epfd, int op, int fd, struct epoll_event *event);
op参数指定操作类型,有以下三种操作类型:
EPOLL_CTL_ADD; //往事件表中注册fd上的事件
EPOLL_CTL_MOD; //修改fd上的注册事件
EPOLL_CTL_DEL; //删除fd上的注册事件
event参数用来指定事件。
epoll_ctl运行成功时返回0,失败返回-1并设置errno。
epoll_wait
int epoll_wait( int epfd, struct epoll_event* events, int maxevents, int timeout);
timeout参数同poll,maxevents参数指定最多监听的事件个数。
epoll_wait如果检测到事件,就将所有就绪的事件从内核事件表复制到第二个参数events指向的数组里,这个数组只用于输出epoll_wait检测到的就绪事件,而不像select和poll的参数那样既用于传入用户注册的事件又用于输出内核检测到的事件,这就极大的提升了应用程序索引就绪文件描述符的效率。
下面两段代码为poll和epoll使用上的区别
//索引poll返回的就绪文件描述符
int ret = poll( fds, MAX_EVENT_NUMBER, -1);
//遍历所有已注册文件描述符并找到其中的就绪者
for(int i = 0;i < MAX_EVENT_NUMBER;i++)
{
if( fds[i].revent & POLLIN) //判断第i个文件描述符的写文件事件是否就绪
{
int sockfd = fds[i].fd; //处理socket
}
}
epoll用法实例
int ret = epoll_wait( epollfd, events, MAX_EVENT_NUMBER, -1);
//只需要遍历就绪的ret个文件描述符
for(int i = 0;i < ret;i++)
{
int sockfd = events[i].data.fd;
}
从上面两段代码可以看出epoll的效率是高于poll的
select、poll和epoll的对比
系统调用 | select | poll | epoll |
---|---|---|---|
事件集合 | 用户通过3个参数分别传入感兴趣的可读、可写及异常等事件,内核通过对这些参数的在线修改来反馈其中的就绪事件。这使得用户每次调用select函数都要重置这三个参数 | 统一处理所有事件类型,因此只需一个事件集参数。用户通过pollfd.events传入感兴趣的事件,内核通过修改pollfd.revent反馈其中的就绪事件 | 内核通过一个事件表直接管理用户感兴趣的所有事件。因此每次调用epoll_wait时,无须反复传入用户感兴趣的事件。epoll_wait系统调用的参数events仅用来反馈就绪的事件 |
最大支持的文件描述符数 | 一般为1024 | 65535 | 65535 |
应用程序索引就绪文件描述符的效率 | O(n) | O(n) | O(1) |
内核实现和工作效率 | 采用轮询方式来检测就绪事件,算法复杂度为O(n) | 采用轮询方式来检测就绪事件,算法复杂度为O(n) | 采用回调方式来检测就绪事件,算法复杂度为O(1) |