一、先举个例子:
你在网易严选上买了一件1999元超暖超赞的秋冬加厚羽绒服,很迫切希望早点收到这件宝贝的快递,但又不清楚是发了哪一家快递公司,于是你可能:
乍一看,方式1/2很无脑,方式3最好。但其实孰优孰劣,还得仁者见仁。方式1在日常生活中确实很傻,但它其实就是网络编程中的阻塞模式,在真实代码作品中并不少见。下面我们就来浅谈一番网络编程中那些常见的事件响应机制。
二、方式1/2/3的对比:
先翻译一下这个例子:
例子场景 |
对应的计算机场景 |
常见的技术 |
快递包裹 |
网络I/O事件 |
Read,Write,Exception |
快递员 |
网络事件的请求方 |
Socket Client端 |
你 |
网络事件的接收方 |
Socket Server端 |
方式1:一直守着,且不能干其它事 |
阻塞式I/O |
Socket默认 |
方式2:询问每一家快递公司 |
遍历所有Socket fd描述符去处理网络事件 |
select,poll |
方式3:由快递员/小邮局主动通知你 |
网络事件驱动模型 |
epoll,kqueue |
例子中的这3种不同的处理方式,体现的是网络编程中不同的事件响应和处理机制。让我们用计算机语言分别解读一下这3种处理方式。
方式1是阻塞式I/O,没有处理完数据函数不返回。你在小邮局门口守株待兔,这段期间你不能干其它任何事情,包括上厕所和玩手机。这看起来很傻,但在真实编程世界中,却随处可见这种阻塞式I/O,尤其是在一些同步通信的应用中。它的缺点很明显,但也并非一无是处:业务逻辑实现简单,系统依赖较少,方便处理异常错误,一旦阻塞线程被挂起不再进入操作系统内核的调度队列(不再消耗CPU)。当然,可以通过socket参数设置为非阻塞模式(如Python的sock.setsockopt()函数)。
方式2是轮询,遍历句柄描述符集合中的每一个描述符fd,检查是否有就绪的网络事件需要处理。Linux/Windows网络编程所提供的select()和poll()函数,就是采用轮询遍历的方式。就如同方式2中你打电话询问每一家快递公司我的包裹到了没,哪怕全世界只有一家快递公司,不仅你很累,那个快递员也会被你烦死(相当于CPU一直在工作),更别说有成百上千家快递公司,对你的电话费和快递员们都是负担(应用程序和操作系统都有开销)。对于并发请求量小的网络应用来说,轮询机制问题不大。但对于那些高吞吐高并发的热门应用来说,C10K问题(甚至C100K问题)是他们的家常便饭,如果此时还使用轮询遍历的方式,性能之差可想而知。这也是大家熟知的Apache被Nginx超越的根本原因。
方式3是事件驱动的方式,也称消息通知方式。Linux内核提供的epoll和FreeBSD提供的kqueue都是采用该方式,本质都是一种多路复用I/O技术。简单说就是应用程序将需要关注的描述符fd登记到操作系统内核中,一旦有就绪的网络I/O事件(读/写)则由操作系统主动通知应用程序。以epoll为例,轮询方式的select()和poll()都只有一个调用函数,而epoll却提供了三个调用函数,epoll_create()用于新建epoll描述符,epoll_ctl()用于往描述符设置要关注的事件(EPOLL_CTL_ADD添加、EPOLL_CTL_DEL删除、EPOLL_CTL_MOD修改),最后用epoll_wait()来等待网络就绪事件,这些就绪事件由操作系统来主动通知,而非应用程序主动轮询。FreeBSD的kqueue也是类似。就如同你不用蹲守小邮局门口,也不需要挨家快递公司打电话去问,你该码代码码代码去,该画图画图去,该做PPT做PPT去,一旦快递到了(网络事件就绪了),自然有快递员或小邮局主动通知你。相比其他方式,事件驱动方式的优势很明显,对于编写低开销高并发高吞吐的网络程序非常有用,流行的nginx、redis、memcache、lighttpd等开源产品皆受益于此。同时对编程要求较高(譬如epoll分为Edge Triggered和Level Triggered两种不同的触发机制,代码实现不同,别用混了),尤其是异常处理方面(譬如如何对待EAGAIN异常)。
三、select/poll/epoll/kqueue主要特点:
select:
poll:
epoll:
kqueue:
四、小结:
在网络编程中,blocking与non-blocking谁更好?select与poll与epoll与kqueue谁更好?这些都是伪命题,脱离了具体需求和业务场景都无从谈起。简单说,
提个醒:网易严选的快递都发顺丰哈 :-D
五、参考资料:
http://baike.baidu.com/view/2877739.htm
http://www.wuzesheng.com/?p=660
https://www.zhihu.com/question/20122137
http://man7.org/linux/man-pages/man7/epoll.7.html
https://banu.com/blog/2/how-to-use-epoll-a-complete-example-in-c/
http://www.ibm.com/developerworks/cn/aix/library/1105_huangrg_kqueue/
https://people.eecs.berkeley.edu/~sangjin/2012/12/21/epoll-vs-kqueue.html
网易云新用户大礼包:https://www.163yun.com/gift
本文来自网易实践者社区,经作者陈俊平授权发布。