select poll epoll三者之间的比较
一、概述
说到Linux下的IO复用,系统提供了三个系统调用,分别是select poll epoll。那么这三者之间有什么不同呢,什么时候使用三个之间的其中一个呢?
下面,我将从系统调用原型来分析其中的不同。
二、系统接口原型
1. select
#include <sys/select.h> int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout); int pselect(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, const struct timespec *timeout,
const sigset_t *sigmask);
2. poll
#include <poll.h> int poll(struct pollfd *fds, nfds_t nfds, int timeout);
int ppoll(struct pollfd *fds, nfds_t nfds,
const struct timespec *timeout_ts, const sigset_t *sigmask);
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};
3. epoll
#include <sys/epoll.h> int epoll_wait(int epfd, struct epoll_event *events,
int maxevents, int timeout);
int epoll_pwait(int epfd, struct epoll_event *events,
int maxevents, int timeout,
const sigset_t *sigmask);
三、参数对比
1. select
- select的第一个参数nfds为fdset集合中最大描述符值加1,fdset是一个位数组,其大小限制为__FD_SETSIZE(1024),位数组的每一位代表其对应的描述符是否需要被检查;
- select的第二三四个参数表示需要关注读、写、错误事件的文件描述符位数组,这些参数既是输入参数也是输出参数,可能会被内核修改用于标示哪些描述符上发生了关注的事件。所以每次调用select前都需要重新初始化fdset。
- timeout参数为超时时间,该结构会被内核修改,其值为超时剩余的时间。
- select对应于内核中的sys_select调用,sys_select首先将第二三四个参数指向的fd_set拷贝到内核,然后对每个被SET的描述符调用进行poll,并记录在临时结果中(fdset),如果有事件发生,select会将临时结果写到用户空间并返回;当轮询一遍后没有任何事件发生时,如果指定了超时时间,则select会睡眠到超时,睡眠结束后再进行一次轮询,并将临时结果写到用户空间,然后返回。
- select返回后,需要逐一检查关注的描述符是否被SET(事件是否发生)。
2. poll
- poll与select不同,通过一个pollfd数组向内核传递需要关注的事件,故没有描述符个数的限制,pollfd中的events字段和revents分别用于标示关注的事件和发生的事件,故pollfd数组只需要被初始化一次。
- poll的实现机制与select类似,其对应内核中的sys_poll,只不过poll向内核传递pollfd数组,然后对pollfd中的每个描述符进行poll,相比处理fdset来说,poll效率更高。
- poll返回后,需要对pollfd中的每个元素检查其revents值,来得指事件是否发生。
poll事件类型
事件 | 描述 | 是否可作为输入 | 是否可作为输出 |
POLLIN | 数据(包括普通数据和优先数据) | 是 | 是 |
POLLRDNORM | 普通数据可读 | 是 | 是 |
POLLRDBAND | 优先级带数据可读(Linux不支持) | 是 | 是 |
POLLPRI | 高优先级数据可读,比如TCP带外数据 | 是 | 是 |
POLLOUT | 数据(包括普通数据和优先数据)可写 | 是 | 是 |
POLLWRNORM | 普通数据可写 | 是 | 是 |
POLLWRBAND | 优先级带数据可写 | 是 | 是 |
POLLRDHUP | TCP连接被对方关闭,或者对方关闭了写操作。它由GNU引入 | 是 | 是 |
POLLERR | 错误 | 否 | 是 |
POLLHUP | 挂起。比如管道的写端被关闭后,读端描述符上将收到POLLHUP事件 | 否 | 是 |
POLLNVAL | 文件描述符没有打开 | 否 | 是 |
3. epoll
- epoll是Linux特有的I/O复用函数。它在实现上与select、poll有很大的差异。首先,epoll使用一组函数来完成任务,而不是单个函数;
- 其次,epoll把用户关心的文件描述符上的事件放在内核里的一个事件表中,从而无需像select和poll那样每次调用都要重复传入文件的事件放在内核里的一个事件表中。但epoll需要使用一个额外的文件描述符,来唯一标识内核中的这个事件表;
- epoll通过epoll_create创建一个用于epoll轮询的描述符,通过epoll_ctl添加/修改/删除事件,通过epoll_wait检查事件,epoll_wait的第二个参数用于存放结果。
- epoll与select、poll不同,首先,其不用每次调用都向内核拷贝事件描述信息,在第一次调用后,事件信息就会与对应的epoll描述符关联起来。另外epoll不是通过轮询,而是通过在等待的描述符上注册回调函数,当事件发生时,回调函数负责把发生的事件存储在就绪事件链表中,最后写到用户空间。
- epoll返回后,该参数指向的缓冲区中即为发生的事件,对缓冲区中每个元素进行处理即可,而不需要像poll、select那样进行轮询检查。
四、性能对比
select、poll的内部实现机制相似,性能差别主要在于向内核传递参数以及对fdset的位操作上,另外,select存在描述符数的硬限制,不能处理很大的描述符集合。
这里主要考察poll与epoll在不同大小描述符集合的情况下性能的差异。
测试程序会统计在不同的文件描述符集合的情况下,1s 内poll与epoll调用的次数。
统计结果如下,从结果可以看出,对poll而言,每秒钟内的系统调用数目虽集合增大而很快降低,而epoll基本保持不变,具有很好的扩展性。
描述符集合大小 |
poll |
epoll |
1 |
331598 |
258604 |
10 |
330648 |
297033 |
100 |
91199 |
288784 |
1000 |
27411 |
296357 |
5000 |
5943 |
288671 |
10000 |
2893 |
292397 |
25000 |
1041 |
285905 |
50000 |
536 |
293033 |
100000 |
224 |
285825 |
五、连接数
我本人也曾经在项目中用过select和epoll,对于select,感触最深的是linux下select最大数目限制(windows 下似乎没有限制),每个进程的select最多能处理FD_SETSIZE个FD(文件句柄),如果要处理超过1024个句柄,只能采用多进程了。
常见的使用select的多进程模型是这样的:
一个进程专门accept,成功后将fd通过UNIX socket传递给子进程处理,父进程可以根据子进程负载分派。
曾经用过1个父进程 + 4个子进程 承载了超过4000个的负载。
这种模型在我们当时的业务运行的非常好。
epoll在连接数方面没有限制,当然可能需要用户调用API重现设置进程的资源限制。
六、相同点
- 都能同时监听多个文件描述符;
- 它们将等待由timeout参数指定的超时时间,直到一个或者多个文件描述符上有事件发生时返回,返回值是就绪的文件描述符的数量;
七、不同点
对于select:
1. 只能通过三个结构体参数处理三种事件,分别是:可读、可写和异常事件,而不能处理更多的事件;
2. 这三个参数既是输入参数,也是输出参数,因此,在每次调用select之前,都得对fd_set进行重置;
对于poll:
1. 将文件描述符和事件关联在一起,任何事件都被统一处理,从而使得编程接口简洁不少;
2. 内核改变的变量是revents,而不是events,因此,调用之前不需要再重置;
由于每次select和poll调用都返回整个用户注册的事件集合(其中包括就绪的和未就绪的),所以应用程序索引就绪文件描述符的时间复杂度为O(n)。
而epoll采用与select和poll完全不同的方式来管理用户注册的事件。
八、poll和epoll在使用上的差别
/*poll example*/
/*如何索引poll返回的就绪文件描述符*/
int ret = poll(fds, MAX_EVENT_NUMBER, -);
/*必须遍历所有已注册文件描述符并找到其中的就绪者(当然,可以利用ret来稍作优化)*/
for(int i = ; i < MAX_EVENT_NUMBER; ++i)
{
if(fds[i].revents & POLLIN)
{
int sockfd = fds[i].fd;
//deal with sockfd.
}
} /*epoll example*/
int epfd = epoll_create(MAXSIZE); struct epoll_event ev,events[];
//设置与要处理的事件相关的文件描述符
ev.data.fd=listenfd;
//设置要处理的事件类型
ev.events=EPOLLIN|EPOLLET;
//注册epoll事件
epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev); int nfds = epoll_wait(epfd,events,,-);
//处理所发生的所有事件
for(int i = ; i< nfds; ++i)
{
//new accept.
if(events[i].data.fd == listenfd)
{
printf("listen=%d\n",events[i].data.fd);
connfd = accept(listenfd,(sockaddr *)(&clientaddr), &clilen);
if(connfd<)
{
perror("connfd<0");
exit();
}
setnonblocking(connfd);
char *str = inet_ntoa(clientaddr.sin_addr);
std::cout<<"connec_ from >>"<<str<<" "<<connfd<<std::endl; //设置用于读操作的文件描述符
ev.data.fd = connfd; //设置用于注测的读操作事件
//ev.data.ptr = NULL;
ev.events = EPOLLIN|EPOLLET; //注册ev
epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev);
ev.data.fd = listenfd; //设置要处理的事件类型
ev.events=EPOLLIN|EPOLLET; //注册epoll事件
epoll_ctl(epfd,EPOLL_CTL_MOD,listenfd,&ev);
continue;
}
else if(events[i].events & EPOLLIN)
{
num1++;
//fprintf(stderr,"reading! %d\n",num1);
if( (sockfd = events[i].data.fd) <= )
{
num1--;
continue;
} new_task = NULL;
while(new_task == NULL)
new_task = new task(); new_task->fd = sockfd;
new_task->next=NULL; //fprintf(stderr,"sockfd %d",sockfd);
//添加新的读任务
pthread_mutex_lock(&mutex);
if(readhead == NULL)
{
readhead = new_task;
readtail = new_task;
}
else
{
readtail->next=new_task;
readtail=new_task;
}
//唤醒所有等待cond1条件的线程
pthread_cond_broadcast(&cond1);
pthread_mutex_unlock(&mutex);
continue;
}
else if(events.events & EPOLLOUT)
{
//fprintf(stderr,"EPOLLOUT");
num++;
rdata=(struct user_data *)events[i].data.ptr;
sockfd =rdata->fd;
if(old == sockfd)
{
fprintf(stderr,"repreted sockfd=%d\n",sockfd);
//exit(1);
}
old=sockfd;
//fprintf(stderr,"write %d\n",num);
int size=write(sockfd, rdata->line, rdata->n_size);
//fprintf(stderr,"write=%d delete rdata\n",size);
fprintf(stderr,"addr=%x fdwrite=%d size=%d\n",rdata,rdata->fd,size); if(rdata!=NULL)//主要问题导致delete重复相同对象 events返回对象相同
{
delete rdata;
rdata=NULL;
} //设置用于读操作的文件描述符
//fprintf(stderr,"after delete rdata\n");
ev.data.fd=sockfd; //设置用于注测的读操作事件
ev.events=EPOLLIN|EPOLLET; //修改sockfd上要处理的事件为EPOLIN
res = epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev); while(res==-)
{
//fprintf(stderr,"out error");
exit();
}
//fprintf(stderr,"out EPOLLOUT\n");
continue;
}
else if(events.events&(EPOLLHUP|EPOLLERR))
{
//fprintf(stderr,"EPPOLLERR\n");
int fd=events.data.fd;
if(fd>)
{
fd=((struct user_data*)(events.data.ptr))->fd;
}
//设置用于注测的读操作事件
ev.data.fd=fd;
ev.events=EPOLLIN|EPOLLET|EPOLLOUT; //修改sockfd上要处理的事件为EPOLIN
epoll_ctl(epfd,EPOLL_CTL_DEL,fd,&ev);
}
}
select poll epoll三者之间的比较的更多相关文章
- Linux I/O复用中select poll epoll模型的介绍及其优缺点的比較
关于I/O多路复用: I/O多路复用(又被称为"事件驱动"),首先要理解的是.操作系统为你提供了一个功能.当你的某个socket可读或者可写的时候.它能够给你一个通知.这样当配合非 ...
- Select\Poll\Epoll异步IO与事件驱动
事件驱动与异步IO 事件驱动编程是一种编程规范,这里程序的执行流由外部事件来规定.它的特点是包含一个事件循环,但外部事件发生时使用回调机制来触发响应的处理.另外两种常见的编程规范是(单线程)同步以及多 ...
- Python之路-python(Queue队列、进程、Gevent协程、Select\Poll\Epoll异步IO与事件驱动)
一.进程: 1.语法 2.进程间通讯 3.进程池 二.Gevent协程 三.Select\Poll\Epoll异步IO与事件驱动 一.进程: 1.语法 简单的启动线程语法 def run(name): ...
- 多进程、协程、事件驱动及select poll epoll
目录 -多线程使用场景 -多进程 --简单的一个多进程例子 --进程间数据的交互实现方法 ---通过Queues和Pipe可以实现进程间数据的传递,但是不能实现数据的共享 ---Queues ---P ...
- Python自动化 【第十篇】:Python进阶-多进程/协程/事件驱动与Select\Poll\Epoll异步IO
本节内容: 多进程 协程 事件驱动与Select\Poll\Epoll异步IO 1. 多进程 启动多个进程 进程中启进程 父进程与子进程 进程间通信 不同进程间内存是不共享的,要想实现两个进程间 ...
- select,poll,epoll的归纳总结区分
Select.Poll与Epoll比较 以下资料都是来自网上搜集整理.引用源详见文章末尾. 1 Select.Poll与Epoll简介 Select select本质上是通过设置或者检查存放fd标志位 ...
- 转--select/poll/epoll到底是什么一回事
面试题:说说select/poll/epoll的区别. 这是面试后台开发时的高频面试题,属于网络编程和IO那一块的知识.Android里面的Handler消息处理机制的底层实现就用到了epoll. 为 ...
- Python异步非阻塞IO多路复用Select/Poll/Epoll使用,线程,进程,协程
1.使用select模拟socketserver伪并发处理客户端请求,代码如下: import socket import select sk = socket.socket() sk.bind((' ...
- 笔记-select,poll,epoll
笔记-select,poll,epoll 1. I/O多路复用 I/O多路复用是指:通过一种机制或一个进程,可以监视多个文件描述符,一旦描述符就绪(写或读),能够通知程序进行相应的读写操作. ...
随机推荐
- TabControl控件中TabPage的显示和隐藏
TabPage里面含有方法Hide和Show,但没有任何作用,实际隐藏和显示需要使用如下2个方法 方法一:此方法比较简单 TabPageServo.Parent = Nothing //隐藏 Ta ...
- 我的css reset
@charset "utf-8"; /*reset*/ body,h1,h2,h3,h4,h5,h6,hr,p,blockquote,dl,dt,dd,ul,ol,li,pre,f ...
- js时间戳与日期格式之间的互转
1. 将时间戳转换成日期格式 // 简单的一句代码 var date = new Date(时间戳); //获取一个时间对象 注意:如果是uinx时间戳记得乘于1000.比如php函数time()获得 ...
- hadoop 2.2.0 eclipse 插件编译 及相关eclipse配置图解
https://github.com/winghc/hadoop2x-eclipse-plugin 官网 http://kangfoo.github.io/article/2013/12/build- ...
- sort 命令
sort sort -t': ' -k 2n -t 可以自定义分隔符 -k 可以自定义分割后取第几个字符串作为排序值 2n表示第二个值,并作为数字来排序
- 开启SQL Server 2012的远程连接
有个远程的SQL Server服务器需要连接,因为SQL server默认是把远程连接关闭的. 以下有文档,照着做了,但是还是连不上, http://www.2cto.com/database/201 ...
- 用 SQL 脚本读取Excel 中的sheet数量及名称
-- Get table (worksheet) or column (field) listings from an excel spreadsheet -- 设置变量 declare @linke ...
- 【HDOJ】1401 Solitaire
双向BFS+状态压缩. /* 1401 */ #include <iostream> #include <queue> #include <map> #includ ...
- Unity 弹出界面时屏蔽对3D场景的点击
注:这里的UI制作用的是NGUI插件 如题,在游戏中经常会遇到这种情况,场景中点击相关物体或者按钮弹出对应的2D界面,这时候除了2D界面上的可点击按钮等,应该屏蔽掉对3D场景的点击或者拖动事件. 在这 ...
- Pie(二分)
ime Limit: 1000MS Memory Limit: 65536K Total Submissions: 8930 Accepted: 3235 Special Judge De ...