epoll介绍
作者:向前的步伐 / 发表: 2019年10月4日 22:40 / 更新: 2019年10月4日 22:57 / linux / 阅读量:827
如果有100W个客户端同时与一个服务器保持着连接,而且每一刻都有几百上千个连接是活跃的。这时,在select/poll时代,服务器进程每次都把这100W个连接告诉操作系统,复制句柄数据结构到内核态,让内核去查这些套接字是否有事件发生,轮询完之后,再复制回用户态,让服务器应用轮询处理已发生的网络事件。这一过程资源消耗很大,因此,select/poll一般只能处理几千的并发连接。
epoll的设计和实现与select完全不同,select上的缺点在epoll上不复存在。epoll通过在linux内核中申请一个简易的文件系统,把原先的select/poll调用分成了3个步骤:
1,调用epoll_create()建立一个epoll对象,在epoll文件系统中为这个句柄对象分配资源。
2,调用epoll_ctl()向epoll对象中添加这100W个连接的套接字。
3,调用epoll_wait()收集发生的事件的连接。
这样一来,要实现100W的连接数,只需要在进程启动时建立一个epoll对象,然后向epoll对象中添加或者删除连接。同时epoll_wait()的效率也非常高,因为调用epoll_wait()时,并没有向操作系统复制这100W个连接句柄数据,内核也不需要去遍历全部的连接。
epoll函数
int epoll_create(int size):创建一个epoll句柄。在linux2.6.8之后,size参数是被忽略的。需要注意的是,当创建好epoll句柄之后,他就会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能会导致fd耗尽。
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *events):epoll的事件注册函数,它不同于select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。
第一个参数是epoll_create()的返回值。
第二个参数表示动作,用三个宏来表示:EPOLL_CTL_ADD注册新的fd到epfd中;EPOLL_CTL_MOD修改已经注册的fd的监听事件;EPOLL_CTL_DEL从epfd中删除一个fd。
第三个参数是需要监听的fd。
第四个参数是告诉内核需要监听什么事,events可以是这几个宏的集合:EPOLLIN表示对应的文件描述符可读;EPOLLOUT表示对应的文件描述符可写;EPOLLPRI表示对应的文件描述符有紧急的数据可读;EPOLLERR表示对应的文件描述符发生错误;EPOLLHUP表示对应的文件描述符被挂断;EPOLLET将epoll设置为边缘触发模式;EPOLLONESHOT只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到epoll队列里。
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout):收集在epoll监控的事件中已经发生的事件。参数events是分配好的epoll_event结构数组,epoll将发生的事件赋值到events数组中。maxevents告诉内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size,参数timeout是超时时间(毫秒)。如果调用成功,返回对应IO上已经准备好的文件描述符的数目,如返回0表示已经超时。
工作原理
epoll只告知那些就绪的文件描述符,而且当调用epoll_wait()获得就绪的文件描述符时,返回的不是实际的描述符,而是就绪的文件描述符的数量的值,你只需要去epoll指定的一个数组中获取相应的文件描述符即可,这里使用了内存映射(mmap)技术,彻底省掉了这些文件描述符在系统调用时在用户态和内核态之间的复制开销。
另一个本质的改进在于epoll采用基于事件的就绪通知方式。在select/poll中,进程只有在调用一定的方法之后,内核才对所有监视的文件描述符进行扫描,而epoll事先通过epoll_ctl()来注册一个文件描述符,一旦基于某个文件描述符就绪时,内核采用类似callback的回调机制,激活这个文件描述符,当进程调用epoll_wait()时便得到通知。
epoll的2种工作方式——水平触发和边缘触发
水平触发(LT):默认的工作模式,即当epoll_wait()检测到某描述符事件就绪并通知应用程序时,应用程序可以不立即处理该事件;下次调用epoll_wait()时,会再次通知此事件。
边缘触发(ET):当epoll_wait()检测到某描述符事件就绪并通知应用程序时,应用程序必须立即处理该事件。如果不处理,下次再调用epoll_wait()时,不再通知此事件。
ET模式很大程度上减少了epoll事件的触发次数,因此效率比LT模式下高。