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í)