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扩展的安装方式,以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呢?

好了,今天到这里正式收官,下一篇继续嗑php和他的event扩展二三事!

注:本文为转载,原文地址PHP高级语法讲解

为者常成,行者常至