Event擴展
Event可以認為是替代libevent最好的擴展,因為libevent已經很久不更新了,而Event一直在更新,而且Event支持更多特性,使用起來也比libevent簡單。
Event地址: http://pecl.php.net/package/event
Event文檔: http://docs.php.net/event
和libevent一樣,系統需要先安裝 Libevent 庫,因為都是基於 Libevent 庫開發的:
yum install libevent-dev
然后安裝PHP擴展。
PHP7安裝:
pecl install event
Event擴展不支持PHP5。
注:后面的代碼示例均使用的php7.1 + event環境。
基本使用
我們把libevent_tcp_server.php的例子改為Event實現的:
event_tcp_server.php
<?php
/**
* Created by PhpStorm.
* User: 公眾號: 飛鴻影的博客(fhyblog)
* Date: 2018/6/23
*/
$receive = [];
$master = [];
$buffers = [];
$socket = stream_socket_server ("tcp://0.0.0.0:9201", $errno, $errstr);
if (false === $socket ) {
echo "$errstr($errno)\n";
exit();
}
if (!$socket) die($errstr."--".$errno);
//stream_set_blocking($socket,0); //可選
$id = (int)$socket;
$master[$id] = $socket;
echo "waiting client...\n";
//accept事件回調函數,參數分別是$fd, $events, $arg
function ev_accept($socket, $flag, $base){
global $receive;
global $master;
global $buffers;
$connection = stream_socket_accept($socket);
stream_set_blocking($connection, 0);//必須
$id = (int)$connection;
echo "new Client $id\n";
$event = new Event($base, $connection, Event::READ | Event::PERSIST, 'ev_read', $id);
$event->add();
$master[$id] = $connection; //根據業務可選
$receive[$id] = ''; //根據業務可選
$buffers[$id] = $event; //根據業務可選
}
//read事件回調函數
function ev_read($buffer, $flag, $id)
{
global $receive;
global $master;
global $buffers;
//該方法里的$buffer和$master[$id]指向相同的內容
// var_dump(func_get_args(), $master[$id] );
//循環讀取並解析客戶端消息
while( 1 ) {
$read = @fread($buffer, 1024);
//客戶端異常斷開
if($read === '' || $read === false){
break;
}
$pos = strpos($read, "\n");
if($pos === false)
{
$receive[$id] .= $read;
// echo "received:".$read.";not all package,continue recdiveing\n";
}else{
$receive[$id] .= trim(substr ($read,0,$pos+1));
$read = substr($read,$pos+1);
switch ( $receive[$id] ){
case "quit":
echo "client close conn\n";
//關閉客戶端連接
unset($master[$id]);//斷開客戶端連接
unset($buffers[$id]);//刪除事件
break;
default:
// echo "all package:\n";
echo $receive[$id]."\n";
break;
}
$receive[$id]='';
}
}
}
//創建全局event base
$base = new EventBase();
//創建並設置 event:其中$events設置為READ | PERSIST ;回調事件為ev_accept,參數 $base
//PERSIST可以讓注冊的事件在執行完后不被刪除,直到調用Event::del()刪除.
$event = new Event($base, $socket, Event::READ | Event::PERSIST, 'ev_accept', $base);
$event->add();
echo "start run...\n";
//進入事件循環
$base->loop();
//下面這句不會被執行
echo "This code will not be executed.\n";
可以發現做的改動非常小,而且代碼更簡潔了。運行腳本后,我們使用telnet測試,效果一模一樣。
使用Buffer
直接看例子吧,還是基於上面的例子改的,注釋里寫得很清楚了:
event_buffer_tcp_server.php
<?php
/**
* Created by PhpStorm.
* User: 公眾號: 飛鴻影的博客(fhyblog)
* Date: 2018/6/23
*/
$receive = [];
$master = [];
$buffers = [];
$socket = stream_socket_server ("tcp://0.0.0.0:9201", $errno, $errstr);
if (false === $socket ) {
echo "$errstr($errno)\n";
exit();
}
if (!$socket) die($errstr."--".$errno);
//stream_set_blocking($socket,0);//可選
$id = (int)$socket;
$master[$id] = $socket;
echo "waiting client...\n";
//accept事件回調函數,參數分別是$fd, $events, $arg
function ev_accept($socket, $flag, $base){
global $receive;
global $master;
global $buffers;
$connection = stream_socket_accept($socket);
//stream_set_blocking($connection, 0);//可選
$id = (int)$connection;
echo "new Client $id\n";
//新建EventBuffer 事件
$event = new EventBufferEvent($base, $connection, 0, 'ev_read', 'ev_write', 'ev_status', $id);
$event->setTimeouts(30, 30); //read and write timeout
$event->setWatermark ( Event::READ, 0, 0xffffff ); //Adjusts read and/or write watermarks
$event->setPriority(10);
$event->enable(Event::READ | Event::PERSIST);
$master[$id] = $connection; //如果去掉該行,客戶端直接被斷開
$receive[$id] = ''; //如果去掉該行,服務端無法正常收到消息
$buffers[$id] = $event; //如果去掉該行,客戶端強制斷開再連接,服務端無法正常收到消息
}
//read事件回調函數,參數分別是EventBufferEvent,arg
function ev_read($buffer, $id)
{
global $receive;
global $master;
global $buffers;
//該方法里的$buffer和$buffers[$id]指向相同的內容
// var_dump(func_get_args(), $buffers[$id], $master[$id]);
//循環讀取並解析客戶端消息
while( 1 ) {
$read = $buffer->read(65535);
// var_dump($read);
//客戶端異常斷開;這里可能返回NULL
if($read === '' || $read === false || $read === NULL){
break;
}
$pos = strpos($read, "\n");
if($pos === false)
{
$receive[$id] .= $read;
echo "received:".$read.";not all package,continue recdiveing\n";
}else{
$receive[$id] .= trim(substr ($read,0,$pos+1));
$read = substr($read,$pos+1);
switch ( $receive[$id] ){
case "quit":
echo "client close conn\n";
//關閉客戶端連接
unset($master[$id]);//斷開客戶端連接
unset($buffers[$id]);//刪除事件
break;
default:
// echo "all package:\n";
echo $receive[$id]."\n";
break;
}
$receive[$id]='';
}
}
}
function ev_write($buffer, $id)
{
echo "$id -- " ."\n";
}
function ev_status($buffer, $events, $id)
{
echo "ev_status - ".$events."\n";
}
//創建全局event base
$base = new EventBase();
//創建並設置 event:其中$events設置為READ | PERSIST ;回調事件為ev_accept,參數 $base
//PERSIST可以讓注冊的事件在執行完后不被刪除,直到調用Event::del()刪除.
$event = new Event($base, $socket, Event::READ | Event::PERSIST, 'ev_accept', $base);
$event->add();
echo "start run...\n";
//進入事件循環
$base->loop();
//下面這句不會被執行
echo "This code will not be executed.\n";
定時器(Timer)
直接看示例:
event_timer.php
<?php
/**
* Created by PhpStorm.
* User: 公眾號: 飛鴻影的博客(fhyblog)
* Date: 2018/6/23
*/
$base = new EventBase ();
$n = 2 ; //sec
//初始化定時器
$e = Event :: timer ( $base , function( $arg ) use (& $e ) {
echo " $arg seconds elapsed\n" ;
$e -> delTimer ();
}, $n );
//添加定時器
$e -> addTimer ( $n ); //sec
$base -> loop ();
運行:
$ php event_timer.php
2 seconds elapsed
和libevent擴展一樣,Event::timer也是對Event的封裝:
<?php
/**
* Created by PhpStorm.
* User: 公眾號: 飛鴻影的博客(fhyblog)
* Date: 2018/6/23
*/
$base = new EventBase ();
$n = 2 ; //sec
//初始化定時器
$event = new Event($base, null, Event::TIMEOUT, 'ev_timer', $n );
$event->add($n);//sec
function ev_timer($fd, $what, $arg){
echo " $arg seconds elapsed\n" ;
global $event;
$event->del();
}
$base->loop();
Event提供的定時器精度是秒。
信號(Signal)
Event 擴展提供了信號(Signal)操作的函數。
<?php
/**
* Created by PhpStorm.
* User: 公眾號: 飛鴻影的博客(fhyblog)
* Date: 2018/6/23
*/
$base = new EventBase ();
//初始化信號事件
$e = Event :: signal ( $base , SIGUSR1, function( $signum , $arg ) use (& $e ) {
echo " Caught signal $signum\n" ;
$e->delSignal(); //移除信號
}, '');
//安裝信號
$e -> addSignal (); //sec
//發送信號
posix_kill(posix_getpid (), SIGUSR1);
$base -> loop ();
相比pcntl_signal,Event :: signal 高效很多。
總結
Libevent 非常強大,Event實現了其很多的接口供PHP調用,我這里僅是使用了常用的幾個特性。由於Event能參考的資料實在是有限,這章寫起來也相對難一些,例子里還是留了一些待再次理解。
(未完待續)
推薦
內容概要:Redis 最為目前炙手可熱的 Key-Value 數據庫,常用做緩存、Session共享中間件,分布式鎖等等。
本系列課程包括:

講師是CSDN 博客專家,多年 Redis 使用經驗。感興趣的朋友可以點擊試看!
