vlambda博客
学习文章列表

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.php while(true) { stream_select($sockets, [],[], 60); foreach ($sockets as $index => $socket) { //TODO 处理有数据的socket
} }          

优点: 方便快捷,轻量化,不用引用依赖库

缺点 : 只能使用select IO 模型,单线程最大只能打开1024 个文件,Select 模型性能比较差,对并发比较高的场景不适用

完整demo

<?php
class SocketServer{ //监听socket protected $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 ./configure --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

<?php$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:如果没有你的关注,那我所做的将毫无意义

欢迎分享点赞在看