操作系统------epoll和slect
发布日期:2021-11-04 22:04:37 浏览次数:25 分类:技术文章

本文共 3642 字,大约阅读时间需要 12 分钟。

 

epoll为处理大批量而作了改进的poll,是LinuxIO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。

 

问题的引出与联系区别

  问题的引出,当需要读两个以上的I/O的时候,如果使用阻塞式的I/O,那么可能长时间的阻塞在一个描述符上面,另外的描述符虽然有数据但是不能读出来,这样实时性不能满足要求,大概的解决方案有以下几种:

1.使用多进程或者多线程,但是这种方法会造成程序的复杂,而且对与进程与线程的创建维护也需要很多的开销。(Apache服务器是用的子进程的方式,优点可以隔离用户)

2.用一个进程,但是使用非阻塞的I/O读取数据,当一个I/O不可读的时候立刻返回,检查下一个是否可读,这种形式的循环为轮询(polling),这种方法比较浪费CPU时间,因为大多数时间是不可读,但是仍花费时间不断反复执行read系统调用。

3.异步I/Oasynchronous I/O),当一个描述符准备好的时候用一个信号告诉进程,但是由于信号个数有限,多个描述符时不适用。

4.一种较好的方式为I/O多路转接(I/O multiplexing)(貌似也翻译多路复用),先构造一张有关描述符的列表(epoll中为队列),然后调用一个函数,直到这些描述符中的一个准备好时才返回,返回时告诉进程哪些I/O就绪。selectepoll这两个机制都是多路I/O机制的解决方案,selectPOSIX标准中的,而epoll所特有的。

 

epoll与select区别

(1)首先selectposix支持的,而epoll特定的系统调用,因此,epoll的可移植性就没有select好,但是考虑到epollselect一般用作服务器的比较多,而服务器中大多又是linux,所以这个可移植性的影响应该不会很大。

(2)其次,select可以监听的文件描述符有限,最大值为1024,而epoll可以监听的文件描述符则是系统对整个进程限制的最大文件描述符。

(3)接下来谈epollselect的性能 比较了,这个一般情况下应该是epoll表现好一些,否则linux也不会去特定实现epoll函数了,那么epoll为什么比select更高效呢?原因有很多,第一点epoll通过每次有就绪事件时都将其插入到一个就绪队列中,使得epoll_wait的返回结果中只存储了已经就绪的事件,而select则返回了所有被监听的事件,事件是否就绪需要应用程序去检测,那么如果已被监听但未就绪的事件较多的话,对性能的影响就比较大了。第二点,每一次调用select获得就绪事件时都要将需要监听的事件重复传递给内核,而epoll对监听文件描述符的处理则和获得就绪事件的调用分开,这样获得就绪事件的调用epoll_wait就不需要重新传递需要监听的事件列表,这种重复的传递需要监听的事件也是性能低下的原因之一。第三点epoll的实现中使用了mmap调用使得内核空间和用户空间共享内存,从而避免了过多的内核和用户空间的切换引起的开销。

(4)工作模式然后就是epoll提供了两种工作模式,一种是水平触发模式,这种模式和select的触发方式是一样的,即只要文件描述符的缓冲区中有数据,就永远通知用户这个描述符是可读的,这种模式对blocknoblock的描述符都支持,编程的难度也比较小 ; 而另一种更高效且只有epoll提供的模式是边缘触发模式,只支持nonblock的文件描述符,他只有在文件描述符有新的监听事件发生的时候(例如有新的数据包到达)才会通知应用程序,在没有新的监听时间发生时,即使缓冲区有数据(即上一次没有读完,或者甚至没有读),epoll也不会继续通知应用程序,使用这种模式一般要求应用程序收到文件描述符读就绪通知时,要一直读数据直到收到EWOULDBLOCK/EAGAIN错误,使用边缘触发就必须即将缓冲区中的内容读完,否则有可能引起死等,尤其是当一个listen_fd需要监听到达连接的时候,如果多个连接同时到达,如果每次只是调用accept一次,就会导致多个连接在内核缓冲区中滞留,处理的办法是用while循环抱住accept,直到其出现EAGAIN。这种模式虽然容易出错,但是性能要比前面的模式更高效,因为只需要监听是否有事件发生,发生了就直接将描述符加入就绪队列即可。

 

 

应用场景:

   都知道epoll相对于select模型的优点,它的速度和并发量相对于select明显的优势,但是是不是epoll就可以完全代替select呢,在得出结论之前还是先要看看他们各自的实现原理。

1 select:在网络编程中统一的操作顺序是创建socket->绑定端口->监听->accept->write/read,当有客户端连接到来时,select会把该连接的文件描述符放到fd_set(一组文件描述符(fd)的集合),然后select会循环遍历它所监测的fd_set内的所有文件描述符,当select循环遍历完所有fd_set内指定的文件描述符对应的poll函数后,如果没有一个资源可用(即没有一个文件可供操作),则select让该进程睡眠,一直等到有资源可用为止,fd_set是一个类似于数组的,由于它每次都要遍历整个数组,所有她的效率会随着文件描述符的数量增多而明显的变慢,除此之外在每次遍历这些描述符之前,系统还需要把这些描述符集合从内核copy到用户空间,然后再copy回去,如果此时没有一个描述符有事件发生(例如:read和write)这些copy操作和便利操作都是无用功,可见slect随着连接数量的增多,效率大大降低。可见如果在高并发的场景下select并不适用,况且select默认的最大描述符为1024,如果想要更多还要做响应参数的配置。

2 epoll:说到epoll都夸赞它的效率和并发量,那么她好在哪里呢。首先调用epoll_create时内核帮我们在epoll文件系统里建了个file结点;除此之外在内核cache里建立红黑树[红黑书介绍](http://www.cnblogs.com/v-July-v/archive/2010/12/29/1983707.html)用于存储以后epoll_ctl传来的socket,当有新的socket连接来时,先遍历红黑中有没有这个socket存在,如果有就立即返回,没有就插入红黑,然后给内核中断处理程序注册一个回调函数,每当有事件发生时就通过回调函数把这些文件描述符放到事先准备好的用来存储就绪事件的链表中,调用epoll_wait时,会把准备就绪的socket拷贝到用户态内存,然后清空准备就绪list链表,最后检查这些socket。在LT模式下,如果这些socket上确实有未处理的事件时,该句柄会再次被放回到刚刚清空的准备就绪链表,保证所有的事件都得到正确的处理。如果到timeout时间后链表中没有数据也立刻返回。因此在并发需求量高的场景中我们即使要监控数百万计的句柄,大多数一次也只返回很少量的准备就绪句柄。由此可见epoll仅需要从内核态copy少量的句柄到用户态,这样就避免了select模型中的无效便利和用户和内核之间的copy操作。

说道这里,可以看到epoll的优势非常明显,几乎没有描述符数量的限制,并发支持完美,不会随着socket的增加而降低效率,也不用在内核空间和用户空间之间做无效的copy操作。 但是是不是所有的场景都适合epoll呢?看下面的例子。

  一个游戏服务器,tcp server负责接收客户端的连接,dbserver负责处理数据信息,一个webserver负责处理服务器的web请求,gameserver负责游戏的逻辑处理,所有这些服务都和另外一个gateserver相连,gateserver负责服务器间的通信和转发(进程间通信),只要游戏服务器在服务状态,这些连接几乎不会断开(异常情况可能会断开),并且这些连接数量一般不会很多。这种情况,select还是epoll呢?很明显是select,因为每时每刻这些连接的socket都有事件发生(比如:服务期间的心跳信息,还有大型网络游戏的同步信息(一般每秒在20-30次)),最重要的是,这种场景下,并发量也不会很大。如果此时用epoll,为此所建立的文件系统,红黑书和链表对于此来说就是杀鸡用牛刀,效率反而不高。当然这里的tcp server负责大量的客户端的连接,毫无疑问epoll是首选,它接受大量的客户端连接,收到客户端的消息之后把消息转发发给select网络模型的gateserver,gateserver再转发给gameserver进行逻辑处理,最后返回给客户端就over了。因此在并发量不高,并且连接长期存在的场景下,select还是要优于epoll的。

 

转载地址:https://blog.csdn.net/xiaochen87654321/article/details/77645896 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:eclipse启动报错:Error:Could not create the Java Virtual Machine Error:A fatal exception has occurred
下一篇:操作系统------多线程与多进程的区别和应用场景

发表评论

最新留言

关注你微信了!
[***.104.42.241]2024年04月19日 04时47分09秒