select/poll/epoll

select,poll,epoll都是实现IO多路复用的一种系统调用,I/O多路复用就是通过一种机制,一个进程可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。

select它可以监听可读,可写,和异常这3种事情,而它是通过一个位图数组来告诉内核需要监听哪些文件描述符,但是它没有把文件描述符和要监听的事情所绑定,所以我们需要提供3个位图数组,来分别对应这3种事件,并且内核它是在这3个数组上面直接进行修改的,所以每次调用select前都要重新初始化描述符集。而内核是通过轮询的方式来检测就绪事件,并且程序索引就绪文件描述符的时间复杂也是O(n)。能够监视描述符的数量有限,最多1024个。

而poll把要监听的文件

epoll 通过在内核里面建了一个红黑树来存储我们要监听的文件描述符,并且还会再建立一个list链表,用于存储准备就绪的事件。epoll不是通过轮询来检测是否就绪,而是通过在相应的中断处理函数里面调用一个回调函数,把这个就绪的fd,加到就绪事件链表里,而当epoll_wait调用的时候就会把这个就绪事件链表写到用户空间。

而LT模式是,每次epoll_wait返回的时候,不删除那个就绪事件链表,(ET应该是删除的),等到再调用的时候,估计是把就绪事件表挨个检查一遍,所以LT模式下只要某个fd处于readable/writable状态,无论什么时候进行epoll_wait都会返回该fd;

epoll独有的两种模式LT和ET。

在LT模式下,只要一个描述符上的事件没有处理完,会在以后调用epoll_wait时次次返回这个文件描述符,而ET模式仅在第一次返回。

 select :是最初解决IO阻塞问题的方法。用结构体fd_set来告诉内核监听多个文件描述符,该结构体被称为描述符集。是用内置数组(数组中的每个元素的每一位用来描述有哪些文件描述符被监听,所以叫位图数组?)来描述监听哪些文件描述符。对结构体的操作封装在三个宏定义中。通过轮寻来查找是否有描述符要被处理,如果没有返回** 
存在的问题: 
1. 能够监视描述符的数量有限,1024?内置数组的形式使得select的最大文件数受限; 
2. 每次调用select前都要重新初始化描述符集,因为是直接在传进来的参数里修改的

3. 接口比较复杂,参数里有3个描述符集合,分别对应3种不同类型的事件。

4. 内核是通过轮询的方式来检测就绪事件的。(每次调用select,内核就把所有被监听的文件描述符挨个检测一遍,如果都没好,就阻塞当前进程,等等再问,只要检测到一个好了,就把所有没好的都给改成0,然后把结果返回)时间复杂度O(n)

5. 程序索引就绪文件描述符的时间复杂也是O(n), 挨个问FD_ISSET

6.每次调用需要经历两次拷贝,一次是把fd集,从用户空间拷贝到内核,然后内核再把结果拷贝回用户空间。

 

poll: 可以监视的fd变多了,因为这个数组是我们自己定义的,而select是内核定义的,并且现在数组中的元素是个结构体(int fd,short events, short revents)events是我们指定这个fd要监视它什么(变得可读,可写or...),而revents是内核帮我们写的,这个fd实际到底发生了什么。所以解决了上述问题2和3,不用每次调用重新初始化fd集了,并且接口变得更加简单了,and问题6也解决了,现在只需要第一次调用的时候拷贝到内核,假如fds不发生变化,那就不用重新拷贝,每次调用只用从内核拷贝到用户空间即可(这个拷贝省不了)。

问题4,5依旧存在

 

epoll:(仅linux支持)

3个重要的函数: epoll_create  epoll_ctl   epoll_wait  

epoll 通过在内核里面建了一个红黑树来存储我们要监听的文件描述符,并且还会再建立一个list链表,用于存储准备就绪的事件。epoll不是通过轮询来检测是否就绪,而是通过在相应的中断处理函数里面调用一个回调函数,把这个就绪的fd,加到就绪事件链表里,而当epoll_wait调用的时候就会把这个就绪事件链表写到用户空间。

通过epoll_create创建一个用于epoll监听的描述符,通过epoll_ctl添加/修改/删除事件,通过epoll_wait检查事件,epoll_wait的第二个参数用于存放结果。 epoll与select、poll不同,首先,其不用每次调用都向内核拷贝事件描述信息,在第一次调用后,事件信息就会与对应的epoll描述符关联起来。另外epoll不是通过轮询来检测是否就绪,而是通过在相应的中断处理函数里面调用一个回调函数,把这个就绪的fd,加到就绪事件链表里,而当epoll_wait调用的时候就会把这个就绪事件链表写到用户空间(当然,链表为空,阻塞进程,以后再问)。

epoll比poll重点好在

1.内核检测就绪事件的复杂度为O(1)

2. epoll_wait里面的events 参数仅仅用来写就绪的fd,所以索引就绪fd的时间复杂度也是O(1),而不用遍历全部被监听的fds了。

select和poll即使只有一个描述符就绪,也要遍历整个集合。如果集合中活跃的描述符很少,遍历过程的开销就会变得很大,而如果集合中大部分的描述符都是活跃的,遍历过程的开销又可以忽略。

epoll的实现中每次只遍历活跃的描述符(如果是水平触发,也会遍历先前活跃的描述符),在活跃描述符较少的情况下就会很有优势,在代码的分析过程中可以看到epoll的实现过于复杂并且其实现过程中需要同步处理(锁),如果大部分描述符都是活跃的,epoll的效率可能不如select或poll。

所以假如监听的fd很多,并且活跃的比较少,epoll很nb

but epoll只有linux支持,so,如果考虑可移植性,还得select poll

and!https://blog.csdn.net/lishenglong666/article/details/45536611    (有3个函数的源码,很屌,但是爷没空看)

这个大神说:如果事件需要循环处理select, poll 每一次的处理都要将全部的数据复制到内核,而epoll的实现中,内核将持久维护加入的描述符,减少了内核和用户复制数据的开销。

poll每次调用要不要复制到内核,铁子我也懵逼了哈!

 

另外epoll还有两种工作模式: LT和ET

LT模式是epoll默认的工作方式,相当于一个效率很高的poll模型;而ET是更高效的工作方式。

T 和 ET本质的区别是:

LT(水平模式)状态时,主线程正在epoll_wait等待事件时,请求到了,epoll_wait返回后没有把事情给处理完,那么下次epoll_wait时此请求还是会返回(立刻返回了);而ET模式状态(边缘模式)下,这次没处理完,下次epoll_wait时将不返回(所以我们应该每次一定要处理完,也就是用while循环,读到or写到 errno为止),所以很大程度降低了epoll_wait这个系统调用的返回次数。)实际实现是et模式下,fd只有状态发送变化的时候(比如unreadable变为readable或从unwritable变为writable时)才会加到就绪事情表里面去,而LT模式是,每次epoll_wait返回的时候,不删除那个就绪事件链表,(ET应该是删除的),等到再调用的时候,估计是把就绪事件表挨个检查一遍,所以LT模式下只要某个fd处于readable/writable状态,无论什么时候进行epoll_wait都会返回该fd;

另外带出一个点,ET模式下,所有文件描述符(监听描述符也ok的,一直accept不就ok了)必须得用fcntl改成非阻塞的(这个不记得了可以结合 read/write看),不然这个进程为被一直阻塞。

 

select和poll即使只有一个描述符就绪,也要遍历整个集合。如果集合中活跃的描述符很少,遍历过程的开销就会变得很大,而如果集合中大部分的描述符都是活跃的,遍历过程的开销又可以忽略。

epoll的实现中每次只遍历活跃的描述符(如果是水平触发,也会遍历先前活跃的描述符),在活跃描述符较少的情况下就会很有优势,在代码的分析过程中可以看到epoll的实现过于复杂并且其实现过程中需要同步处理(锁),如果大部分描述符都是活跃的,epoll的效率可能不如select或poll。

应用场景

很容易产生一种错觉认为只要用 epoll 就可以了,select 和 poll 都已经过时了,其实它们都有各自的使用场景。

1. select 应用场景

select 的 timeout 参数精度为 1ns,而 poll 和 epoll 为 1ms,因此 select 更加适用于实时性要求比较高的场景,比如核反应堆的控制。

select 可移植性更好,几乎被所有主流平台所支持。

2. poll 应用场景

poll 没有最大描述符数量的限制,如果平台支持并且对实时性要求不高,应该使用 poll 而不是 select。

3. epoll 应用场景

只需要运行在 Linux 平台上,有大量的描述符需要同时轮询,并且这些连接最好是长连接。

需要同时监控小于 1000 个描述符,就没有必要使用 epoll,因为这个应用场景下并不能体现 epoll 的优势。

需要监控的描述符状态变化多,而且都是非常短暂的,也没有必要使用 epoll。因为 epoll 中的所有描述符都存储在内核中,造成每次需要对描述符的状态改变都需要通过 epoll_ctl() 进行系统调用,频繁系统调用降低效率。并且 epoll 的描述符存储在内核,不容易调试。

                        

select:

1. 用select写并发服务器的例子

csapp p686 

https://blog.csdn.net/daaikuaichuan/article/details/83715044

 https://www.cnblogs.com/liudw-0215/p/9661583.html

2. select详细介绍

 linux高性能服务器p147

poll:

 用poll写并发服务器的例子

https://www.cnblogs.com/kellerfz/p/7809902.html

select和poll的例子unp也有,但我没看orz

 

epoll:

例子看 linux高性能服务器 p154 and还有3者的比较

大神3个函数的源码                    https://blog.csdn.net/lishenglong666/article/details/45536611

et里面accept,读,写的处理方式                https://www.cnblogs.com/chris-cp/p/5308776.html

3者的简单比较,可以看                       https://www.jianshu.com/p/b5bc204da984

简单比较2号             https://segmentfault.com/a/1190000003063859

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值