首页 > 代码库 > select、poll和epoll
select、poll和epoll
I/O复用:
在一个进程或者多个进程的需要多个I/O,不能阻塞在一个I/O上而停止不前,而是用到I/O复用。进程预先告知内核需要哪些I/O描述符,内核一旦发现指定的一个或多个I/O条件就绪,则通知进程进行相应操作,这就是I/O复用。
使用场合:
1、客户处理多个描述符(交互式输入和网络套接字)
2、TCP服务器既处理监听套接字,又处理连接套接字
3、一服务器既处理TCP又处理UDP
4、一服务器要处理多个服务或多个协议
select函数:
允许进程指示内核等待多个事件中的任何一个发生,且只在有一个或多个事件发生或经历一段指定的时间后才唤醒它。
其中:maxfdp1表示探测描述符中的最大值加一(因为描述符从0开始,其表示个数),后面三个参数依次表示读、写和异常描述符集,最后一个表示等待时间。timeout:NULL 永远等待;正数,等待一段时间后返回;0,不等待,检查描述符后立即返回。
#include <sys/select.h> #include <sys/time.h> int select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout); 返回:若有就绪描述符则为其数目,若超时为0,出错为-1 struct timeval { long tv_sec; //seconds long tv_usec; //microseconds }; void FD_ZERO(fd_set *fdset); //clear all bits in fdset void FD_SET(int fd, fd_set *fdset); //trun on the bit in fdset void FD_CLR(int fd, fd_set *fdset); //turn off the bit for fd in fdset void FD_ISSET(int fd, fd_set *fdset); //is the bit for fd on in fdset
poll函数
与select函数大致相同,不同在于select描述符最大个数FD_SETSIZE,poll可更大。且传递的结构不同,poll对每个描述符管理起来,select分别用三个数组管理起来。timeout:INFTIM永远等待,0立即返回,正数等待指定毫秒数返回。
#include <poll.h> int poll(struct pollfd *fdarray, unsigned long nfds, int timeout); //返回:若有就需描述符则为其数目,超时为0,出错为-1 struct pollfd { int fd; //descriptor to check short events; //events of interest on fd short revents; //events that pccurred on fd };
总结:参考http://www.open-open.com/lib/view/open1410403215664.html#articleHeader0
select缺点:1、单进程可监视文件描述符最大限制1024个,可更改。但select采用轮询方式扫描文件描述符,文件描述符数量越多性能越差(Linux内核中:#define _FD_SETSIZE 1024)
2、内核、用户空间内存拷贝,select需要赋值大量的句柄数据结构,产生巨大开销;
3、select返回整个句柄数组,应用程序需要遍历数组查找就绪文件描述符;
4、select水平触发,应用程序如果没有完成对一个已经就绪的文件描述符进行IO操作,那么之后每次select调用还是会将其桃枝进程
poll:相比select只是数据结构发生变化,用一个结构体数组来表示监视的文件描述符,每一个结构存储监视的文件描述符和其监视事件,并在其中返回监视结果。其监视文件数量没有限制。但是其他缺点和select一样。
例如:服务器需要支持100万并发连接,在_FD_SETSIZE为1024的情况下,我们至少需要创建1K歌进程才能实现100万的并发连接,除进程间上下文切换的时间开销,从内核、用户空间的内存拷贝,数组轮询等都是系统难以承受和实现的。因此基于select模型的服务器,要达到10万级别的并发访问控制,是很难完成的。
epoll
就上面例子中,select/poll都是服务器进程每次都把这100万个连接告诉操作系统(从用户赋值句柄数据结构到内核),让操作系统内核查询这些套接字上是否有事件发生,该过程资源消耗较大,因此select/poll一般只能处理几千的并发连接。
epoll的设计和实现与select完全不同。epoll通过Linux内核中申请一个建议的文件系统(B+树),吧原先的select/poll分为:
1、epoll_creat()简历一个epoll对象(epoll文件系统中为这个句柄对象分配资源)
2、epoll_ctl向epoll对象中添加监视的描述符;
3、epoll_wait收集发生的事件的连接;
epoll实现思路:
当某一进程调用epoll_creat方法,Linux内核会创建一个eventpoll结构体,这个结构体中有两个成员与epoll的使用方式密切相关
struct eventpoll{ .... /*红黑树的根节点,这颗树中存储着所有添加到epoll中的需要监控的事件*/ struct rb_root rbr; /*双链表中则存放着将要通过epoll_wait返回给用户的满足条件的事件*/ struct list_head rdlist; .... };
每一个epoll对象都有一个eventpoll结构体,用于存放通过epoll_ctl方法将epoll对象中添加进来的事件。这些事件都会挂载在红黑树中,如此重复添加的事件也可以通过红黑树而高效的识别出来。
所有添加到epoll中的事件都会与设备(网卡)驱动程序简历回调关系,当相应的事件发生时会调用这个回调方法。该回调方法在内核中叫ep_poll_callback,它将发生的事件添加到rdlist双链表中。
对于每一个事件都会建立epitem结构体:
struct epitem{ struct rb_node rbn;//红黑树节点 struct list_head rdllink;//双向链表节点 struct epoll_filefd ffd; //事件句柄信息 struct eventpoll *ep; //指向其所属的eventpoll对象 struct epoll_event event; //期待发生的事件类型 }
当调用epoll_wait检查是否有事件发生时,只需要检查eventpoll对象中的rdlist双链表中是否有epitem元素即可。如果rdlist不为空,则把发生的事件赋值到用户态,同时将时间数量返回。
select、poll和epoll