介绍epoll的文章大多都具体介绍了epoll模型和select/poll模型之间的比較,这里就不再具体列举,仅仅总结一下我对这两个模型的看法:
1.要监视的文件句柄很多,并且每次完毕操作的句柄很少,使用epoll模型效率比select/poll模型要高很多;2.取决于文静句柄的响应速度,在LAN环境中这几个模型的效率差点儿相同;WAN环境中epoll的效率最高;
1.Linux2.6内核epoll介绍
先介绍2本书《The Linux Networking Architecture--Design and Implementation of Network Protocols in the Linux Kernel》,以2.4内核解说Linux TCP/IP实现。相当不错.作为一个现实世界中的实现,非常多时候你必须作非常多权衡。这时候參考一个久经考验的系统更有实际意义。
举个样例,linux内核中sk_buff结构为了追求速度和安全,牺牲了部分内存,所以在发送TCP包的时候。不管应用层数据多大,sk_buff最小也有272的字节.事实上对于socket应用层程序来说,另外一本书《UNIX Network Programming Volume 1》意义更大一点.2003年的时候。这本书出了最新的第3版本号。只是主要还是修订第2版本号。当中第6章《I/O Multiplexing》是最重要的。Stevens给出了网络IO的基本模型。
在这里最重要的莫过于select模型和Asynchronous I/O模型.从理论上说,AIO似乎是最高效的,你的IO操作能够马上返回,然后等待os告诉你IO操作完毕。
可是一直以来,怎样实现就没有一个完美的方案。最著名的windows完毕port实现的AIO,实际上也是内部用线程池实现的罢了。最后的结果是IO有个线程池。你应用也须要一个线程池...... 非常多文档事实上已经指出了这带来的线程context-switch带来的代价。在linux 平台上,关于网络AIO一直是修改最多的地方。2.4的年代就有非常多AIO内核patch,最著名的应该算是SGI那个。可是一直到2.6内核公布。网络模块的AIO一直没有进入稳定内核版本号(大部分都是使用用户线程模拟方法。在使用了NPTL的linux上面事实上和windows的完毕port基本上差点儿相同了)。2.6内核所支持的AIO特指磁盘的AIO---支持io_submit(),io_getevents()以及对Direct IO的支持(就是绕过VFS系统buffer直接写硬盘,对于流server在内存平稳性上有相当帮助)。
所以,剩下的select模型基本上就是我们在linux上面的唯一选择,事实上,假设加上no-block socket的配置。能够完毕一个"伪"AIO的实现,仅仅只是推动力在于你而不是os而已。只是传统的select/poll函数有着一些无法忍受的缺点。所以改进一直是2.4-2.5开发版本号内核的任务,包含/dev/poll,realtime signal等等。终于。Davide Libenzi开发的epoll进入2.6内核成为正式的解决方式
2、epoll的长处
<1>支持一个进程打开大数目的socket描写叙述符(FD)
select 最不能忍受的是一个进程所打开的FD是有一定限制的,由FD_SETSIZE设置。默认值是2048。对于那些须要支持的上万连接数目的IMserver来说显然太少了。这时候你一是能够选择改动这个宏然后又一次编译内核。只是资料也同一时候指出这样会带来网络效率的下降,二是能够选择多进程的解决方式(传统的 Apache方案),只是尽管linux上面创建进程的代价比較小。但仍旧是不可忽视的,加上进程间数据同步远比不上线程间同步的高效,所以也不是一种完美的方案。只是 epoll则没有这个限制,它所支持的FD上限是最大能够打开文件的数目,这个数字一般远大于2048,举个样例,在1GB内存的机器上大约是10万左右。详细数目能够cat
/proc/sys/fs/file-max察看,一般来说这个数目和系统内存关系非常大。
<2>IO效率不随FD数目添加而线性下降
传统的select/poll还有一个致命弱点就是当你拥有一个非常大的socket集合。只是由于网络延时,任一时间仅仅有部分的socket是"活跃"的。可是select/poll每次调用都会线性扫描所有的集合,导致效率呈现线性下降。可是epoll不存在这个问题,它仅仅会对"活跃"的socket进行操作---这是由于在内核实现中epoll是依据每一个fd上面的callback函数实现的。那么,仅仅有"活跃"的socket才会主动的去调用 callback函数。其它idle状态socket则不会。在这点上,epoll实现了一个"伪"AIO。由于这时候推动力在os内核。在一些
benchmark中,假设全部的socket基本上都是活跃的---比方一个快速LAN环境。epoll并不比select/poll有什么效率。相反,假设过多使用epoll_ctl,效率相比还有略微的下降。可是一旦使用idle connections模拟WAN环境,epoll的效率就远在select/poll之上了。
3、epoll的用法
第一个參数是epoll_create()的返回值,第二个參数表示动作,用三个宏来表示:
在用完之后,记得用close()来关闭这个创建出来的epoll句柄。之后在你的网络主循环里面,每一帧的调用epoll_wait(int epfd, epoll_event events, int max events, int timeout)来查询全部的网络接口。看哪一个能够读,哪一个能够写了。主要的语法为:
nfds = epoll_wait(kdpfd, events, maxevents, -1);
当中kdpfd为用epoll_create创建之后的句柄,events是一个epoll_event*的指针,当epoll_wait这个函数操作成功之后,epoll_events里面将储存全部的读写事件。max_events是当前须要监听的全部socket句柄数。最后一个timeout是 epoll_wait的超时,为0的时候表示立即返回。为-1的时候表示一直等下去,直到有事件范围,为随意正整数的时候表示等这么长的时间,假设一直没有事件,则范围。一般假设网络主循环是单独的线程的话,能够用-1来等。这样能够保证一些效率。假设是和主逻辑在同一个线程的话。则能够用0来保证主循环的效率。
*/
4.Linux下EPOll编程实例
Epoll模型主要负责对大量并发用户的请求进行及时处理,完毕server与client的数据交互。其详细的实现过程例如以下:
(a) 使用epoll_create()函数创建文件描写叙述,设定将可管理的最大socket描写叙述符数目。
(b) 创建与epoll关联的接收线程。应用程序能够创建多个接收线程来处理epoll上的读通知事件,线程的数量依赖于程序的详细须要。
(c) 创建一个侦听socket描写叙述符ListenSock;将该描写叙述符设定为非堵塞模式,调用Listen()函数在套接字上侦听有无新的连接请求,在epoll_event结构中设置要处理的事件类型EPOLLIN,工作方式为 epoll_ET。以提高工作效率。同一时候使用epoll_ctl()注冊事件,最后启动网络监视线程。
(d) 网络监视线程启动循环。epoll_wait()等待epoll事件发生。
(e) 假设epoll事件表明有新的连接请求,则调用accept()函数,将用户socket描写叙述符加入到epoll_data联合体,同一时候设定该描写叙述符为非堵塞,并在epoll_event结构中设置要处理的事件类型为读和写,工作方式为epoll_ET.
(f) 假设epoll事件表明socket描写叙述符上有数据可读,则将该socket描写叙述符增加可读队列,通知接收线程读入数据,并将接收到的数据放入到接收数据的链表中,经逻辑处理后,将反馈的数据包放入到发送数据链表中,等待由发送线程发送。
原文:http://www.cnblogs.com/mfmdaoyou/p/6977927.html