乐趣区

走在Swoole学习的道路上一次解耦历程

概述

看标题也不知道作者想要说些什么,最近在看 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']);
    }

谢谢观赏

谢谢大家耐心观看,希望对您有所帮助,也希望大家提供下不同的意见,找到更有效的方式来完成,共同学习,谢谢!

退出移动版