网络编程(二)丨戏说非阻塞网络编程

tutu

在数据加密领域举例子,我们经常会提到Alice和Bob,我们也继续延续这种传统。
在遥远的1752年的英国,电报这种可以穿越千山万水发送消息的东西还没被发明。 Alice和Bob是大学认识的一对情侣。由于毕业找工作的原因,Alice在利物浦工作 ,Bob在曼彻斯特工作(笔者掰着指头就能编出这两个了o_o|| )。

2

虽然在不同的城市工作,但这毫不影响两个人的感情,他们经常写信来互诉衷肠。 那么问题就来了,这两个城市路程大约60km,邮差每天送信一次。 天气好的时候,Alice当天写的信Bob晚上就能收到,但如果遇上坏天气或者邮差睡过头了, 几天之后Bob才能看到心爱的Alice的亲笔信。

Bob非常思念Alice,魂不守舍的时候就去邮箱里看看有没有Alice的消息, 没有Alice的信就一直守候在信箱旁,等待邮差送信过来。

如果把Alice和Bob看作是互联网上的两台主机,Bob这种行为在计算机网络通信领域被叫做 阻塞式通信。我们可以看到,Bob这种行为无疑是十分愚蠢而幼稚的, 为了等信导致不能进行其它日常的事务处理。如果设置超时时间,极端点儿是有可能饿死在信箱旁的。

3

当然,我们的Bob同学没有那么极端,每次在信箱前等待5分钟后就去做别的事情。

然而,Bob同学依然面临一个矛盾。虽然不会永远的傻傻等待Alice的信件,但:

  • 如果太频繁地检查邮箱,手头的事情会被经常打断,太耗费精力。
  • 如果检查的间隔时间太长,就不可能每次都及时收到Alice的信件。

如果用网络服务器来类比Bob同学,伪代码是这样的:

Bob同学面临的矛盾,之于网络服务器就是CHECK_INTERVAL:

  • 设置太小,就会频繁地进行CheckMailBox(),CPU占用率就会非常高。
  • 设置太大,可能会导致邮件在sleep(CHECK_INTERVAL)的时候到了,不能被及时处理。

难道说这就没有两全其美的办法了么?

我们的工程师Bob在经历了一番思索后想到了一个绝妙的办法:

在信箱的口上加一个铃铛,让邮差放信的时候帮忙摇一下铃铛,Bob听到铃响再来取信。

4

这样,Bob就可以专心工作,既不用分心来查看邮箱,也能及时的查阅新邮件。

但细心的同学发现,我们这里略过了一个重要的细节。要想专心工作需要一个外部前提:

需要邮差大哥配合,每次来信箱放完信,摇一下铃铛。

每次来送信的邮差又不总是同一个人,和每个邮差交代一下也不现实。

这当然还是难不倒我们的工程师Bob同学。Bob把邮箱改造了一下:

每次邮差把信放进邮箱,邮箱上就会竖起一个小旗子,表示有信在里面。

tutu

这就像服务端编程中,我们不能要求每一个TCP连接提供“通知”的机制, 只能从服务器自身的机制上想办法。我们在Linux、UNIX网络编程上经常用到的: select()、poll()、epoll()都是我们上面说的Bob改造过的邮箱类似的系统调用机制。

只是他们之间有一些细节的差异而已:

select()和poll()

select()是在1983年首次出现在4.2版的BSD UNIX中。 poll()出现得稍晚一些,首次出现同样是在UNIX平台, 晚到了1997年才在2.1.23版本的Linux内核中首次被加入。

select()有一个众所周知的问题,只能处理小于等于1024的文件描述符; poll()虽然没有这个问题,但在很多平台的实现里,poll()和select()的实现基本是同一套代码。 只是提供了两套不同的接口罢了:

  • select:

  • poll:

select()在处理大量的网络连接带来的socket描述符低效的问题仍旧存在于poll()中。

当年,select()被设计出来的时候, UNIX和Linux内核的设计者怎么也没意料到日后会在服务端处理那么多的socket连接。

如果UNIX和Linux是中国人发明的估计就没有这个问题了,毕竟欧美人少, 很少有机会见识成百万,上千万乃至上亿的并发。 哈哈,说笑了,其实是这样,早些年UNIX开发者都是些大学的教授和学生, 实在没想到UNIX和TCP/IP在日后能有这么大的影响力。 所以就有了IPv4、select()等等这些对用户量预估不够给后世的困扰了。

select()和poll()的效率问题

我们现在回到正题,简单聊一下select()和poll()为什么效率低的问题:

假设我们的Bob同学由于工作十分出色,升职加薪,工作上的事情也比当 初多了很多。

Bob的信箱就是一个socket,Alice和Bob通过这条虚拟的socket进行 沟通。我们Bob还有很多朋友和工作上的伙伴,Bob为每个联系人都设立了 单独的邮箱,所以也请了一个秘书专门帮着收信。

这时Bob相当于就是一个高并发的服务器。我们的select()就相当于是 Bob的秘书。秘书在处理邮箱的时候的方法也十分的原始,就是Bob在调用 select()的时候(大家应该能明白我的意思),秘书去挨个查看邮箱上的 小旗子。这样做的时间复杂度是O(n)的。

邮箱数量少一些还好,秘书虽然累但也能处理得过来。但太多了秘书实在 处理起来费劲,我们的秘书比较有个性,跟Bob同学讲明了,如果邮箱数量 到了1024以上她就罢工。Bob虽然心有怨言,但明白秘书的苦衷也是在没 有好办法。

后来,Bob这里又找了一个名叫poll()的秘书,poll()倒是比select() 更加任劳任怨,并没有给Bob提出1024这个上限。但无奈还是用的select() 的老办法:Bob调用的时候还是挨个看邮箱。数量多了不免要查上个半天。

如何解决高并发的问题

这个问题是个很大的问题,我们这里就不展开说,后面还会提。 但有一点我们都很清楚,首先要解决的是select()和poll()的效率问题。

然后,epoll嘭地一声横空出世,全剧终。

等等,太坑爹了,板凳都搬来了,你就让我听这个?

那我还是正经的说说吧:

下面这段摘自wikipedia:https://zh.wikipedia.org/wiki/Epoll

epoll是Linux内核的可扩展I/O事件通知机制。它设计目的只在取代既有POSIX select(2)与poll(2)系统函数,让需要大量操作文件描述符的程序得以发挥更优异的性能 (举例来说:旧有的系统函数所花费的时间复杂度为O(n),epoll则耗时O(1))。 epoll与FreeBSD的kqueue类似,底层都是由可配置的操作系统内核对象建构而成, 并以文件描述符(file descriptor)的形式呈现于用户空间。

epoll由下面几个系统调用组成:

为了解决高并发问题,大约在2000年,Jonathan Lemon在FreeBSD内核中实现了第一个版本 的kqueue,并在FreeBSD 4.1版本发布。之后FreeBSD在处理高并发的问题上一直领先于Linux。在2002年,Linux的2.5.44版本(测试版本)首次加入了epoll机制。 直到2004年,在Linux的2.6.9版本epoll相关的API才稳定下来, Linux的高并发机制这才跟赶上FreeBSD。所以,在2004年之前很多公司在服务器操作系统选型的时候选择的是FreeBSD,而不是Linux。各种操作系统在解决这个问题的办法上也是百花齐放:

jishu

如果继续用Alice和Bob的例子来说的话事情就是这样的:

看着这两个秘书这么辛苦,Bob终于坐不住了,想出来一个绝妙的主意:

秘书们设计了一个指示板和邮箱联动,邮箱里有信的时候,指示板上灯就会亮起来。 这样,哪个邮箱里有信秘书就一目了然了。这样检查邮箱这件事情的时间复杂度就是 O(1)了。

指示板如下图所示:

lala

epoll的ET和LT

关于epoll的ET和LT之前已经说过,这里借用Alice和Bob的例子。

  • ET (Edge Triggered) 边沿触发来了信件,指示板上的灯只闪一下。
  • LT (Level Triggered) 水平触发来了信件,指示板上的灯一直亮着,直到信箱中的信全部被取走。

 

 

12月05日Python实战班第七期开课,报名火爆进行中…

那么实用的课程,你怎么能错过!

咨询报名QQ:279312229

报名电话:17801018615

Reboot官方QQ交流群:238757010 365534424

Reboot官网:http://www.51reboot.com/course/actual/

 

文章分类 技术博客, 运维
2 comments on “网络编程(二)丨戏说非阻塞网络编程
  1. zskjgx说道:

    我来看看,欢迎不欢迎?

发表评论

电子邮件地址不会被公开。

您可以使用这些HTML标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">

在线交流

数百位业内高手和同行在等你交流
Reboot运维开发分享