Easyswoole源碼分析-8-Console(控制臺(tái))

1.簡介

EasySwoole 提供了console控制臺(tái)組件,在項(xiàng)目運(yùn)行的時(shí)候,可通過命令和服務(wù)端進(jìn)行通訊,查看服務(wù)端運(yùn)行狀態(tài),實(shí)時(shí)推送運(yùn)行邏輯等

知識(shí)點(diǎn)
1.swoole_event_add
2.addListener
3.EasySwoole CONSOLE組件

2.流程

啟動(dòng)Easyswoole時(shí)會(huì)啟動(dòng)主服務(wù)隔嫡,根據(jù)配置啟動(dòng)其它服務(wù)查剖,比如Console和Crontab服務(wù),客戶端執(zhí)行php easyswoole console 會(huì)連接console服務(wù)氧敢,連接成功后發(fā)送相應(yīng)指令服務(wù)器執(zhí)行后返回侯嘀,客戶端輸出結(jié)果另凌。

image.png
?  easyswoole php easyswoole console
connect to  tcp://127.0.0.1:9500 success
Welcome to EasySwoole Console
auth root 123456 // 根據(jù)配置文件配置的登錄信息登錄
auth success
server // 命令,下面為返回的結(jié)果
進(jìn)行服務(wù)端的管理

用法: 命令 [命令參數(shù)]

server status                    | 查看服務(wù)當(dāng)前的狀態(tài)
server hostIp                    | 顯示服務(wù)當(dāng)前的IP地址
server reload                    | 重載服務(wù)端
server shutdown                  | 關(guān)閉服務(wù)端
server clientInfo [fd]           | 查看某個(gè)鏈接的信息
server close [fd]                | 斷開某個(gè)鏈接

console服務(wù)啟動(dòng)流程戒幔。 這個(gè)圖最好對(duì)著代碼看吠谢,不然我自己都看不懂。

image.png

Console Client 執(zhí)行流程,主要完成創(chuàng)建client诗茎、連接console服務(wù)工坊、發(fā)送指令献汗、返回執(zhí)行結(jié)果。

image.png

Console Server receive 數(shù)據(jù)處理流程

image.png

3.代碼分析

3.1 啟動(dòng)服務(wù)

Core.php 中的extraHandler為啟動(dòng)console的核新代碼

private function extraHandler()
{
        $serverName = Config::getInstance()->getConf('SERVER_NAME');
        //注冊Console
        if(Config::getInstance()->getConf('CONSOLE.ENABLE')){
            // 獲取console配置信息
            $config = Config::getInstance()->getConf('CONSOLE');
            // 添加服務(wù)
            ServerManager::getInstance()->addServer('CONSOLE',$config['PORT'],SWOOLE_TCP,$config['LISTEN_ADDRESS']);
            // 賦予服務(wù)功能
            Console::getInstance()->attachServer(ServerManager::getInstance()->getSwooleServer('CONSOLE'),new ConsoleConfig());
            // 將創(chuàng)建的服務(wù)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對(duì)象容器里面注冊auth王污、server罢吃、log對(duì)象
            ConsoleModuleContainer::getInstance()->set(new Auth());
            ConsoleModuleContainer ::getInstance()->set(new Server());
            ConsoleModuleContainer ::getInstance()->set(new Log());
        }
        //注冊crontab進(jìn)程
        Crontab::getInstance()->__run();
}

添加服務(wù)

public function addServer(string $serverName,int $port,int $type = SWOOLE_TCP,string $listenAddress = '0.0.0.0',array $setting = [
        "open_eof_check"=>false,
    ]):EventRegister
{
        ···
        // 增加監(jiān)聽的端口。業(yè)務(wù)代碼中可以通過調(diào)用 [Server->getClientInfo](https://wiki.swoole.com/wiki/page/p-connection_info.html) 來獲取某個(gè)連接來自于哪個(gè)端口昭齐。
        $subPort = $this->swooleServer->addlistener($listenAddress,$port,$type);
       ···
}

賦予服務(wù)功能尿招,感興趣的話可以去看看ConsoleProtocolParser這個(gè)類

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自動(dòng)分包
            'package_eof' => "\r\n", // 以\r\n分包
        ));
        // new socket config
        $conf = new DispatcherConfig();
        // 設(shè)置解包、打包類
        $conf->setParser(new ConsoleProtocolParser());
        // 設(shè)置通信類型
        $conf->setType($conf::TCP);
        // 將socket config 對(duì)象給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就谜、接收、發(fā)送里覆、返回丧荐、輸出相應(yīng)信息。

public function exec(array $args): ?string
{
        // TODO: Implement exec() method.
        // 獲取console配置信息
        $conf = Config::getInstance()->getConf('CONSOLE');
        // 協(xié)程執(zhí)行
        go(function ()use($conf){
            // 創(chuàng)建client對(duì)象
            $client = new Client($conf['LISTEN_ADDRESS'],$conf['PORT']);
            // 連接console服務(wù)器
            if($client->connect()){
                echo "connect to  tcp://".$conf['LISTEN_ADDRESS'].":".$conf['PORT']." success \n";
                // 協(xié)程接收console服務(wù)返回的數(shù)據(jù)
                go(function ()use($client){
                    while (1){
                        // 接收數(shù)據(jù)
                        $data = $client->recv(-1);
                        if(!empty($data)){
                            echo $data."\n";
                        }else if($client !== false){
                            exit();
                        }
                    };
                });
                // 將STDIN加入到底層的reactor事件監(jiān)聽中
                swoole_event_add(STDIN,function()use($client){
                    $ret = trim(fgets(STDIN));
                    if(!empty($ret)){
                        // 協(xié)程發(fā)送指令到console 服務(wù)
                        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數(shù)據(jù)

這個(gè)方法為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數(shù)據(jù)(這里的decode和php中的json_decode 是不同的)
            $caller = $this->config->getParser()->decode($data,$client);
        }catch (\Throwable $throwable){
            //注意喧枷,在解包出現(xiàn)異常的時(shí)候篮奄,則調(diào)用異常處理,默認(rèn)是斷開連接割去,服務(wù)端拋出異常
            $this->hookException($server,$throwable,$data,$client,$response);
            goto response;
        }
        //如果成功返回一個(gè)調(diào)用者窟却,那么執(zhí)行調(diào)用邏輯
        if($caller instanceof Caller){
            // 將$client 對(duì)象 set給caller
            $caller->setClient($client);
            // 獲取控制器名稱(ConsoleTcpController)
            $controllerClass = $caller->getControllerClass();
            try{
                // 獲取控制器對(duì)象(ConsoleTcpController對(duì)象)
                $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);
            }
        }
        // 返回?cái)?shù)據(jù)
        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貼一下,因?yàn)榻邮諗?shù)據(jù)decode的時(shí)候setControllerClass的類為ConsoleTcpController呻逆,這也是為什么我一直在注釋中提到的這個(gè)方法夸赫。

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));
        // 設(shè)置controller,這里是重點(diǎn)
        $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.結(jié)語

這樣當(dāng)自己項(xiàng)目中想搭建這樣一套console的時(shí)候,可以仿照Easyswoole中的這種方式咖城。

僅僅記錄學(xué)習(xí)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末茬腿,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子宜雀,更是在濱河造成了極大的恐慌切平,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,122評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件辐董,死亡現(xiàn)場離奇詭異悴品,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)简烘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門苔严,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人孤澎,你說我怎么就攤上這事届氢。” “怎么了覆旭?”我有些...
    開封第一講書人閱讀 164,491評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵退子,是天一觀的道長岖妄。 經(jīng)常有香客問我,道長寂祥,這世上最難降的妖魔是什么衣吠? 我笑而不...
    開封第一講書人閱讀 58,636評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮壤靶,結(jié)果婚禮上缚俏,老公的妹妹穿的比我還像新娘。我一直安慰自己贮乳,他們只是感情好忧换,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著向拆,像睡著了一般亚茬。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上浓恳,一...
    開封第一講書人閱讀 51,541評(píng)論 1 305
  • 那天刹缝,我揣著相機(jī)與錄音,去河邊找鬼颈将。 笑死梢夯,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的晴圾。 我是一名探鬼主播颂砸,決...
    沈念sama閱讀 40,292評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼死姚!你這毒婦竟也來了人乓?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,211評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤都毒,失蹤者是張志新(化名)和其女友劉穎色罚,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體账劲,經(jīng)...
    沈念sama閱讀 45,655評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡戳护,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了涤垫。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片姑尺。...
    茶點(diǎn)故事閱讀 39,965評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡竟终,死狀恐怖蝠猬,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情统捶,我是刑警寧澤榆芦,帶...
    沈念sama閱讀 35,684評(píng)論 5 347
  • 正文 年R本政府宣布柄粹,位于F島的核電站,受9級(jí)特大地震影響匆绣,放射性物質(zhì)發(fā)生泄漏驻右。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評(píng)論 3 329
  • 文/蒙蒙 一崎淳、第九天 我趴在偏房一處隱蔽的房頂上張望堪夭。 院中可真熱鬧,春花似錦拣凹、人聲如沸森爽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽爬迟。三九已至,卻和暖如春菊匿,著一層夾襖步出監(jiān)牢的瞬間付呕,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評(píng)論 1 269
  • 我被黑心中介騙來泰國打工跌捆, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留徽职,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,126評(píng)論 3 370
  • 正文 我出身青樓佩厚,卻偏偏與公主長得像活箕,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子可款,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 計(jì)算機(jī)網(wǎng)絡(luò)概述 網(wǎng)絡(luò)編程的實(shí)質(zhì)就是兩個(gè)(或多個(gè))設(shè)備(例如計(jì)算機(jī))之間的數(shù)據(jù)傳輸育韩。 按照計(jì)算機(jī)網(wǎng)絡(luò)的定義,通過一定...
    蛋炒飯_By閱讀 1,226評(píng)論 0 10
  • 第一部分 HTML&CSS整理答案 1. 什么是HTML5闺鲸? 答:HTML5是最新的HTML標(biāo)準(zhǔn)筋讨。 注意:講述HT...
    kismetajun閱讀 27,486評(píng)論 1 45
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,101評(píng)論 1 32
  • 工廠模式類似于現(xiàn)實(shí)生活中的工廠可以產(chǎn)生大量相似的商品,去做同樣的事情摸恍,實(shí)現(xiàn)同樣的效果;這時(shí)候需要使用工廠模式悉罕。簡單...
    舟漁行舟閱讀 7,761評(píng)論 2 17
  • Apple 篇 (Android 篇請(qǐng)戳??《智能手機(jī)漫談(二)——從“三要素”到三顆“驕陽”》) 1.喬布斯與初代...
    醉幽冉閱讀 16,473評(píng)論 28 25