PHP默认没有提供epoll的函数,需要安装llibevent或者event扩展,建议使用event扩展。
不管是llibevent扩展或者event扩展,都需要先安装libevent程序来提供函数库,libevent封装了对于select, poll, epoll, kqueue等IO多路复用器的统一调用方式,并会根据当前系统选择一个合适的复用器。
安装libevent与event
https://blog.csdn.net/raoxiaoya/article/details/106170760
epoll实现原理
https://blog.csdn.net/raoxiaoya/article/details/106185479
libevent的功能
Libevent提供了事件通知,io缓存事件,定时器,超时,异步解析dns,事件驱动的http server以及一个rpc框架。
事件通知:当文件描述符可读可写时将执行回调函数。
Io缓存:缓存事件提供了输入输出缓存,能自动的读入和写入,用户不必直接操作io。
定时器:libevent提供了定时器的机制,能够在一定的时间间隔之后调用回调函数。
信号:触发信号,执行回调。
异步的dns解析:libevent提供了异步解析dns服务器的dns解析函数集。
事件驱动的http服务器:libevent提供了一个简单的,可集成到应用程序中的HTTP服务器。
RPC客户端服务器框架:libevent为创建RPC服务器和客户端创建了一个RPC框架,能自动的封装和解封数据结构。
event扩展提供的内容
event扩展是对libevent程序的调用的封装,所以对epoll的操作只是很小的一个部分。了解epoll实现原理有助于理解event扩展。
这里,我先使用 Event 和 EventBase 两个类来实现,先来介绍一下 $fd
和 $cb
:
Event 构造函数
mixed $fd 可以有五种值:
1、由 sream族函数创建的资源。
2、由 socket族创建的资源。
3、操作系统的文件描述符编号,整型。
4、-1,对应为定时器。
5、信号值,整型,对应为信号事件。
Event callbacks
文档 https://www.php.net/manual/zh/event.callbacks.php
callback 最后一个参数 mixed $arg 就是 Event 构造函数的最后一个值,如果有的话。
下面来说说 $fd
1、PHP的资源类型保存了对外部资源的引用,但是它们的ID号是没有规律对应的,比如,一个socket资源,资源ID为7,通过该资源就可以找到对应操作系统上的文件描述符fd,比如3;这也就是为什么它可以接受 stream resource, socket resource, numeric file descriptor
作为参数,因为通过stream resource
或者socket resource
也可以得到numeric file descriptor
的值,并且,最终是以操作系统的fd号numeric file descriptor来作为epoll函数的参数的。
2、传入Event的$fd
参数是什么类型,那么回调函数中的$fd
就是什么类型,会原样传入。
一个简单的epoll调用
epoll_create
, epoll_wait
由 EventBase
类实现
epoll_ctl
由 Event
类实现
启动服务
php server/socketServerEpoll.php
查看进程ID
ps -ef |grep Epoll
3138
查看进程打开的 fd
cd /proc/3138/fd
ll
可知监听的 fd 编号为3 ,由上面的分析可知,将 Event 构造中的 $this->serverSocket 换成 3 也是一样的。
但是,监听 fd 分配的编号不一定是 3,有可能是4,5,6,所以最好还是使用 $this->serverSocket 。
由epoll原理可知,操作系统会通知应用程序某fd发生了什么事件,比如 EPOLLIN,EPOLLOUT事件,应用程序只得到了fd编号
和 事件编号
,所以,某 fd 的某个事件对应的回调函数是注册在应用程序一方的,并且由应用程序决定如何调用回调函数,比如 [fd, EPOLLIN, callback],此处的 fd 为系统的fd号,从这一点来看,你可以为不同的 fd 的不同事件定义不同的回调处理。
Event与libevent的关系
实际上libevent为php程序提供了动态链接库(.so),也就是扩展,而event.so依赖于此扩展,在安装event的时候要正确指定libevent的位置,这样event才能去引入并调用它提供的方法,下面是libevent提供的动态连接库(.so)和静态链接库(.a),so是在程序运行阶段才被加载进内存的,a则是在链接期间就被结合到程序中。
查看 socketServerEpoll.php 打开的全部文件
其实就和加载PHP扩展一样,不同的是有的是PHP内核来加载,有的是由某个扩展来加载,此处是event扩展再去加载libevent的扩展,它们都是程序启动时就被加载而不管是否被使用。
特别注意的是:
一个连接断开后,一定要调用 Event 对象的 del() 或者 free(() 方法来移除注册在应用程序上的 [fd, EPOLLIN, callback];否则后面的连接可能无法注册事件回调。主要原因是系统的fd号会被重复使用(但是依然是不同的连接,因为socket的编号不一样)。
现象如下:
1、本地客户端连本地服务端,服务端得到7,设置事件并加入到epoll的fd池。
2、发送信息,可正常通讯。
3、正常断开此连接,重新建立连接,得到的还是7,然后居然没有触发任何回调。
4、利用strace监控服务端程序,发现连接断开后应用程序没有发起 EPOLL_CTL_DEL
调用,不过这也没关系,因为操作系统会自动将此fd从“fd池”移除;重建连接后应用程序没有发起 EPOLL_CTL_ADD
调用,这是因为应用程序自己还维护了一个列表(上面提到的 [fd, EPOLLIN, callback] ),因为之前这里没有移除7,所以应用程序会以为7是重复添加,直接忽略;然后导致新建的连接没有 EPOLL_CTL_ADD
,所以操作系统并不会通知新7的任何事件。
具体代码
https://gitee.com/phprao/socket/blob/master/server/socketServerEpoll.php
调用监控