概述
看标题也不知道作者想要说些什么,最近在看 Swoole 方面的内容,在封装框架时遇到了一个关于解耦的问题,解耦大家并不陌生,这次的解耦是关于监听事件和心跳检测的一个 demo,直接来看下问题吧。
解决思路
在 Swoole 启动时我想加入一些事件监听,比如 RPC 中的注册中心,我启动、提供了哪些服务类似的场景等等,但是这只是一种事件监听,如果要多个呢?比如当我接收到请求之后、收到消息之后、连接关闭之后等等,你需要创建很多的时间监听,这个时候你可能在启动时候把所有事件都要注册进去,接下来看下代码是如何简单粗暴的实现的。
<?php
namespace LoyaltyLu\Core;
use LoyaltyLu\Core\Event\Event;
use Swoole\Http\Server;
class Http
{
...
public function start()
{$reload = Reload::get_instance();
$reload->watch = [CONFIG_PATH, FRAME_PATH, APP_PATH];
$reload->md5Flag = $reload->getMd5();
#主动收集已有事件收集 Listener 目录
$this->collecEvent();
// 定时器
swoole_timer_tick(3000, function () use ($reload) {if ($reload->reload()) {$this->server->reload(); // 重启
}
});
Event::trigger('start', ['sss']);
}
/**
* 收集事件
*/
public function collecEvent()
{$files = glob(EVENT_PATH . "/*.php");
if (!empty($files)) {foreach ($files as $dir => $fileName) {include "{$fileName}";
$fileName = explode('/', $fileName);
$className = explode('.', end($fileName))[0];
$nameSpace = 'App\\Listener\\' . $className;# 可放入配置文件
if (class_exists($nameSpace)) {
$obj = new $nameSpace;
#得到自己定义的事件名称,利用反射读取类文档注释
$re = new \ReflectionClass($obj);
if (strlen($re->getDocComment()) < 2) {throw new \Exception('没有按照规范定义事件名称');
} else {preg_match("/@Listener\((.*)\)/i", $re->getDocComment(), $eventName);
if (empty($eventName)) {throw new \Exception('没有按照规范定义事件名称');
}
#注册的事件
Event::register($eventName[1], [$obj, 'handle']);
}
}
}
}
}
...
}
- 当启动 Swoole 时调用 collecEvent 方法
- collecEvent 负责收集,注册指定目录、命名空间下的方法
EVENT_PATH = APP_PATH.'/listener'
- 循环遍历目录下所有文件
- 获取文件名称
- 拼接命名空间(命名空间可以放置到 Config 等配置文件当中)
-
class_exists
检查类是否存在 -
ReflectionClass
这里用到了反射、注解的形式判断是否符合 -
getDocComment()
获取注释内容 - 正则
/@Listener\((.*)\)/i
匹配出需要监听的所属事件 - 执行
Event::register($event,$callback)
进行事件注册
接下来看下 Event
类:
class Event
{public static $events = [];
// 事件注册
/**
* @param $event 事件名
* @param $callback 事件回调
*/
public static function register($event, $callback)
{$event = strtolower($event);// 不区分大小写
if (!isset(self::$events[$event])) {self::$events[$event] = [];}
self::$events[$event] = ['callback' => $callback];
}
// 事件触发
public static function trigger($event, $params = [])
{$event = strtolower($event);// 不区分大小写
if (isset(self::$events[$event])) {call_user_func(self::$events[$event]['callback'], $params);
return true;
}
return false;
}
}
-
Event
当中包含 2 个静态方法:事件注册、事件触发 - 事件注册执行的是把说有的事件放入 $event 属性当中
- 事件触发:验证事件是否存在、
call_user_func()
执行回调函数
再看下如何声明一个事件的文件:
<?php
namespace App\Listener;
/**
* Class StartListener
* @package App\Listener
* @Listener(start)
*/
class StartListener
{public function handle($params)
{go(function () {// 创建携程环境
// 升级的 websockt
$cli = new \Swoole\Coroutine\Http\Client('127.0.0.1', 9600);
$ret = $cli->upgrade('/');
if ($ret) {// Config::get()
$data = [
'method' => 'register',
'serviceName' => "Server",
'ip' => '0.0.0.0',
'port' => 9800,
];
$cli->push(json_encode($data));
// 心跳处理
swoole_timer_tick(3000, function () use ($cli) {if ($cli->errCode == 0) {$cli->push('11', WEBSOCKET_OPCODE_PING);
}
});
}
});
}
}
- 应该这里继承一个接口类,必须实现
handle()
方法,大家可以自己实现下 -
handle()
方法这里实现了一个类似通知注册中心注册服务的功能 -
swoole_timer_tick()
利用毫秒级定时器处理心跳 - 文档注释,提供给反射抓取
/**
* Class StartListener
* @package App\Listener
* @Listener(start)
*/
调用
public function start()
{
...
#主动收集已有事件收集 Listener 目录
$this->collecEvent();
...
Event::trigger('start', ['sss']);
}
谢谢观赏
谢谢大家耐心观看,希望对您有所帮助,也希望大家提供下不同的意见,找到更有效的方式来完成,共同学习,谢谢!