Easyswoole源码分析-8-Console(控制台)

1.简介

EasySwoole 提供了console控制台组件,在项目运行的时候,可通过命令和服务端进行通讯,查看服务端运行状态,实时推送运行逻辑等

知识点
1.swoole_event_add
2.addListener
3.EasySwoole CONSOLE组件

2.流程

启动Easyswoole时会启动主服务,根据配置启动其它服务,比如Console和Crontab服务,客户端执行php easyswoole console 会连接console服务,连接成功后发送相应指令服务器执行后返回,客户端输出结果。

image.png
➜  easyswoole php easyswoole console
connect to  tcp://127.0.0.1:9500 success
Welcome to EasySwoole Console
auth root 123456 // 根据配置文件配置的登录信息登录
auth success
server // 命令,下面为返回的结果
进行服务端的管理

用法: 命令 [命令参数]

server status                    | 查看服务当前的状态
server hostIp                    | 显示服务当前的IP地址
server reload                    | 重载服务端
server shutdown                  | 关闭服务端
server clientInfo [fd]           | 查看某个链接的信息
server close [fd]                | 断开某个链接

console服务启动流程。 这个图最好对着代码看,不然我自己都看不懂。

image.png

Console Client 执行流程,主要完成创建client、连接console服务、发送指令、返回执行结果。

image.png

Console Server receive 数据处理流程

image.png

3.代码分析

3.1 启动服务

Core.php 中的extraHandler为启动console的核新代码

private function extraHandler()
{
        $serverName = Config::getInstance()->getConf('SERVER_NAME');
        //注册Console
        if(Config::getInstance()->getConf('CONSOLE.ENABLE')){
            // 获取console配置信息
            $config = Config::getInstance()->getConf('CONSOLE');
            // 添加服务
            ServerManager::getInstance()->addServer('CONSOLE',$config['PORT'],SWOOLE_TCP,$config['LISTEN_ADDRESS']);
            // 赋予服务功能
            Console::getInstance()->attachServer(ServerManager::getInstance()->getSwooleServer('CONSOLE'),new ConsoleConfig());
            // 将创建的服务set给Console
            Console::getInstance()->setServer(ServerManager::getInstance()->getSwooleServer());
            // 注册close方法
            ServerManager::getInstance()->getSwooleServer('CONSOLE')->on('close',function (){
                Auth::$authTable->set(Config::getInstance()->getConf('CONSOLE.USER'),[
                    'fd'=>0
                ]);
            });
            // console对象容器里面注册auth、server、log对象
            ConsoleModuleContainer::getInstance()->set(new Auth());
            ConsoleModuleContainer ::getInstance()->set(new Server());
            ConsoleModuleContainer ::getInstance()->set(new Log());
        }
        //注册crontab进程
        Crontab::getInstance()->__run();
}

添加服务

public function addServer(string $serverName,int $port,int $type = SWOOLE_TCP,string $listenAddress = '0.0.0.0',array $setting = [
        "open_eof_check"=>false,
    ]):EventRegister
{
        ···
        // 增加监听的端口。业务代码中可以通过调用 [Server->getClientInfo](https://wiki.swoole.com/wiki/page/p-connection_info.html) 来获取某个连接来自于哪个端口。
        $subPort = $this->swooleServer->addlistener($listenAddress,$port,$type);
       ···
}

赋予服务功能,感兴趣的话可以去看看ConsoleProtocolParser这个类

public function attachServer($server,Config $config)
{
        $this->config = $config;
        // 是否为swoole_server
        if($server instanceof \swoole_server){
            $this->server = $server;
            $server = $server->addlistener($config->getListenAddress(),$config->getListenPort(),SWOOLE_TCP);
        }
        $server->set(array(
            "open_eof_split" => true, // 启用EOF自动分包
            'package_eof' => "\r\n", // 以\r\n分包
        ));
        // new socket config
        $conf = new DispatcherConfig();
        // 设置解包、打包类
        $conf->setParser(new ConsoleProtocolParser());
        // 设置通信类型
        $conf->setType($conf::TCP);
        // 将socket config 对象给Dispatcher
        $dispatcher = new Dispatcher($conf);
        // 注册receive方法
        $server->on('receive', function (\swoole_server $server, $fd, $reactor_id, $data) use ($dispatcher) {
            $dispatcher->dispatch($server, $data, $fd, $reactor_id);
        });
        // 注册connect方法
        $server->on('connect', function (\swoole_server $server, int $fd, int $reactorId) {
            $hello = 'Welcome to ' . $this->config->getServerName();
            $this->send($fd,$hello);
        });
}
3.2 console客户端

这一块主要完成的功能是,连接server、接收、发送、返回、输出相应信息。

public function exec(array $args): ?string
{
        // TODO: Implement exec() method.
        // 获取console配置信息
        $conf = Config::getInstance()->getConf('CONSOLE');
        // 协程执行
        go(function ()use($conf){
            // 创建client对象
            $client = new Client($conf['LISTEN_ADDRESS'],$conf['PORT']);
            // 连接console服务器
            if($client->connect()){
                echo "connect to  tcp://".$conf['LISTEN_ADDRESS'].":".$conf['PORT']." success \n";
                // 协程接收console服务返回的数据
                go(function ()use($client){
                    while (1){
                        // 接收数据
                        $data = $client->recv(-1);
                        if(!empty($data)){
                            echo $data."\n";
                        }else if($client !== false){
                            exit();
                        }
                    };
                });
                // 将STDIN加入到底层的reactor事件监听中
                swoole_event_add(STDIN,function()use($client){
                    $ret = trim(fgets(STDIN));
                    if(!empty($ret)){
                        // 协程发送指令到console 服务
                        go(function ()use($client,$ret){
                            $client->sendCommand($ret);
                        });
                    }
                });
            }else{
                echo "connect to  tcp://".$conf['LISTEN_ADDRESS'].":".$conf['PORT']." fail \n";
            }
        });
        return null;
}
3.3 receive数据

这个方法为receive流程的核心代码

function dispatch(\swoole_server $server ,string $data, ...$args):void
    {
        $clientIp = null;
        $type = $this->config->getType();
        // switch连接类型
        switch ($type){
            case Config::TCP:{
                $client = new Tcp( ...$args);
                break;
            }
            case Config::WEB_SOCKET:{
                $client = new WebSocket( ...$args);
                break;
            }
            case Config::UDP:{
                $client = new Udp( ...$args);
                break;
            }
            default:{
                throw new \Exception('dispatcher type error : '.$type);
            }
        }
        $caller = null;
        $response = new Response();
        try{
            // decode数据(这里的decode和php中的json_decode 是不同的)
            $caller = $this->config->getParser()->decode($data,$client);
        }catch (\Throwable $throwable){
            //注意,在解包出现异常的时候,则调用异常处理,默认是断开连接,服务端抛出异常
            $this->hookException($server,$throwable,$data,$client,$response);
            goto response;
        }
        //如果成功返回一个调用者,那么执行调用逻辑
        if($caller instanceof Caller){
            // 将$client 对象 set给caller
            $caller->setClient($client);
            // 获取控制器名称(ConsoleTcpController)
            $controllerClass = $caller->getControllerClass();
            try{
                // 获取控制器对象(ConsoleTcpController对象)
                $controller = $this->getController($controllerClass);
            }catch (\Throwable $throwable){
                $this->hookException($server,$throwable,$data,$client,$response);
                goto response;
            }
            // ConsoleTcpController是否继承自Controller
            if($controller instanceof Controller){
                try{
                    // hook ConsoleTcpController 中的方法
                    $controller->__hook( $server,$this->config, $caller, $response);
                }catch (\Throwable $throwable){
                    $this->hookException($server,$throwable,$data,$client,$response);
                }finally {
                    $this->recycleController($controllerClass,$controller);
                }
            }else{
                $throwable = new ControllerPoolEmpty('controller pool empty for '.$controllerClass);
                $this->hookException($server,$throwable,$data,$client,$response);
            }
        }
        // 返回数据
        response :{
            switch ($response->getStatus()){
                case Response::STATUS_OK:{
                    $this->response($server,$client,$response);
                    break;
                }
                case Response::STATUS_RESPONSE_AND_CLOSE:{
                    $this->response($server,$client,$response);
                    $this->close($server,$client);
                    break;
                }
                case Response::STATUS_CLOSE:{
                    $this->close($server,$client);
                    break;
                }
            }
        }
    }

感觉还是有必要将ConsoleProtocolParser贴一下,因为接收数据decode的时候setControllerClass的类为ConsoleTcpController,这也是为什么我一直在注释中提到的这个方法。

class ConsoleProtocolParser implements ParserInterface
{
    public function decode($raw, $client): ?Caller
    {
        // TODO: Implement decode() method.
        $data = trim($raw);
        $arr = explode(" ",$data);
        $caller = new Caller();
        $caller->setAction(array_shift($arr));
        // 设置controller,这里是重点
        $caller->setControllerClass(ConsoleTcpController::class);
        $caller->setArgs($arr);
        return $caller;
    }

    public function encode(Response $response, $client): ?string
    {
        // TODO: Implement encode() method.
        $str = $response->getMessage();
        if(empty($str)){
            $str = 'empty response';
        }
        return $str."\r\n";
    }

}

4.结语

这样当自己项目中想搭建这样一套console的时候,可以仿照Easyswoole中的这种方式。

仅仅记录学习

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 224,861评论 6 522
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 96,263评论 3 402
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 172,033评论 0 366
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 60,999评论 1 300
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 70,000评论 6 400
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 53,483评论 1 314
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 41,850评论 3 428
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 40,827评论 0 279
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 47,366评论 1 324
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 39,404评论 3 346
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 41,525评论 1 355
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 37,130评论 5 351
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 42,853评论 3 338
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 33,293评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 34,426评论 1 276
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 50,082评论 3 381
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 46,590评论 2 366

推荐阅读更多精彩内容

  • 计算机网络概述 网络编程的实质就是两个(或多个)设备(例如计算机)之间的数据传输。 按照计算机网络的定义,通过一定...
    蛋炒饭_By阅读 1,233评论 0 10
  • 第一部分 HTML&CSS整理答案 1. 什么是HTML5? 答:HTML5是最新的HTML标准。 注意:讲述HT...
    kismetajun阅读 27,542评论 1 45
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,120评论 1 32
  • 工厂模式类似于现实生活中的工厂可以产生大量相似的商品,去做同样的事情,实现同样的效果;这时候需要使用工厂模式。简单...
    舟渔行舟阅读 7,787评论 2 17
  • Apple 篇 (Android 篇请戳👉《智能手机漫谈(二)——从“三要素”到三颗“骄阳”》) 1.乔布斯与初代...
    醉幽冉阅读 16,480评论 28 25