Libevent 是一个用C语言编写的、轻量级的开源高性能I/O框架,支持多种 I/O 多路复用技术: epoll、 poll、 dev/poll、 select 和 kqueue 等;支持 I/O,定时器和信号等事件;注册事件优先级。PHP提供了对应的扩展 libeventEvent

libevent扩展很久没有更新了,仅支持PHP5系列,PHP7虽然有网友fork了 libevent 扩展的源码进行更新兼容,但是稳定性不好,可能会出现段错误,所以PHP7最好使用 Event 扩展。

与libevent扩展不同的是,Event 扩展提供了面向对象的接口,且支持更多特性。

libevent扩展

libevent地址: http://pecl.php.net/package/libevent

libevent文档: http://docs.php.net/libevent

系统需要先安装 Libevent 库:

yum install libevent-dev

然后安装PHP扩展。

PHP5安装:

pecl install libevent-0.1.0

PHP7安装(不稳定):

git clone https://github.com/expressif/pecl-event-libevent.git
cd pecl-event-libevent
phpize
./configure
make && sudo make install

注:后面的代码示例均使用的php5.6 + libevent-0.1.0环境。

基本使用

下面的例子实现了一个单进程的TCP server,基于libevent实现I/O复用,达到高性能。

libevent_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。
//也就是 event_set 函数的$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 = event_new();
event_set($event, $connection, EV_READ | EV_PERSIST, 'ev_read', $id);
event_base_set($event, $base);
event_add($event); $master[$id] = $connection;
$receive[$id] = '';
$buffers[$id] = $event; // 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"; // fclose($master[$id]); //断开客户端连接
// event_del($buffers[$id]); //删除事件 //下面的写法与上面调用函数效果一样,都是关闭客户端连接
unset($master[$id]);
unset($buffers[$id]);
break;
default:
// echo "all package:\n";
echo $receive[$id]."\n";
break;
}
$receive[$id]='';
}
}
} //创建全局event base
$base = event_base_new();
//创建 event
$event = event_new();
//设置 event:其中$events设置为EV_READ | EV_PERSIST ;回调事件为ev_accept,参数 $base
//EV_PERSIST可以让注册的事件在执行完后不被删除,直到调用event_del()删除.
event_set($event, $socket, EV_READ | EV_PERSIST, 'ev_accept', $base);
// 全局event base添加 当前event
event_base_set($event, $base);
event_add($event);
echo "start run...\n"; //进入事件循环
event_base_loop($base); //下面这句不会被执行
echo "This code will not be executed.\n";

我们先运行代码:

$ php libevent_tcp_server.php
waiting client...
start run...

客户端使用telnet:

$ telnet 127.0.0.1 9201
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
hello server!

代码里面我加了很多注释,基本上能看明白。需要注意的是:

1、event_base是全局的,只需要创建一次,后续都是event的设置和添加。

2、event_set 的回调函数有三个参数,分别是$fd, $events, $arg。也就是 event_set 函数的$fd, $events, $arg参数。arg 如果需要多个,可以为数组。fd参数实际是保存的客户端连接,是个resource。events参数支持下列这些常量:

  • EV_TIMEOUT: 超时。利用事件可以实现定时器
  • EV_READ: 只要网络缓冲中还有数据,回调函数就会被触发
  • EV_WRITE: 只要塞给网络缓冲的数据被写完,回调函数就会被触发
  • EV_SIGNAL: POSIX信号量
  • EV_PERSIST: 不指定这个属性的话,回调函数被触发后事件会被删除
  • EV_ET: Edge-Trigger边缘触发

使用event_buffer

libevent还提供了event_buffer_系列函数。手册里的解释是:Libevent在基础的API里提供了一层抽象层,使用 buffered event ,我们无序手动处理I/O。估计是对性能的提升。

示例:

libevent_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"; 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"; //#1 下面改成了event_buffer事件,与event事件有些不同
//event_buffer_new额外支持写、错误事件
$buffer = event_buffer_new($connection, 'ev_read', 'ev_write', 'ev_error', $id);
event_buffer_base_set($buffer, $base);
//指定超时时间,单位秒
event_buffer_timeout_set($buffer, 30, 30);
//设置水位,参考:https://www.cnblogs.com/nengm1988/p/8203784.html
event_buffer_watermark_set($buffer, EV_READ, 0, 0xffffff);
//设置优先级
event_buffer_priority_set($buffer, 10);
//开启event_buffer
event_buffer_enable($buffer, EV_READ | EV_PERSIST); $master[$id] = $connection;
$receive[$id] = '';
$buffers[$id] = $buffer;
} //#2 read回调,由于使用了event_buffer,这里仅接受2个参数,分别是fd和arg
function ev_read($buffer, $id)
{
// var_dump(func_get_args());
global $receive;
global $master;
global $buffers; while( 1 ) {
//#3 使用event_buffer_read,而不是fread
$read = @event_buffer_read($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]); // fclose($master[$id]);
// event_buffer_free($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_error($buffer, $error, $id)
{
echo "ev_error - ".$error."\n";
} $base = event_base_new();
$event = event_new();
event_set($event, $socket, EV_READ | EV_PERSIST, 'ev_accept', $base);
event_base_set($event, $base);
event_add($event);
echo "start run...\n";
event_base_loop($base);

注释我都写了,相比前一个例字,主要有3个地方不同:

1、ev_accept 里设置read事件全换成了待buffer的函数;

2、ev_read 回调接收参数为2个;

3、ev_read 回调里读取消息使用 event_buffer_read,而不是fread。另外增加了ev_writeev_error回调。

定时器

libevent提供了event_timer_*系列函数,实现一次性定时器,精度微秒。

libevent_timer.php

<?php
/**
* Created by PhpStorm.
* User: 公众号: 飞鸿影的博客(fhyblog)
* Date: 2018/6/23
*/ $TIME_INTVAL = 1000000; //单位微秒 //回调函数
function ev_timer($fd, $events, $args){
// var_dump(func_get_args()); //打印结果:参数fd为NULL,参数events固定为EV_TIMEOUT常量
static $c;
$c++;
echo time()." hello\n"; event_timer_add($args[1], $args[0]);//再次添加定时器 if($c > 5){
event_timer_del($args[1]); //删除定时器
}
} $base = event_base_new();
$ev_timer = event_timer_new();
event_timer_set($ev_timer, 'ev_timer', [$TIME_INTVAL, $ev_timer]);
event_base_set($ev_timer, $base);
event_timer_add($ev_timer, $TIME_INTVAL);//单位微秒 event_base_loop($base);

上面的例子实现了每1秒执行一次回调函数。

使用event_*系列函数也可以实现:

libevent_timer2.php

<?php
/**
* Created by PhpStorm.
* User: 公众号: 飞鸿影的博客(fhyblog)
* Date: 2018/6/23
*/ $TIME_INTVAL = 1000000; function ev_timer($fd, $events, $args){
static $c;
$c++;
echo time()." hello\n"; event_timer_add($args[1], $args[0]); if($c > 5){
event_timer_del($args[1]);
}
} $base = event_base_new();
$event = event_new();
event_set($event, 0, EV_TIMEOUT, 'ev_timer', [$TIME_INTVAL, $event]);
event_base_set($event, $base);
event_add($event, $TIME_INTVAL); event_base_loop($base);

可以看出,event_timer_*系列函数是对event_*系列函数EV_TIMEOUT事件的包装。

总结

event_*系列函数基本上可以分为上面三大类。还有几个函数没有提到,大家看手册就能了解。

(未完待续)


推荐

PHP进阶之路

内容概要:从亿级 PV 项目的架构梳理,到性能提升实战,然后在更大体系的系统下,构造并使用服务治理框架。最后不要拘泥于一门语言,使用 java 快速构建一套 api 服务。包含内容:

纯干货!讲师是阿里巴巴资深研发工程师周梦康,《深入 PHP 内核》作者之一。感兴趣的朋友可以点击试看!


PHP之高性能I/O框架:Libevent(一)的更多相关文章

  1. 开源软件实践之linux高性能服务器编程框架和选型

    很多人学习编程技术一般都通过一本编程语言的入门书籍,然后尝试做一些例子和小项目.但是这些都不能让我们深入的学习很多的编程技巧和高深技术,当然这个时候很多有经验的学习人员就会告诉大家,找一个好的开源软件 ...

  2. 高性能分布式执行框架——Ray

    Ray是UC Berkeley AMP实验室新推出的高性能分布式执行框架,它使用了和传统分布式计算系统不一样的架构和对分布式计算的抽象方式,具有比Spark更优异的计算性能. Ray目前还处于实验室阶 ...

  3. 新手入门:目前为止最透彻的的Netty高性能原理和框架架构解析

    1.引言 Netty 是一个广受欢迎的异步事件驱动的Java开源网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端. 本文基于 Netty 4.1 展开介绍相关理论模型,使用场景,基本组件 ...

  4. Voovan 是一个高性能异步网络框架和 HTTP(Java)

    Voovan 是一个高性能异步网络框架和 HTTP 服务器框架,同时支持 HTTP 客户端抓取.动态编译支持.数据库访问封装以及 DateTime.String.Log.反射.对象工具.流操作.文件操 ...

  5. Netty高性能原理和框架架构解析

    1.引言 Netty 是一个广受欢迎的异步事件驱动的Java开源网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端. 本文基于 Netty 4.1 展开介绍相关理论模型,使用场景,基本组件 ...

  6. linux高性能服务器编程 (八) --高性能服务器程序框架

    第八章 高性能服务器编程框架 这一章主要介绍服务器的三个主要模块: I/O处理单元.逻辑单元.存储单元.另外服务器的模型有:C/S模型和P2P模型.虽然服务器模型比较多,但是其核心框架都一样,只是在于 ...

  7. 2018-10-29-微软-Tech-Summit-技术暨生态大会课程-·-基于-Roslyn-打造高性能预编译框架...

    title author date CreateTime categories 微软 Tech Summit 技术暨生态大会课程 · 基于 Roslyn 打造高性能预编译框架 lindexi 2018 ...

  8. 高性能环形队列框架 Disruptor 核心概念

    高性能环形队列框架 Disruptor Disruptor 是英国外汇交易公司LMAX开发的一款高吞吐低延迟内存队列框架,其充分考虑了底层CPU等运行模式来进行数据结构设计 (mechanical s ...

  9. [非专业翻译] 高性能对象映射框架 - Mapster

    [非专业翻译] 高性能对象映射框架 - Mapster 系列介绍 [非专业翻译] 是对没有中文文档进行翻译的系列博客,文章由机翻和译者自己理解构成,和原文相比有所有不通,但意思基本一致. 因个人能力有 ...

随机推荐

  1. C#-VS发布网站-摘

    在vs生成发布文件 现在已经有了网站,可以发布了.可以将网站发布到您可以使用 Visual Studio 支持的任何连接协议访问的任何位置.复制网站有下面几种方式可选: 复制到本地计算机上的文件夹. ...

  2. 创建私有maven服务器

    私服的创建 1.下载nexus服务  nexus-2.12.0-01-bundle https://pan.baidu.com/s/1o8OfieI 2.下载maven工具   apache-mave ...

  3. openresty + lua 3、openresty http 调用

    http 的话,openresty 已经集成,ng 的话,自己引入即可. github 地址:https://github.com/pintsized/lua-resty-http github 里提 ...

  4. 关于java弱引用

    最近在学java虚拟机相关的东西,看了一篇微信的推送  在此分享https://mp.weixin.qq.com/s/oLhZWWWIVc90cNUBukkqHw 一.强引用 强引用就是我们经常使用的 ...

  5. jQuery插件初级练习3

    <!DOCTYPE html><html> <head> <meta charset="UTF-8"> <title>& ...

  6. hive的 order by & distribute by & cluter by

    我们应该都清楚order by 的含义: 根据某个字段对输出的数据排序,因为只有一个reducer,所以查询效率较慢. 那么hive中,另外两个排序,distribute by和cluster by的 ...

  7. chrome常用小插件

    1.广告终结者                    (去广告) 2.adsafe2.0.1                  (去广告) 3.Infinity New Tab           ( ...

  8. 中美会计准则差异比较(PRC GAAP VS US GAAP)

    http://bbs.chinaacc.com/forum-2-43/topic-2023118.html 一.中美会计准则的实质性差别    rule based vs principle base ...

  9. ue4开发入门教程

    ue4是一套开源跨平台的游戏引擎,游戏画质3A水准,具有强大的材质编辑器,各种插件齐全.想要学习ue4的,可以参考这篇文章作为入门. 学习这篇文章,建议具有一定的C++基础,对游戏有一定了解. 1.首 ...

  10. bootstrap基础学习(四)——网格系统(列的偏移、排序、嵌套)

    网格系统——列偏移.列排序.列嵌套 列偏移:有的时候,我们不希望相邻的两个列紧靠在一起,但又不想使用margin或者其他的技术手段来.这个时候就可以使用列偏移(offset)功能来实现.使用列偏移也非 ...