基于swoole打造laravel的rpc框架

发布于:2022-07-26 ⋅ 阅读:(314) ⋅ 点赞:(0)

基于swoole打造laravel的rpc框架

简介

为了深入了解swoole是如何对框架进行加速与提供php服务的功能,以及了解rpc微服务的原理,在基于composer的 psr-4 规则下,搭建一套简单的类似 laravel 的框架,起名为:swostar,swostar框架中以 IOC 容器与Provider为核心,对所有的服务进行单利绑定(bind)与服务解析(make),Provider加载要提供的服务。

其中提供的主要核心功能有:路由解析,控制器的加载,event事件注册,rpc服务提供,consul服务提供

目录结构如下:

├─app
├─├─Provider ----- 服务提供者
├─├─Listener ----- event事件监听
├─├─Http --------- 框架控制器层
├─├─RPC ---------- rpc客户端与服务端
├─bin ------------ 框架启动入口
├─config --------- 框架配置
├─route ---------- 路由定义
├─vendor
├─├─core
├─├─├─swostar-frame --------- 框架核心代码
├─├─├─├─src
├─├─├─├─├─Config--------- 用于加载app/config的配置文件
├─├─├─├─├─Consul--------- consul应用服务
├─├─├─├─├─Container------ IOC容器的绑定与解析
├─├─├─├─├─Event---------- event事件加载
├─├─├─├─├─Foundation----- 框架应用的核心入口,加载框架与驱动
├─├─├─├─├─Routes--------- 路由模块
├─├─├─├─├─RPC------------ rpc客户端与服务端
├─├─├─├─├─Server--------- 提供swoole的http与tcp服务

获取源码:

git clone git@gitee.com:dear-q/swostar.git

框架结构图:

在这里插入图片描述


项目搭建:

1 . 使用composer生成vender

1 . 1创建项目 ,当前的项目为goods, 新建两个文件夹

#当前的项目
mkdir goods

#用于composer 生成 vendor
mkdir swostar

1 . 2 分别在两个目录中进行composer初始化

composer init

执行后输入:core/swostar

在这里插入图片描述

1 . 3 .运行创建后,分别更改内容更改如下

vim goods/composer.json
{
    "name": "core/swostar",
    "authors": [
        {
            "name": "Dear",
            "email": "15626109675@163.com"
        }
    ],
    "require": {
        "core/swostar-frame": "master-dev"
    },
    "repositories": {
        "Dear": {
            "type": "path",
            "url": "../swostar"
        }
    },
    "autoload": {
        "psr-4": {
            "App\\":"app/"
        }
    }
}
vim swostar/composer.json
{
    "name": "core/swostar-frame",
    "authors": [
        {
            "name": "Dear",
            "email": "15626109675@163.com"
        }
    ],
    "require": {},
    "autoload": {
        "psr-4": {
            "SwoStar\\":"./src/"
        },
        "files" : [
            "src/Supper/Helper.php"
        ]
    }
}

1 . 4 . 回到goods中加载composer生成vendor,composer会根据 composer.json中的repositories定义的 url 把swostar的文件加载到vendor中,同时会生成composer的配置信息

composer require core/swostar-frame:master-dev

在这里插入图片描述

在这里插入图片描述

composer加载好后,就可以再vendor中开始编写加载框架的代码了


2 . 创建框架入口,加载app/config与驱动框架

2 . 1 启动框架 /bin/swostar

<?php
//加载vendor
require dirname(__DIR__).'/vendor/autoload.php';

//运行框架
(new \SwoStar\Foundation\Application(dirname(__DIR__)))->run($argv);

2 . 2 IOC容器注册与解析

IOC的原理其实就是把创建好的对象,存储进数组当中,当解析的时候,通过标识从数组中获取到对象返回去而已,从而减少对象的创建而带来的消耗

SwoStar/Container/Container.php

<?php
namespace SwoStar\Container;

use Closure;
use Exception;

/**
 * 容器
 */
class Container
{
    // 0. 单例
    protected static $instance;
    
    // 1. 容器,用于记录创建好的对象信息或者数据
    protected $bindings = [];

    // 1. 容器,用于记录创建好的对象信息或者数据
    protected $instances = [];

    /**
     * 容器绑定的方法 : 就是把一个对象存储进数组中
     * @param  string $abstract 标识
     * @param  object $object   实例对象或者闭包
     */
    public function bind($abstract, $object)
    {
        // 标识要绑定
        // 1. 就是一个对象
        // 2. 闭包的方式
        // 3. 类对象的字符串 (类的地址)
        $this->bindings[$abstract] = $object;
    }

    /**
     * 从容器中解析实例对象或者闭包
     * @param  string $abstract   标识
     * @param  array  $parameters 传递的参数
     * @return object             是一个闭包或者对象
     */
    public function make($abstract, $parameters = [])
    {
        //如果已经记录了对象,则返回对象
        //参数如:config
        if (isset($this->instances[$abstract])) {
            return $this->instances[$abstract];
        }

        //传递的是命名空间,则创建对象
        //参数如:SwoStar\Consul\Response
        if (!$this->has($abstract)) {

            // 如果不存在自行
            // 选择返回, 可以抛出一个异常
            // throw new Exception('没有找到这个容器对象'.$abstract, 500);

            $object = $abstract;
        } else {
            //获取bind()中绑定好的对象或者属性信息
            $object = $this->bindings[$abstract];
        }

        // 在这个容器中是否存在
        // 1. 判断是否一个为对象
        // 2. 闭包的方式
        if ($object instanceof Closure) {
            return $object();
        }

        // 3. 类对象的字符串 (类的地址)
        //创建对象并且记录数组中
        return $this->instances[$abstract] = (is_object($object)) ? $object :  new $object(...$parameters) ;
    }

    /**
     * 判断是否在容器中
     * 容器很多便于扩展
     * 可能在其他场景中会用到
     * @param $abstract
     * @return bool
     */
    public function has($abstract)
    {
        return isset($this->bindings[$abstract]);
    }

    /**
     * 单例创建
     * @return mixed
     */
    public static function getInstance()
    {
        if (is_null(static::$instance)) {
            static::$instance = new static;
        }

        return static::$instance;
    }

    /**
     * @param null $container
     * @return null
     */
    public static function setInstance($container = null)
    {
        return static::$instance = $container;
    }
}


2 . 3 框架应用的核心入口,加载框架与驱动

2 . 3 . 1

SwoStar/Foundation/Application.php : 加载应用框架,加载服务提供者,容器绑定

  • 主要是加载 app/config的配置文件,通过app.php提供的服务提供者进行服务应用加载
  • 启动http服务
  • 提供tcp服务
<?php
namespace SwoStar\Foundation;

use SwoStar\Console\Input;
use SwoStar\Container\Container;
use SwoStar\Rpc\RpcServer;

use SwoStar\Server\Http\Server as HttpServer;

class Application extends Container
{
    protected const SWOSTAR_WELCOME = "
      _____                     _____     ___
     /  __/             ____   /  __/  __/  /__   ___ __      __  __
     \__ \  | | /| / / / __ \  \__ \  /_   ___/  /  _`  |    / /_/ /
     __/ /  | |/ |/ / / /_/ /  __/ /   /  /_    |  (_|  |   /  ___/
    /___/   |__/\__/  \____/  /___/    \___/     \___/\_|  /__/
    ";

    protected $basePath = "";

    /**
     * 定义要加载的驱动
     * @var array
     */
    protected $bootstraps = [
        //加载config目录下的所有配置信息
        Bootstrap\LoadConfiguration::class,

        //加载驱动,加载服务提供者
        Bootstrap\ServerPrivoder::class
    ];

    /**
     * 在启动http服务之前,先加载框架,加载驱动
     * 注意:这里并没有加载到onworkstart函数中,因为在启动http服务之前加载框架
     * 也相当于加载到了常驻内存中,不过还是有些区别,也可以自行加载到workstart中
     * Application constructor.
     * @param $path    .值为:/docker/www/work/18/activities
     */
    function __construct($path)
    {
        if (!empty($path)) {
            $this->setBasePath($path);
        }

        //设置单利
        self::setInstance($this);

        // 加载框架驱动
        $this->bootstrap();

        Input::info(self::SWOSTAR_WELCOME, "启动项目");
    }

    /**
     * 加载框架驱动
     */
    public function bootstrap()
    {
        foreach ($this->bootstraps as $key => $bootstrap) {
            (new $bootstrap())->bootstrap($this);
        }
    }

    /**
     * 运行框架,启动http服务
     * @param $argv
     * @throws \Exception
     */
    public function run($argv)
    {
        $server = null;

        switch ($argv[1]) {
            case 'http:start':
                $server = new HttpServer($this);
                break;

            default:
                break;
        }

        // 判断是否开启rpc
        if ($this->make("config")->get("server.rpc.flag")) {
            new RpcServer($this,$server);
        }

        // 就是启动swostar
        $server->start();
    }

    /**
     * 获取当前服务器的config文件夹所在的完整配置路径
     * 返回数据:/docker/www/work/18/activities/config
     * @return string
     */
    public function getConfigPath()
    {

        return $this->getBasePath()."/config";
    }

    /**
     * 获取当前服务器中项目文件夹所在的完整配置路径
     * 返回数据:/docker/www/work/18/activities
     * @return string
     */
    public function getBasePath()
    {
        return $this->basePath;
    }

    /**
     * 设置当前服务器中项目文件夹所在的完整配置路径
     * 存储的数据: /docker/www/work/18/activities
     * @param $path
     */
    public function setBasePath($path)
    {
        $this->basePath = \rtrim($path, '\/');
    }

}
2 . 3 . 2 (核)

SwoStar/Foundation/Bootstrap/LoadConfiguration.php : 加载app/config目录下的所有配置,并转换成数组绑定到容器中

<?php
namespace SwoStar\Foundation\Bootstrap;

use SwoStar\Config\Config;
use SwoStar\Foundation\Application;


class LoadConfiguration
{
    /**
     * 绑定容器为config,用于加载config配置文件中定义的配置信息
     * @param Application $app
     */
    public function bootstrap(Application $app)
    {
        $app->bind('config', new Config($app->getConfigPath()));
    }
}

2 . 3 . 3

SwoStar/Foundation/config/config.php : 具体的操作类,进行绑定与获取config的配置信息

<?php

namespace SwoStar\Config;

class Config
{

    protected $itmes = [];

    function __construct($path)
    {
        // 读取配置
        $this->itmes = $this->phpParser($path);
    }

    /**
     * 读取PHP文件类型的配置文件
     * $path路径为当前服务器的config文件位置: /docker/www/work/18/activities/config
     * @param $path
     * @return null
     */
    protected function phpParser($path)
    {
        // 1 . 找到当前config目录中下的所有配置文件
        // 此处跳过多级的情况
        $files = scandir($path);

        $data = null;

        // 2. 读取文件信息
        foreach ($files as $key => $file) {
            // 2 . 1 过滤操作
            if ($file === '.' || $file === '..') {
                continue;
            }

            // 2.2 获取文件名
            $filename = \stristr($file, ".php", true);

            // 2.3 拼接路径,加载文件信息到框架中
            //路径 :  /docker/www/work/18/activities/config/service.php
            $data[$filename] = include $path."/".$file;
        }

        // 3. 返回
        return $data;
    }

    /**
     * 解析配置文件的配置信息
     * key.key2.key3
     * @param $keys
     * @return array|mixed|null
     */
    public function get($keys)
    {
        $data = $this->itmes;

        foreach (\explode('.', $keys) as $key => $value) {
            $data = $data[$value];
        }

        return $data;
    }
}
2 . 3 . 4 (核)

SwoStar/Foundation/Bootstrap/ServerPrivoder.php : 对app/config/app.php中定义的服务提供者(priovders)进行注册与驱动

<?php
namespace SwoStar\Foundation\Bootstrap;

use SwoStar\Foundation\Application;


class ServerPrivoder
{

    /**
     * 加载驱动,加载服务提供者
     * @param Application $app
     */
    public function bootstrap(Application $app)
    {
        //获取服务提供者的配置信息
        $priovders = $app->make('config')->get('app.priovders');

        // 先是 register,后boot
        foreach ($priovders as $key => $priovder) {
            $prio = new $priovder($app);
            //注册
            $prio->register();
            //驱动
            $prio->boot();
        }
    }
}


2 . 4 加载路由,解析控制器

对config/app.php中的RouteServerProvider进行路由解析,并把route文件加载进来

2 . 4 . 1

App/Providers/RouteServerProvider.php : 定义路由文件,控制器Controller的工作命名空间

<?php
namespace App\Providers;

use SwoStar\Routes\RouteServerProvider as ServerProvider;

class RouteServerProvider extends ServerProvider
{

    public function boot()
    {
        $this->mapRouteHttp();

        //路由服务提供者,绑定路由的加载与解析
        parent::boot();
    }

    /**
     * http服务的工作路径配置
     */
    public function mapRouteHttp()
    {
        $this->map['http'] = [
            //工作命名空间
            'namespace' => 'App\Http\Controller',
            //路由文件
            'path' => $this->app->getBasePath()."/route/http.php",
            //标签
            'flag' => 'http'
        ];
    }

    /**
     * websocket服务的工作路径配置
     */
    public function mapRouteWS()
    {
        $this->map['websocket'] = [
            'namespace' => 'App\WebSocket\Controller',
            'path' => $this->app->getBasePath()."/route/websocket.php",
            'flag' => 'websocket'
        ];
    }
}

2 . 4 . 2

SwoStar/Route/ServerPriovder.php : 路由服务提供者,绑定路由的加载与解析 , 绑定在项目启动的时候绑定,解析则有swoole中请求http的 onRequest 里解析

<?php
namespace SwoStar\Routes;

use SwoStar\Supper\ServerPriovder;

/**
 * 路由服务提供者,绑定路由的加载与解析
 */
class RouteServerProvider extends ServerPriovder
{
    // 记录路由文件,以及路由信息
    protected $map;

    public function boot()
    {
        //注册路由,并且绑定路由对象到数组中
        $this->app->bind('route', Route::getInstance()->registerRoute($this->map));
    }
}

2 . 4 . 3 (核)

SwoStar/Route/Route.php : 这里是具体的进行route操作的类,注册与解析路由的具体方法

  • registerRoute方法:先加载路由文件,才行执行后面两个方法

  • addRoute方法:根据在路由文件中添加的路由,加载到控制器中的具体方法,并且绑定到内存中

  • match方法:路由请求解析与校验的方法 , 在swoole中的 onRequest 中进行解析调用

<?php
namespace SwoStar\Routes;

class Route
{
    protected static $instance = null;

    // 路由本质实现是会有一个容器在存储解析之后的路由
    protected $routes = [];

    // 定义了访问的类型
    protected $verbs = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'];

    // 区分服务类型
    protected $flag;


    protected $namespace;

    protected function __construct( )
    {

    }

    public static function getInstance()
    {
        if (\is_null(self::$instance)) {
            self::$instance = new static();
        }
        return self::$instance ;
    }

    public function get($uri, $action)
    {
        return $this->addRoute(['GET'], $uri, $action);
    }

    public function post($uri, $action)
    {
        return $this->addRoute(['POST'], $uri, $action);
    }

    public function any($uri, $action)
    {
        return $this->addRoute(self::$verbs, $uri, $action);
    }

    /**
     * 注册路由,根据在路由文件中添加的路由,加载到控制器中的具体方法,并且绑定到内存中
     * @param $methods
     * @param $uri
     * @param $action
     * @return $this
     */
    protected function addRoute($methods, $uri, $action)
    {
        foreach ($methods as $method ) {
            if ($action instanceof \Closure) {
                $this->routes[$this->flag][$method][$uri] = $action;
            } else {
                $this->routes[$this->flag][$method][$uri] = $this->namespace."\\".$action;
            }
        }
        return $this;
    }

    /**
     * 路由请求解析与校验的方法 , 在swoole中的 onRequest 中进行解析调用
     * @param $pathinfo
     * @param $flag
     * @param $method
     * @return mixed|string
     */
    public function match($pathinfo, $flag, $method)
    {
        $action = null;
        // 根据传递服务标识,请求类型查找route
        foreach ($this->routes[$flag][$method] as $uri => $value) {
            // 保持route标识与pathinfo一致性
            $uri = ($uri && \substr($uri, 0, 1) != '/') ? "/".$uri : $uri;

            if ($pathinfo === $uri) {
                $action = $value;
                break;
            }
        }
        // 判断是否查找到route
        if (!empty($action)) {
            // 执行方法操作
            return $this->runAction($action);
        }
        dd($action, "没有查找到方法");
        return "404";
    }

    /**
     * 这是运行实际方法
     * @param $action
     * @return mixed
     */
    public function runAction($action)
    {
        if ($action instanceof \Closure) {
            return $action();
        } else {
            $arr = \explode("@", $action);
            $class = new $arr[0]();
            return $class->{$arr[1]}();
        }
    }

    /**
     * 注册路由,加载路由文件
     * @param array $map
     * @return $this
     */
    public function registerRoute(Array $map)
    {
        foreach ($map as $key => $route) {
            $this->flag = $key;
            $this->namespace = $route['namespace'];

            // 根据route文件引入执行
            require_once $route['path'];
        }
        return $this;
    }

    public function getRoutes()
    {
        return $this->routes;
    }
}


2 . 5 event事件驱动

event事件,用于加载定义的函数 , 可用于框架初始化时要加载的配置,如加载consul 的配置信息

2 . 5 . 1

config/app.php中加载RouteServerProvider, 服务提供者启动后,EventServerProvider.php服务提供者会通过 app/event.php中定义的配置,把当定义的文件夹加载进框架中

SwoStar/Event/EventServerProvider.php : 加载event事件与绑定,解析

<?php
namespace SwoStar\Event;

use SwoStar\Config\Config;

use SwoStar\Supper\ServerPriovder;


class EventServerProvider extends ServerPriovder
{

    public function boot()
    {
        $event = new Event();

        $config = $this->app->make('config');

        // 根据app/config/event.php中Listeners配置加载事件
        $this->registeListenenrs($event, $config);

        // 根据app/config/event.php中event配置加载
        $this->registeEvents($event, $config);

        $this->app->bind('event', $event);

        // var_dump($this->app->make('event')->getEvents());
    }

    /**
     * 根据app/config/event.php中Listeners配置加载
     * @param Event $event
     * @param Config $config
     */
    public function registeListenenrs(Event $event, Config $config)
    {
        $listeners = $config->get('event.listeners');

        foreach ($listeners as $key => $listener) {
            // 根据指定的地址查询下面的PHP文件
            $files = scandir($this->app->getBasePath().$listener['path']);

            $data = null;

            // 2. 读取文件信息
            foreach ($files as $key => $file) {
                if ($file === '.' || $file === '..') {
                    continue;
                }

                 // 创建对象
                $class = $listener['namespace'].explode(".",$file)[0];

                if (class_exists($class)) {
                    $listener = new $class($this->app);

                    $event->register($listener->getName(), [$listener, 'handler']);
                }
            }

        }
    }

    /**
     * 根据app/config/event.php中event配置加载
     * @param Event $event
     * @param Config $config
     */
    public function registeEvents(Event $event, Config $config)
    {
        $events = $config->get('event.events');

        foreach ($events as $key => $class) {
           if (class_exists($class)) {

               $listener = new $class($this->app);

               $event->register($listener->getName(), [$listener, 'handler']);
           }
        }
    }
}

2 . 5 . 2

SwoStar/Event/Event.php : 具体的注册与解析类

<?php
namespace SwoStar\Event;


class Event
{
    // 存储所有定义的事件
    protected $events = [];

    /**
     * 注册事件方法
     * @param $flag
     * @param $callback
     */
    public function register($flag, $callback)
    {
        // 统一标识 大小写
        $flag = \strtolower($flag);
        // 注册事件
        $this->events[$flag] = [
            'callback' => $callback,
        ];
    }

    /**
     * 触发事件
     * @param $flag .事件标识
     * @param array $params .给事件传递的参数
     * @return bool
     */
    public function trigger($flag, $params = [])
    {
        // 统一标识 大小写
        $flag = \strtolower($flag);

        // 判断是否存在事件
        if (isset($this->events[$flag])) {
            ($this->events[$flag]['callback'])(...$params);

            return true;
        }
    }

    /**
     * 触发容器
     * @return array
     */
    public function getEvents()
    {
        return $this->events;
    }
}


2 . 6 加载RPC

rpc的服务提供者,用于提供rpc中需要的tcp-service服务与client客户端

2 . 6 . 1

config/app.php 中,加载rpc的服务提供,从而驱动rpc的服务

App/Providers/RpcServerPriovder.php : 设置rpc的驱动方式,可以是config配置,这里使用consul来驱动

<?php
namespace App\Providers;

use SwoStar\Rpc\RpcServerPriovder as ServerPriovder;

/**
 *
 */
class RpcServerPriovder extends ServerPriovder
{
    protected $services;

    /**
     * 加载rpc配置信息
     */
    protected function provider()
    {
        //这里设置了获取consul的配置,是个闭包函数,底层调用判断到$this->services是个闭包函数,则会进行实例化
        $this->services = function($sname){
            $services = $this->app->make('consul-agent')->health($sname)->getResult();
            $address= [];

            foreach ($services as $key => $value) {
                $address[] = [
                    "host" => $value["Service"]["Address"],
                    "port" => $value["Service"]["Port"]
                ];
            }

            return $address;
        };
    }

    public function boot()
    {
        parent::boot();
    }
}

2 . 6 . 2

SwoStar/Rpc/RpcServerPriovder.php : 进行rpc服务配置信息的绑定,如rpc的服务名称,IP,端口

<?php
namespace SwoStar\Rpc;

use SwoStar\Supper\ServerPriovder;

/**
 *
 */
class RpcServerPriovder extends ServerPriovder
{

    protected $services;

    public function boot()
    {
        $this->provider();

        $this->app->bind('rpc-proxy', new Proxy($this->services));
    }

    protected function provider()
    {

    }
}

2 . 6 . 3

SwoStar/Rpc/Proxy.php : 根据配置的属性,返回配置的数据类型,如是返回config的配置还是返回consul的配置

<?php
namespace SwoStar\Rpc;

class Proxy
{
    // 用于存储源 -- 服务源地址
    protected $services;

    public function __construct($services = null)
    {
        $this->services = $services;
    }

    /**
     * 获取配置信息
     * @param string $sname
     * @return mixed
     */
    public function getService($sname = '')
    {
        $services = $this->services($sname);

        return $services[\array_rand($services, 1)];
    }

    /**
     * 获取服务信息
     * @param string $sname
     * @return array|mixed|null
     */
    public function services($sname = '')
    {
        //如果为空则获取配置文件的配置信息
        if (empty($this->services)) {
            return app('config')->get('service.'.$sname);
        }

        //这里是数组,则表示是获取配置文件中的配置信息
        if (is_array($this->services)) {
            return $this->services;
        }

        //如果调用的是consul,则为对象,则获取对象的数据
        if ($this->services instanceof \Closure) {
            return ($this->services)($sname);
        }

    }
}

2 . 6 . 4

SwoStar/Rpc/RpcClient.php : 用于调用其他服务的rpc客户端,这里的配置信息会从上面的容器中获取,请求得到相应并返回

<?php
namespace SwoStar\Rpc;

use \Swoole\Coroutine\Client;
/**
 *
 */
class RpcClient
{
    protected $classType ;

    protected $service ;

    // 通过它来调用指定的rpc服务端
    protected function proxy($method, $params)
    {
        /*
        json {
          "method" : class::method,
          "params" : 参数
        }
         */
        $data = [
            'method' => $this->classType."::".$method,
            'params' => $params
        ];

        // 获取服务的配置信息
       
        $service = app('rpc-proxy')->getService($this->service);

        // 发送
        return $this->send($service['host'], $service['port'], $data);
    }

    /**
     * @param $host
     * @param $port
     * @param $data
     * @return mixed
     * @throws \Exception
     */
    public function send($host, $port, $data)
    {
        $client = new Client(SWOOLE_SOCK_TCP);
        if (!$client->connect($host, $port, 0.5))
        {
            throw new \Exception("连接不上服务", 500);
        }
        $client->send(\json_encode($data));
        $ret = $client->recv(4);
        $client->close();
        return $ret;
    }

    public function __call($method, $args)
    {
        return $this->proxy($method, $args);
    }
    public static function __callStatic($method, $args)
    {
        return self::proxy($method, $args);
    }
}

2 . 6 . 5

SwoStar/Rpc/RpcServer.php : 提供rpc的swoole-server服务,用于接收client端投递过来的数据,解析路由,执行路由绑定的控制器,并返回响应给到client端,这里有启动框架的时候,由Application.php中启动

<?php
namespace SwoStar\Rpc;

use SwoStar\Foundation\Application;
use SwoStar\Message\Response;

use SwoStar\Server\ServerBase;

class RpcServer
{
    /**
     * [protected description]
     * @var Application
     */
    protected $app;

    protected $config;
    /**
     * [protected description]
     * @var ServerBase
     */
    protected $server;

    protected $listen;

    function __construct(Application $app, ServerBase $server)
    {
        $this->app = $app;
        $this->server = $server;
        $this->config = $this->app->make("config");

        // 获取创建的swoole服务对象 调用多端口listen
        $this->listen = $server->getServer()->listen($this->config->get("server.rpc.host"),$this->config->get("server.rpc.port"),$this->config->get("server.rpc.type"));

        // 注册事件
        $this->listen->on('receive', [$this, 'receive']);
        $this->listen->on('connect', [$this, 'connect']);
        $this->listen->on('close', [$this, 'close']);

        // 一定要做的事情设置,swoole的配置为空或者覆盖
        $this->listen->set($this->config->get("server.rpc.swoole"));


        dd('tcp:\\\\'.$this->config->get("server.rpc.host").":".$this->config->get("server.rpc.port"), '启动rpc服务');
    }

    public function receive($ser, $fd, $from_id, $data)
    {

        /*
        json {
          "method" : class::method,
          "params" : 参数
        }
         */
        $oper = \json_decode($data, true);

        // 执行对象
        $class = explode("::", $oper['method'])[0];
        $class = new $class();
        // 得到执行的方法
        $method = explode("::", $oper['method'])[1];
        // 执行
        $ret = $class->{$method}(...$oper['params']);
        // 返回结果
        $ser->send($fd, Response::send($ret));

        dd('receive'.$fd, 'rpc');
    }

    public function connect($ser, $fd)
    {
        dd('connect'.$fd, 'rpc');
    }

    public function close($ser, $fd)
    {
        dd('close'.$fd, 'rpc');
    }
}


2 . 7 swoole - tcp与http的请求响应处理

统一处理tcp与http请求的Request与Response 注: 这里没进行粘包处理

2 . 7 . 1

SwoStar/Message/Http/Request.php : 请求数据处理

<?php
namespace SwoStar\Message\Http;

use Swoole\Http\Request as SwooleRequest;

class Request
{
    // 记录请求方式是get还是 port
    protected $method;

    // 记录后续请求地址 / /dd /index/dd
    protected $uriPath;

    protected $swooleRequest;

    public function getMethod()
    {
        return $this->method;
    }

    public function getUriPath()
    {
        return $this->uriPath;
    }
    /**
     * [init description]
     * @param  SwooleRequest $request [description]
     * @return Request                 [description]
     */
    public static function init(SwooleRequest $request)
    {
        $self = new static;

        $self->swooleRequest = $request;
        $self->server = $request->server;

        $self->method = $request->server['request_method'] ?? '';
        $self->uriPath = $request->server['request_uri'] ?? '';
        return $self;
    }
}

2 . 7 . 2

SwoStar/Message/Response.php : 返回响应处理,如果需要做粘包处理则在此处解决

<?php
namespace SwoStar\Message;

/**
 *
 */
class Response
{
    public static function send($message)
    {
        if (\is_array($message)) {
            return json_encode($message);
        } else if (\is_string($message)) {
            return $message;
        } else {
            return $message;
        }
    }
}


2 . 8 consul服务加载

创建consul的服务,对接consul,在框架启动的时候加载配置信息注册到consul,rpc服务调用的时候获取consul存储的配置信息,使用consul的好处是consul可以监听服务,返回存活状态的服务,如果不考虑心跳,可以使用redis或者config配置来存储配置信息

config/app.php 中加载ConsulServerPriovder,进行consul驱动绑定,并在event事件中进去服务注册

2 . 8 . 1

SwoStar/Supper/ServerPriovder/ConsulServerPriovder.php : 进行consul的请求类的绑定

<?php
namespace SwoStar\Consul;

use SwoStar\Supper\ServerPriovder;

/**
 *
 */
class ConsulServerPriovder extends ServerPriovder
{

    /**
     * 加载consul驱动
     */
    public function boot()
    {
        $config = $this->app->make("config");

        $this->app->bind("consul-agent",
            new Agent(
                new Consul($config->get('consul.app.host'), $config->get('consul.app.port'))
            )
        );
    }
}

2 . 8 . 2

SwoStar/Consul/Consul.php 基于携程的swoole-http请求

<?php
namespace SwoStar\Consul;

use Swoole\Coroutine\Http\Client;

class Consul
{
    protected $host;

    protected $port;

    public function __construct($host , $port)
    {
        $this->host = $host;
        $this->port = $port;
    }


    public function get(string $url = null, array $options = [])
    {
        return $this->request('GET', $url, $options);
    }

    public function delete(string $url, array $options = [])
    {
        return $this->request('DELETE', $url, $options);
    }

    public function put(string $url, array $options = [])
    {
        return $this->request('PUT', $url, $options);
    }

    public function patch(string $url, array $options = [])
    {
        return $this->request('PATCH', $url, $options);
    }

    public function post(string $url, array $options = [])
    {
        return $this->request('POST', $url, $options);
    }

    public function options(string $url, array $options = [])
    {
        return $this->request('OPTIONS', $url, $options);
    }

    /**
     * 携程执行请求
     * @param $method
     * @param $uri
     * @param $options
     * @return Response
     */
    private function request($method, $uri, $options)
    {
        $client = new Client($this->host, $this->port);

        // get , DELETE
        $client->setMethod($method);

        if (!empty($options)) {
            $client->setData(json_encode($options['body']));
        }

        $client->execute($uri);

        $headers    = $client->headers;
        $statusCode = $client->statusCode;
        $body       = $client->body;

        $client->close();

        return Response::new($headers, $body, $statusCode);
    }
}

2 . 8 . 3

SwoStar/Consul/Response.php 请求与响应

<?php


namespace SwoStar\Consul;

class Response
{
    /**
     * @var array
     */
    private $headers;

    /**
     * @var string
     */
    private $body;

    /**
     * @var int
     */
    private $status;

    /**
     * @param array  $headers
     * @param string $body
     * @param  $status
     * @return Response
     */
    public static function new(array $headers, string $body, $status = 200): self
    {
        $self = app(Response::class);

        $self->body    = $body;
        $self->status  = $status;
        $self->headers = $headers;

        return $self;
    }

    /**
     * @return array
     */
    public function getHeaders(): array
    {
        return $this->headers;
    }

    /**
     * @return string
     */
    public function getBody(): string
    {
        return $this->body;
    }

    /**
     * @return int
     */
    public function getStatusCode(): int
    {
        return $this->status;
    }

    /**
     * @return array|mixed
     */
    public function getResult()
    {
        if (empty($this->body)) {
            return $this->body;
        }

        return \json_decode($this->body, true);
    }
}

2 . 8 . 4

SwoStar/Consul/Agent.php 请求consul

<?php
namespace SwoStar\Consul;

class Agent
{
    /**
     * @var Consul
     */
    protected $consul;

    public function __construct($consul)
    {
        $this->consul = $consul;
    }

    /**
     * 获取所有的服务信息
     * @return Response
     */
    public function services()
    {
        return $this->consul->get('/v1/agent/services');
    }
    /**
     * 从consul中获取当前健康的服务
     * @method health
     * @param  [type] $sname [description]
     * @return [type]        [description]
     */
    public function health($sname)
    {
        return $this->consul->get('/v1/health/service/'.$sname."?passing=true");
    }

    /**
     * 注册服务,在event事件中监听启动
     * @param array $service
     * @return Response
     */
    public function registerService(array $service)
    {
        $params = [
            'body' => $service,
        ];

        return $this->consul->put('/v1/agent/service/register', $params);
    }

    /**
     * 注销服务
     * @param string $serviceId
     * @return Response
     */
    public function deregisterService(string $serviceId)
    {
        return $this->consul->put('/v1/agent/service/deregister/' . $serviceId);
    }
}


2 . 9 基于swoole提供http服务

在加载完框架后,就开始启动swoole-http服务,启动后,前面加载的框架会被加载进电脑的内存中进行常驻,当有请求到onRequest的时候,就会解析路由,请求到对应的控制器中,完成业务逻辑后返回响应

2 . 9 . 1

SwoStar/Server/Http/Http.php : 提供swoole的http服务,加载框架进内存中(启动http服务,前面加载的框架就会进入内存中),获取http响应,解析路由

<?php
namespace SwoStar\Server\Http;

use SwoStar\Console\Input;
use SwoStar\Message\Response;

use SwoStar\Server\ServerBase;
use SwoStar\Message\Http\Request;

class Server extends ServerBase
{

    /**
     * 因为不同的服务构建不一样
     */
    protected function createServer(){
        $this->server = new \Swoole\Http\Server($this->host, $this->port);

        Input::info("http:\\\\".swoole_get_local_ip()['eth0'].":".$this->port, "访问地址");
    }
    /**
     * 因为不同的服务构建不一样
     */
    protected function initServerConfig(){
        $this->port = $this->app->make("config")->get("server.http.port");
        $this->host = $this->app->make("config")->get("server.http.host");
    }
    /**
     * 因为不同的服务构建不一样
     */
    protected function initEvent(){
        $this->setEvent('sub', [
          'request' => 'onRequest'
        ]);
    }

    /**
     * @param $request
     * @param $response
     */
    public function onRequest($request, $response)
    {

        if ($request->server['path_info'] == '/favicon.ico' || $request->server['request_uri'] == '/favicon.ico') {
            $response->end();
            return;
        }
        // 获取请求对象
        $httpRequest = Request::init($request);

        // 解析路由并响应请求
        $return = $this->app->make('route')->match($httpRequest->getUriPath(), "http", $httpRequest->getMethod());

        $response->end(Response::send($return));
    }
}

2 . 9 . 2

SwoStar/Server/ServerBase.php : 完成swoole-http 的其他回调事件,如框架启动时要触发的事件,注册服务到consul等

<?php
namespace SwoStar\Server;

use SwoStar\Foundation\Application;

abstract class ServerBase
{
    /**
     * @var SwoStar\Foundation\Application
     */
    protected $app;
    /**
     * @var Swoole\Server
     */
    protected $server;

    protected $host = '0.0.0.0';

    protected $port = 9500;

    protected $serverConfig = [
        'task_worker_num' => 0,
    ];

    protected $serverEvent = [
        "server" => [ // 有swoole的本身的事件 -- 是在swoole的整体生命周期
            'start' => 'onStart',
            'Shutdown' => "onShutdown"
         ],
        "sub" => [], // http - websocket 是记录明确swoole服务独有的事件
        "ext" => [], // 根据用户扩展task事件
    ];

    function __construct(Application $app)
    {
        $this->app = $app;
        // 初识化服务的设置
        $this->initServerConfig();
        // 创建服务
        $this->createServer();
        // 初识事件
        $this->initEvent();
        // 设置回调函数
        $this->setSwooleEvent();
    }
    /**
     * 因为不同的服务构建不一样
     */
    abstract protected function initServerConfig();
    /**
     * 因为不同的服务构建不一样
     */
    abstract protected function createServer();
    /**
     * 因为不同的服务构建不一样
     */
    abstract protected function initEvent();


    public function onStart($server) {
        $this->app->make('event')->trigger("swoole:start", [$this, $server]);
    }
    
    public function onShutdown($server) {
        $this->app->make('event')->trigger("swoole:stop", [$this, $server]);
    }

    /**
     * 设置swoole的回调事件
     */
    protected function setSwooleEvent()
    {
        foreach ($this->serverEvent as $type => $events) {
            foreach ($events as $event => $func) {
                $this->server->on($event, [$this, $func]);
            }
        }
    }


    public function start()
    {
        $this->server->set($this->serverConfig);

        $this->server->start();
    }

    /**
     * @param array
     *
     * @return static
     */
    public function setEvent($type, $event)
    {
        // 暂时不支持直接设置系统的回调事件
        if ($type == "server") {
            return;
        }
        $this->serverEvent[$type] = $event;
    }


    public function getServer()
    {
        return $this->server;
    }

}


2 . 10 测试

到这里,一个简单的基本的框架核心就搭建好了,这里在config/server.php文件进行配置,就可以启动服务了,如果这里选择了配置了consul,则需要先把consul的服务启动好。

2 . 10 . 1 运行服务:
php bin/swostar http:start

在这里插入图片描述

consul中的服务注册

在这里插入图片描述

2 . 10 . 2 rpc服务测试

复制当先项目,更改为另一个项目,这里用 activities 项目,部署到另一台机器中,如下

启动服务:

在这里插入图片描述

consul服务

在这里插入图片描述

注意 : 这里如果用的docker容器,则需要进行nginx的代理,由于这里用的是docker,所以需要用到nginx给http和tcp进行代理,需要在两台机器上进行配置,这里只那一台服务器进行演示

2 . 10 . 3 nginx代理http与tcp

代理http

server {
    listen 9506;
    listen [::]:9506;
    #server_name  localhost;
    #root /docker/www/work/18/activities;
    #index index.php index.html;


    location / {
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

        proxy_pass http://172.17.0.3:9506;
    }
}

代理tcp

upstream swoft-ssh {
   hash $remote_addr consistent;
   server 172.17.0.3:9508 weight=5 max_fails=3 fail_timeout=30s;
}

server {
   #监听端口
   listen 9508;
   proxy_connect_timeout 10s;
   #设置客户端和代理服务之间的超时时间,如果5分钟内没操作将自动断开。
   proxy_timeout 300s;
   proxy_pass swoft-ssh;
}

配置好后,基本就可以请求到框架了

2 . 10 . 4 在goods服务中添加rpc服务端逻辑

App/Rpc/Service/DemoService.php : 提供服务

<?php
namespace App\Rpc\Service;

class DemoService
{
    public function getList()
    {
        return ["data" => '这里是goods服务提供的数据'];
    }

    public function getUser()
    {
        return ["data" => 'getUser'];
    }
}

2 . 10 . 5 在activities服务的控制器中,远程rpc调用

App/Http/Controller/IndexController.php :

<?php
namespace App\Http\Controller;

use App\Rpc\Client\DemoClient;

class IndexController
{
    public function demo()
    {
        $data = app('consul-agent')->health('swostar');

        return ['msg'=> '操作成功','status' => 200, 'data' => [$data]];
    }

    public function rpc()
    {
        //远程调用goods服务上的DemoClient控制器的getList方法
        $out = (new DemoClient)->getList();

        return ['msg'=> '操作成功','status' => 200, 'data' => json_decode($out)];
    }
}

添加DemoClient的swoole客户端代码

App/Rpc/Client/DemoClient.php

<?php
namespace App\Rpc\Client;

use SwoStar\Rpc\RpcClient;

class DemoClient extends RpcClient
{
    //goods服务中DemoService类
    protected $classType = \App\Rpc\Service\DemoService::class;

    //这里goods既是consul注册服务时的NAME
    protected $service = "goods";
}

添加路由文件:

route/http.php

<?php
use SwoStar\Routes\Route;

Route::get('index', function(){
    return "this is route index() test";
});

Route::get('/index/rpc', 'IndexController@rpc');

请求返回:

在这里插入图片描述

本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到