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

著作权归作者所有。

商业转载请联系作者获得授权非商业转载请注明出处。
作者大家可以看我的知乎专栏
链接http://zhuanlan.zhihu.com/auxten/20204159
这是一个系列的文章之三之四已经写完了会陆陆续续搬到Linuxtone着急的同学可以看

  • 网络编程三从libevent到事件通知机制

  • 网络编程四互联网中TCP Socket服务器的实现过程需要考虑哪些安全问题

wKioL1ZvjMnCjb-PAAKle0j8qtM096.jpg

在数据加密领域举例子我们经常会提到Alice和Bob我们也继续延续这种传统。

在遥远的1752年的英国电报这种可以穿越千山万水发送消息的东西还没被发明。 Alice和Bob是大学认识的一对情侣。由于毕业找工作的原因Alice在利物浦工作 Bob在曼彻斯特工作笔者掰着指头就能编出这两个了o_o|| 。

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

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

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

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

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

  • 如果太频繁地检查邮箱手头的事情会被经常打断太耗费精力。

  • 如果检查的间隔时间太长就不可能每次都及时收到Alice的信件。

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

CHECK_INTERVAL = 5 * 60 # 检测间隔
mailbox_fd = listen(MAILBOX) # 假设邮箱对应着一个端口
while True:
  mails = CheckMailBox(mailbox_fd) # 看一眼mail
  if mails: # 说明有信来了
    # 处理邮件
    processMail()
  sleep(CHECK_INTERVAL)

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

  • 设置太小就会频繁地进行CheckMailBox()CPU占用率就会非常高。

  • 设置太大可能会导致邮件在sleep(CHECK_INTERVAL)的时候到了不能被及时处理。

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

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

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

0002a56d2a62b1bc77c7ebae29bdcd7e_b.png


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

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

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

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

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

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

2161889380f7850f7aac1357fcbca7ad_b.jpg

这就像服务端编程中我们不能要求每一个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:

#include <sys/select.h>

int
select(int nfds, fd_set * restrict readfds, fd_set * restrict writefds, fd_set * restrict errorfds, struct timeval * restrict timeout);
  • poll:

#include <poll.h>

int
poll(struct pollfd fds[], nfds_t nfds, int timeout);

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

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

如果UNIX和Linux是中国人发明的估计就没有这个问题了毕竟欧美人少
很少有机会见识成百万上千万乃至上亿的并发。

哈哈说笑了其实是这样早些年UNIX开发者都是些大学的教授和学生
实在没想到UNIX和TCP/IP在日后能有这么大的影响力。
所以就有了IPv4、select()等等这些对用户量预估不够给后世的困扰了。

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

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

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

Bob的信箱就是一个socketAlice和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嘭地一声横空出世全剧终。

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

那我还是正经的说说吧

下面这段摘自wikipediazh.wikipedia.org/wiki/E

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

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

int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event * event);
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

为了解决高并发问题大约在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。

各种操作系统在解决这个问题的办法上也是百花齐放

技术操作系统kqueueUNIX (FreeBSD、MacOS)epollLinux 2.5.44/2.6.9IOCP (IO Completion Port)Windows NT 3.5, AIX, Solaris 10

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

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

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

指示板如下图所示

7eccad5725b430ab2713bdbb5c8dd509_b.png

epoll的ET和LT

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

  • ET (Edge Triggered) 边沿触发

    来了信件指示板上的灯只闪一下。

  • LT (Level Triggered) 水平触发

    来了信件指示板上的灯一直亮着直到信箱中的信全部被取走。

    ==================================

    》》  想要了解更多精彩内容欢迎关注》》

    更多技术交流,请加QQ交流群:238757010 


本文出自 “Reboot运维开发” 博客,转载请与作者联系!

你可能感兴趣的:(epoll,网络编程,poll,unix网络编程)