PHP IO编程epoll实现方案
来源:http://a.nxw.so/21owAg
什么是EPOll
epoll是Linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。另一点原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。epoll除了提供select/poll那种IO事件的水平触发(Level Triggered)外,还提供了边缘触发(Edge Triggered),这就使得用户空间程序有可能缓存IO状态,减少epoll_wait/epoll_pwait的调用,提高应用程序效率。
一、PHP 原生socket 实现IO(select模式)
现阶段php原生方法是没有办法使用epoll模型的,原生实现只能使用select模型,主要步骤如下:
//1:创建一个socket监听,参数ip:端口$socket = stream_socket_server($socket_address);//2:设置为非阻塞stream_set_blocking($socket , 0);s//3:监听socket 整个进程阻塞在这里,持续监听可读事件//此处参数均为引用传递,在函数中会改变传值,第一个为要监听可读的socket数组,第二为可写的sockets,接口详情参考https://www.php.net/manual/zh/function.stream-select.phpwhile(true) {stream_select($sockets, [],[], 60);foreach ($sockets as $index => $socket) {//TODO 处理有数据的socket}}
优点: 方便快捷,轻量化,不用引用依赖库
缺点 : 只能使用select IO 模型,单线程最大只能打开1024 个文件,Select 模型性能比较差,对并发比较高的场景不适用
完整demo
class SocketServer{//监听socketprotected $socket = NULL;//所有的socket连接protected $sockets = array();//连接事件回调public $onConnect = NULL;//断线事件回调public $onClose = NULL;//接收消息事件回调public $onMessage = NULL;public function __construct($socket_address) {//创建一个socket监听$this->socket = stream_socket_server($socket_address);//设置为非阻塞stream_set_blocking($this->socket, 0);//将socket监听加入allSockets$this->sockets[(int)$this->socket] = $this->socket;}public function run() {while(true) {//不监听可写事件与带外数据事件$write = $except = array();//监听所有的socket事件$read = $this->sockets;//整个进程阻塞在这里,持续监听可读事件//此处参数均为引用传递,在函数中会改变传值stream_select($read, $write, $except, 60);//处理所有可读事件foreach ($read as $index => $socket) {//如果是监听socket,此处表示有新的连接if ($socket === $this->socket) {//通过stream_socket_accept获取新的连接$new_conn_socket = stream_socket_accept($socket);if ($this->onConnect) {//触发连接事件的回调,并将当前连接传递给回掉函数call_user_func($this->onConnect, $socket);}//记录此socket连接,以便于sream_select监听可读事件$this->sockets[(int)$new_conn_socket] = $new_conn_socket;} else//如果可读事件不为监听socket,则表示对应客户端有数据发过来{//从连接中读取数据$buffer = fread($socket, 65535);//如果数据为空,表示客户端已经断开连接if ('' === $buffer || false === $buffer) {//尝试触发onClose回调if ($this->onClose) {call_user_func($this->onClose, $socket);}fclose($socket);//关闭socket连接并从allSockets中删除unset($this->sockets[(int)$socket]);continue;}//表示一个正常的连接,已经读取到消息,交给回掉函数处理if ($this->onMessage) {call_user_func($this->onMessage, $socket, $buffer);}}}}}}$server = new SocketServer('tcp://0.0.0.0:9501');$server->onConnect = function ($conn) {echo 'connect';};$server->onClose = function ($conn) {echo 'close';};$server->onMessage = function ($conn, $message) {$http_resonse = "HTTP/1.1 200 OK\r\n";$http_resonse .= "Connection: keep-alive\r\n";$http_resonse .= "Server: php socket server\r\n";$http_resonse .= "Content-length: 11\r\n\r\n";$http_resonse .= "hello world";fwrite($conn, $http_resonse);};$server->run();
二、使用event扩展实现epoll(或者Libeven 拓展,两者选一个)
php event就是一个事件库,对市面上各种常用的IO复用技术的统一封装,通过它可以实现epoll io模型通信
拓展安装
# 下载eventwget https://pecl.php.net/get/event-3.0.3.tgz# 解压文件tar -xf event-3.0.3.tgz# 进入目录cd event-3.0.3# 执行phpize/www/server/php/72/bin/phpize--with-php-config=/www/server/php/72/bin/php-config# 安装make && make install#修改php.ini配置extension = /www/server/php/72/lib/php/extensions/no-debug-non-zts-20170718/event.so
Demo 使用:https://cloud.tencent.com/developer/article/1586427
$s_host = '0.0.0.0';$i_port = 9501;$r_listen_socket = socket_create( AF_INET, SOCK_STREAM, SOL_TCP );socket_set_option( $r_listen_socket, SOL_SOCKET, SO_REUSEADDR, 1 );socket_bind( $r_listen_socket, $s_host, $i_port );socket_listen( $r_listen_socket );// 将$listen_socket设置为非阻塞IOsocket_set_nonblock( $r_listen_socket );$a_event_array = array();$a_client_array = array();// 创建event-base$o_event_base = new EventBase();$s_method_name = $o_event_base->getMethod();if ( 'epoll' != $s_method_name ) {exit( "not epoll" );}function read_callback( $r_connection_socket, $i_event_flag, $o_event_base ) {$s_content = socket_read( $r_connection_socket, 1024 );echo "接受到:".$s_content;// 在这个客户端连接socket上添加 读事件// 当这个客户端连接socket一旦满足可写条件,我们就可以向socket中写数据了global $a_event_array;global $a_client_array;$o_write_event = new Event( $o_event_base, $r_connection_socket, Event::WRITE | Event::PERSIST, 'write_callback', array('content' => $s_content,) );$o_write_event->add();$a_event_array[ intval( $r_connection_socket ) ]['write'] = $o_write_event;}function write_callback( $r_connection_socket, $i_event_flag, $a_data ) {global $a_event_array;global $a_client_array;$s_content = $a_data['content'];foreach( $a_client_array as $r_target_socket ) {if ( intval( $r_target_socket ) != intval( $r_connection_socket ) ) {socket_write( $r_target_socket, $s_content, strlen( $s_content ) );}}$o_event = $a_event_array[ intval( $r_connection_socket ) ]['write'];$o_event->del();unset( $a_event_array[ intval( $r_connection_socket ) ]['write'] );}function accept_callback( $r_listen_socket, $i_event_flag, $o_event_base ) {global $a_event_array;global $a_client_array;// socket_accept接受连接,生成一个新的socket,一个客户端连接socket$r_connection_socket = socket_accept( $r_listen_socket );$a_client_array[] = $r_connection_socket;// 在这个客户端连接socket上添加 读事件// 也就说 要从客户端连接上读取消息$o_read_event = new Event( $o_event_base, $r_connection_socket, Event::READ | Event::PERSIST, 'read_callback', $o_event_base );$o_read_event->add();$a_event_array[ intval( $r_connection_socket ) ]['read'] = $o_read_event;}// 在$listen_socket上添加一个 读事件// 为啥是读事件?// 因为$listen_socket上发生事件就是:客户端建立连接// 所以,应该是读事件$o_event = new Event( $o_event_base, $r_listen_socket, Event::READ | Event::PERSIST, 'accept_callback', $o_event_base );$o_event->add();//$a_event_array[] = $o_event;$o_event_base->loop();
优点: 支持epoll模型,并发性能比较好,足够灵活,适合写框架使用
缺点:文档,教程比较少,很多靠自己摸索,封装度不高,在项目中使用需要二次封装
三、Swoole
官网:https://www.swoole.com/
文档:https://wiki.swoole.com/#/
Swoole 高度封装了各种通信服务 如:tcp,http,webscoket等,使用简单方便,内部都是使用epoll调用
$server = new Swoole\Server('127.0.0.1', 9503);$server->on('start', function ($server) {echo "TCP Server is started at tcp://127.0.0.1:9503\n";});$server->on('connect', function ($server, $fd){echo "connection open: {$fd}\n";});$server->on('receive', function ($server, $fd, $reactor_id, $data) {$server->send($fd, "Swoole: {$data}");});$server->on('close', function ($server, $fd) {echo "connection close: {$fd}\n";});$server->start();
优点:可以使用epoll模型,效率高,封装度高,使用方便,文档完善
缺点:框架庞大,与传统FPM 编程完全不同,很多操作容易引起程序异常,入门门槛比较高
总结
现阶段php原生不支持epoll模式的IO调用,可以通过三方拓展Event实现epoll调用,或者使用swoole 直接创建服务即可
简单分享快乐学习,如有错误请多包涵!
PS:如果没有你的关注,那我所做的将毫无意义!
欢迎分享,点赞,在看。
