12. PHP socket 初探 - 颤颤抖抖开篇 epoll(一)
正如标题所言,颤颤抖抖开篇 epoll。颤颤抖抖的原因大概也就是以前几乎没有亲自 手刃 epoll 的经验,仅仅靠 epoll 的理论知识骗吃骗喝骗人事哄小孩儿装高手,现如今,没有了大师兄的铁头功照顾,没有了六师弟的轻功水上漂背,没有了阿梅的太极功护身,不得不自己个儿当一次排头兵了。
先立个 flag,那就是 epoll 比 select 牛逼,尽管 select 是 POSIX 标准。即便是 select 的高配版本 poll,也比 epoll 差太多太多。网络如此发达的今天,epoll 是解决 c10k 问题的功臣,这是没有办法的事情。epoll 虽然是后出生的,但是却有着与生俱来的高傲,就像王思聪;select 就是普通屌丝,花点儿钱使劲装扮自己也顶多就是个 poll。这 poll 和 epoll,可差一个 e 呢,没办法,与生俱来的差距。
坊间传闻,在 epoll 出世前,QQ 用户量剧增,但是 select 以及 select 的高配版本 poll 都无法解决他们的问题,于是乎 QQ 当年的服务器就不得不用 UDP 协议来避规这个问题,一直到后来有了 epoll,QQ 开始逐步在PC客户端中的配置项中允许用户选择 UDP 服务器或 TCP 服务器。
还是通过浅显的示例来说明下为啥 epoll 比 select 厉害(这个例子在前面文章中应该提过,今儿再回放一遍)。
你要去继续练习大力金刚腿,阿梅还是要替你收双十一的10个快递。为了方便自己记忆这些快递,你把十个快递记录到了一个清单上给了阿梅。但这个时候阿梅显然不太清楚怎么应付这场景,于是每当收到X个快递,阿梅都是直接把快递清单抄写一份再拿给你并告诉你:“有快递来了!”,至于来了几个快递以及是分别是哪个镖局护送的,阿梅是不会告诉你的。于是只能是你自己,把单子上的10个快递逐次和收到的对比一遍,然后对比完毕后再把这个单子给了阿梅,然后阿梅继续等。
又是一年双十一,阿梅这次学聪明了,经历过那场球赛后,她已经得到了自我,实现了人生价值,今年的阿梅是一个全新的阿梅,一个剃了光头的阿梅。
你要去继续练习大力金刚腿,阿梅还是要替你收双十一的10个快递。为了方便自己记忆这些快递,你把十个快递记录到了一个清单上给了阿梅。但这个时候的阿梅显然已经得到了自我,是升华了的阿梅,于是每收到X个( X >= 1 )快递,阿梅都会在冲你喊一句:“顺丰镖局大师兄的铁头套,圆通镖局六师弟的鸡蛋到了!”,而你,不用再去依次对单子,阿梅会直接告诉你是哪个镖局护送的哪个快递,然后她还会按照你提前告诉她的“如果收到鸡蛋就给六师弟,收到铁头套就给大师兄”。哪怕你买了 10000 个快递,阿梅照样四两拨千斤,太极功夫收快递,而你,只需要安静的练习大力金刚腿。
剃光头前的阿梅,就是 select,不敢正眼看老板娘一眼。
剃光头后的阿梅,就是 epoll,可徒手接魔鬼队的死亡之球。
快递就相当于是 socket fd,包括监听socket和连接socket;那个清单就是 fd 的集合;阿梅就是 select 或者 epoll;你就是当前的一个进程;某个快递到了,就相当于是某个fd已经可读或可写。
select 虽然一定程度上解决了一个进程可以读写多个 fd 的问题,但是 select 有如下致命缺点:
- 默认情况下,select 可管理的 fd 的数量是 1024 个(阿梅最多帮你收 1024 个快递)
- select 每次检测到fd集合中有可读写的fd时,它会把整个fd全部复制一遍给你,然后你自己再去逐个轮询究竟是哪个fd可读写
- 正如以上所说,它会把整个fd全部复制给你(她把整个清单抄了一份给你),从术语上讲,这个过程是将fd从内核态复制一遍给用户态的调用进程
- 正如以上所说,你自己逐个轮询所有fd才能知道究竟是哪个可读写(反正就是有快递来了,来了几个都是谁你自己个儿对着清单查去)
- 你自己个轮询的过程是线性的,如果有个n个fd,那么时间复杂度一定是O(n)
而 epoll 则拥有更加专业的高端大气上档次的技能指标:
- 理论上可以搞定无上限的 fd(可以收无数个快递的阿梅)
- 只挑出可读写(其实严格意义上还有异常)的活跃的fd,其余的fd不理会
- 使用 MMAP 加速内核态数据拷贝
除此之外,需要特殊指出的是,epoll 本身的两种模式:
- 水平触发。这种方式下,如果监听到了有X个事件发生,那么内核态会将这些事件拷贝到用户态,但是可惜的是,如果用户只处理了其中一件,剩余的X-1件出于某种原因并没有理会,那么下次的时候,这些未处理完的X-1个事件依然会从内核态拷贝到用户态。这样做是有阴阳两面的,阳面是事件安全的不会发生丢失,阴面是对于性能来说是一种浪费。其实这个时候的epoll颇有些类似于poll的工作方式。
- 边缘触发。这种方式下,是鸡血版本的epoll,是释放自我的epoll,也是应该是正确的使用方式。这种情况下,如果发生了X个事件,然而你只处理了其中1个事件,那么剩余的X-1个事件就算“丢失”了。性能是上去了,与之俱来的就是可能的事件丢失。
那么,你以为是时候写代码演示epoll了,然而并不是,原因有两个:
- 通过C语言可以直接操作epoll,但是,为了避免装逼失败,我决定不用C来演示(放到后面再深入的时候)
- 如果说通过PHP来操作,我不得不提一件悲催的事情,据我自己得到的经验告诉我 那就是PHP无法直接操控epoll,而是要通过操作libevent来搞定epoll。
那么,什么是 Libevent 呢?怎么听着好耳熟,不光耳熟,你看下下图,是不是还有点儿眼熟?没错,这的博客的前端页面就是抄的 Libevent 官网 的。
我先从 Libevent 官网抄袭一段话:“Currently, libevent supports /dev/poll, kqueue(2), event ports, POSIX select(2), Windows select(), poll(2), and epoll(4). ”,你就能大概知道 Libevent 是干啥的了。大概意思就是 Libevent 对 /dev/poll、Mac 中的 kqueue、select、poll 以及 epoll 的 API 进行了封装,屏蔽了这几个多路复用开发上的一些细节和不同点,对外提供统一的 API 的一个高性能网络事件库。
额外提醒一点,这个东西是用 C 语言编写的,几十年过去了,你大爷还是你大爷。
回到正路上来,就是 PHP 中如何使用 Libevent。在 pecl.php.net 上,有两个扩展都可以使 phper 方便地操控 libevent,一个就叫 libevent,另一个叫做 event,推荐大家用后者。前者不知道什么原因版本一直停留在 0.10 Beta 状态,开发日期则停留在了 2013-05-22日,我没怎么试过,估计可能不支持 php7,不过,还是要感谢开发者。event 扩展就比较屌了,版本迭代不错,看起来开发者挺积极的,也支持 php7,目前的稳定版本是 2.3.0,所以推荐大家使用 event 扩展。
php 扩展的安装方式
正好在此补充一下 php 扩展的安装方式,以 event 扩展为例。
下载 event 2.3.0 的稳定版本,wget https://pecl.php.net/get/event-2.3.0.tgz
解压 tgz 源码包,tar -zxvf event-2.3.0.tgz
cd event-2.3.0 进入到主目录中,然后执行 phpize,再执行 ./configure
执行 make
执行 make install 安装
配置 php 的 cli 环境配置文件,注意不是 apache2,也不是 fpm 的,而是 cli 的 php.ini,添加一句 :extension = '/usr/lib/php/20151012/event.so',然后在终端中执行 php -m 看下,是不是有 event 呢?