打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
select、poll、epoll的比较

 

 linux提供了selectpollepoll接口来实现IO复用,三者的原型如下所示,本文从参数、实现、性能等方面对三者进行对比。

 

intselect(int nfds, fd_set *readfds, fd_set *writefds, fd_set*exceptfds, struct timeval *timeout);

intpoll(struct pollfd *fds, nfds_t nfds, int timeout);

intepoll_wait(int epfd, struct epoll_event *events, int maxevents, inttimeout);

 

selectpollepoll_wait参数及实现对比

1.  select的第一个参数nfdsfdset集合中最大描述符值加1fdset是一个位数组,其大小限制为__FD_SETSIZE1024),位数组的每一位代表其对应的描述符是否需要被检查。

 

select的第二三四个参数表示需要关注读、写、错误事件的文件描述符位数组,这些参数既是输入参数也是输出参数,可能会被内核修改用于标示哪些描述符上发生了关注的事件。所以每次调用select前都需要重新初始化fdset

 

timeout参数为超时时间,该结构会被内核修改,其值为超时剩余的时间。

 

select对应于内核中的sys_select调用,sys_select首先将第二三四个参数指向的fd_set拷贝到内核,然后对每个被SET的描述符调用进行poll,并记录在临时结果中(fdset),如果有事件发生,select会将临时结果写到用户空间并返回;当轮询一遍后没有任何事件发生时,如果指定了超时时间,则select会睡眠到超时,睡眠结束后再进行一次轮询,并将临时结果写到用户空间,然后返回。

 

select返回后,需要逐一检查关注的描述符是否被SET(事件是否发生)。

 

2.  pollselect不同,通过一个pollfd数组向内核传递需要关注的事件,故没有描述符个数的限制,pollfd中的events字段和revents分别用于标示关注的事件和发生的事件,故pollfd数组只需要被初始化一次。

 

poll的实现机制与select类似,其对应内核中的sys_poll,只不过poll向内核传递pollfd数组,然后对pollfd中的每个描述符进行poll,相比处理fdset来说,poll效率更高。

 

poll返回后,需要对pollfd中的每个元素检查其revents值,来得指事件是否发生。

 

3.  epoll通过epoll_create创建一个用于epoll轮询的描述符,通过epoll_ctl添加/修改/删除事件,通过epoll_wait检查事件,epoll_wait的第二个参数用于存放结果。

 

epollselectpoll不同,首先,其不用每次调用都向内核拷贝事件描述信息,在第一次调用后,事件信息就会与对应的epoll描述符关联起来。另外epoll不是通过轮询,而是通过在等待的描述符上注册回调函数,当事件发生时,回调函数负责把发生的事件存储在就绪事件链表中,最后写到用户空间。

 

epoll返回后,该参数指向的缓冲区中即为发生的事件,对缓冲区中每个元素进行处理即可,而不需要像pollselect那样进行轮询检查。

 

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

1.select
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set*exceptfds, struct timeval *timeout);
void FD_CLR(int fd, fd_set *set);
int FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);

select的调用一般要注意几点:
①readfds等是值结果参数,会被函数修改,所以一般会另外定义一个allread_fdset,保持全部要监听读的句柄,将它的拷贝传递给select函数,返回可读的句柄集合,类型fdset支持赋值运算符=;
②要注意计算nfds,当新增监听句柄时比较容易修改,当减少监听句柄时较麻烦些,如果要精确修改需要遍历或者采用最大堆等数据结构维护这个句柄集,以方便的找到第二大的句柄,或者干脆在减少监听句柄时不管nfds;
③ timeout如果为NULL表示阻塞等,如果timeout指向的时间为0,表示非阻塞,否则表示select的超时时间;
④select返回-1表示错误,返回0表示超时时间到没有监听到的事件发生,返回正数表示监听到的所有事件数(包括可读,可写,异常),通常在处理事件时会利用这个返回值来提高效率,避免不必要的事件触发检查。(比如总共只有一个事件,已经在可读集合中处理了一个事件,则可写和异常就没必要再去遍历句柄集判断是否发生事件了);
⑤ Linux的实现中select返回时会将timeout修改为剩余时间,所以重复使用timeout需要注意。

select的缺点在于:
①由于描述符集合set的限制,每个set最多只能监听FD_SETSIZE(在Linux上是1024)个句柄(不同机器可能不一样);
② 返回的可读集合是个fdset类型,需要对所有的监听读句柄一一进行FD_ISSET的测试来判断是否可读;
③nfds的存在就是为了解决select的效率问题(select遍历nfds个文件描述符,判断每个描述符是否是自己关心的,对关心的描述符判断是否发生事件)。但是解决不彻底,比如如果只监听0和1000两个句柄,select需要遍历1001个句柄来检查事件。

2. poll
poll的功能和select类似,返回值意义一致。只不过调用方式不同,效率较高,解决了select的缺点①,一定程度上解决③。
int poll(struct pollfd fds[], nfds_t nfds, int timeout);
poll的调用注意以下几点:
① nfds表示监听的fds的长度,如果fds[i].fd <0,则poll忽略这样的pollfd;
② timeout是以ms为单位的超时时间;一般需要自定义INFTIM=-1,表示永远等下去;0表示立即返回不等待;
③ poll在处理流设备时能提供额外的信息;
④ 使用poll不需要显式地监听异常事件,使用poll如果pollfd异常,则内核会设置revents的POLLERR位;
⑤ POLLIN等价于POLLRDNORM | POLLRDBAND; POLLOUT 等价于POLLWRNORM;POLLIN |POLLRDPRI 等价于select的读;POLLOUT | POLLWRBAND 等价于select的写;

和select相比,poll的优点是:
① 不再局限于FD_SETSIZE个监听描述符,只要能打开的描述符,都可以监听;
② timeout参数不会被函数修改,分辨率较低些(但实际上没有影响);
③ 监听描述符集合不再是值结果参数,而是event表示监听事件,revents表示触发的事件;
④poll的效率比select稍高(poll只遍历输入的监听数组中的描述符,如果数组中的fd<0,则poll忽略fd,当监听的描述符离散时效率稍高于select,比如监听0和1000两个句柄,则poll只需要遍历两个描述符,而select需要遍历1001个描述符;当监听描述符连续时,poll和select效率相当,底层实现也是一致的)。

poll和select共同的问题是性能较差:遍历所有的文件描述符,当监听描述符个数增加时,监听效率降低,并且select和poll每次都要在用户态和内核态拷贝监听的描述符参数。

3. epoll
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event*event);
         typedef union epoll_data {
            void *ptr;
            int fd;
            __uint32_t u32;
            __uint64_t u64;
         } epoll_data_t;

         struct epoll_event {
            __uint32_tevents;     
            epoll_data_tdata;     
         };

int epoll_wait(int epfd, struct epoll_event * events, intmaxevents, int timeout);

epoll解决了select和poll的几个性能上的缺陷:①不限制监听的描述符个数(poll也是),只受进程打开描述符总数的限制;②监听性能不随着监听描述符数的增加而增加,是O(1)的,不再是轮询描述符来探测事件,而是由描述符主动上报事件;③使用共享内存的方式,不在用户和内核之间反复传递监听的描述符信息;④返回参数中就是触发事件的列表,不用再遍历输入事件表查询各个事件是否被触发。
epoll显著提高性能的前提是:监听大量描述符,并且每次触发事件的描述符文件非常少。
epoll的另外区别是:①epoll创建了描述符,记得close;②支持水平触发和边沿触发。

epoll使用注意事项:
① int epoll_create(int size);   //创建epoll文件描述符
参数size并不是限制了epoll所能监听的描述符最大个数,只是对内核初始分配内部数据结构的一个建议。返回是epoll描述符。-1表示创建失败。

② int epoll_ctl(int epfd, int op, int fd, struct epoll_event*event);   //epoll文件描述符的控制接口
epoll_ctl控制对指定描述符fd执行op操作,event是与fd关联的监听事件。
op操作有三种:添加EPOLL_CTL_ADD,删除EPOLL_CTL_DEL,修改EPOLL_CTL_MOD。分别添加、删除和修改对fd的监听事件。
重复添加fd会怎样(event相同或不相同):添加失败(errno:17, File exists)
删除和修改不存在的fd会怎样:删除或修改失败(errno:9,Bad file descriptor)
event是与监听的fd相关联的事件信息,event->events描述了要监听的事件类型,有以下类型:
EPOLLIN 可读
EPOLLOUT 可写
EPOLLRDHUP 套接口对端close或shutdown写,在ET模式下比较有用
EPOLLPRI 紧急数据可读
EPOLLERR 异常条件
EPOLLHUP 挂起,EPOLLERR和EPOLLHUP始终由epoll_wait监听,不需要用户设置
EPOLLET 边沿触发模式,在描述符状态跳变时才上报监听事件。(监听默认都是LT模式)
EPOLLONESHOT只一次有效,设置oneshot标记,描述符在触发一次事件之后自动失效(fd还被监听),不会再上报任何事件,直到使用EPOLL_CTL_MOD重新激活,设置新的监听事件为止(可不可以和之前的事件一样?)。
event->data是个共用体,可以存放和fd绑定的描述符信息,比如就存放描述符本身fd,或者一个结构体信息,包括fd,ip,port等等。
在epoll_wait返回时,只会返回一个event列表,需要从列表元素中获取fd等信息。
返回0表示控制成功,返回-1表示失败。

③ int epoll_wait(int epfd, struct epoll_event * events, intmaxevents, int timeout);
//等待epfd上的io事件,最多返回maxevents个事件
timeo

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
Linux epoll
Linux 2.6内核中提高网络I/O性能的新方法
聊一聊 Python 的 socket,以及 select、poll、epoll 又是怎么一回事?
select,poll,epoll总结
【原创】技术系列之 网络模型(三)多路复用模型
彻底理解 IO多路复用
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服