這是關於 Swoole 學習的第六篇文章:Swoole 整合成一個小框架。html
寫了關於 Swoole 入門的 5 篇文章後,增長了很多的關注者,也獲得了一些大佬的鼓勵,也有不少關注者都加了微信好友,交流以後發現一些朋友比我優秀還比我努力,也獲得了一些大佬的建議。react
發現持續寫文章真的不是件容易的事,擔憂別人認爲沒價值,擔憂想法太幼稚或有漏洞被別人笑話,擔憂肚子裏墨水太少,寫不出來... 知道本身思路還不夠清晰,邏輯還不夠嚴謹,告訴本身不要緊,一些都會好起來的,逆境才能成長嘛,敢寫就是好的開始,以此來激勵本身持續的學習和思考。web
跑題了,說回正題。json
這篇文章實際上是讀者的小小要求,事情是這樣的:api
讀者:「亮哥,看了你的文章頗有收穫,將文章 Demo 放在本地直接就能運行了,太感謝了」瀏覽器
本人:「哈哈。。。有收穫就好,感謝支持 ~ 」微信
讀者:「我有一個小小的要求,如今每一個文件都是獨立的,我想部署到生產環境,想操做上更便捷,有日誌...」websocket
本人:「你說的不是框架嗎?如今有不少現成的,看 Swoole 官網推薦的 Swoft、EasySwoole、MixPHP 等。詳細的參考這個地址:https://wiki.swoole.com/wiki/page/p-open_source.html」swoole
讀者:「看了,發現文件太多了,看不懂,你能幫忙講解下嗎?」
本人:「What?我也是入門呀,要不我搞個簡單的輪子吧」
......
因而就有了這篇文章,正好也是對前面 5 篇文章的複習吧。
命令以下:
index.php 這是文件名稱,你們叫什麼均可以。
目錄結構以下:
├─ controller │ ├── ... ├─ client │ ├─ websocket │ ├── ... │ ├─ tcp │ ├── ... ├─ server │ ├─ config │ ├── config.php │ ├─ core │ ├── Common.php │ ├── Core.php │ ├── HandlerException.php │ ├─ log -- 須要 讀/寫 權限 │ ├── ... ├─ index.php
目前就這幾個文件,後期研究新的知識點會直接集成到這裏面。
說說實現了什麼:
一、啓動了 WebSocket、HTTP、TCP、UDP 等服務。
二、WebSocket 例子,在 client/websocket 文件夾,實現了視頻彈幕。
三、HTTP 例子,在瀏覽器直接訪問:http://ip:port,邏輯代碼在 controller 文件夾。
四、TCP 例子,在 client/tcp 文件夾。
五、UDP 例子,直接運行 netcat -u ip port
便可。
六、相關配置,在 server/config 文件夾。
放不全,就放一個主要的文件吧(Core.php)。
<?php if (!defined('SERVER_PATH')) exit("No Access"); class Core { private static $serv; public function __construct() { set_error_handler(['HandlerException', 'appError']); register_shutdown_function(['HandlerException', 'fatalError']); } public static function run() { static::checkCli(); static::checkExtension(); static::showUsageUI(); static::parseCommand(); } protected static function checkCli() { if (php_sapi_name() !== 'cli') { exit(output('服務只能運行在 cli sapi 模式下')); } } protected static function checkExtension() { if (!extension_loaded('swoole')) { exit(output('請安裝 swoole 擴展')); } } protected static function showUsageUI() { global $argc; if ($argc <= 1 || $argc >3) { echo PHP_EOL; echo "----------------------------------------".PHP_EOL; echo "| Swoole |".PHP_EOL; echo "|--------------------------------------|".PHP_EOL; echo '| USAGE: php index.php commond |'.PHP_EOL; echo '|--------------------------------------|'.PHP_EOL; echo '| 1. start 以debug模式開啓服務 |'.PHP_EOL; echo '| 2. start -d 以daemon模式開啓服務 |'.PHP_EOL; echo '| 3. status 查看服務狀態 |'.PHP_EOL; echo '| 4. reload 熱加載 |'.PHP_EOL; echo '| 5. stop 關閉服務 |'.PHP_EOL; echo "----------------------------------------".PHP_EOL; echo PHP_EOL; exit; } } protected static function parseCommand() { global $argv; $command = $argv[1]; $option = isset( $argv[2] ) ? $argv[2] : '' ; switch ($command) { case 'start': if ($option === '-d') { //以daemon形式啓動 get_config(['set@daemonize' => true]); } self::workerStart(); break; case 'status': self::workerStatus(); break; case 'reload': self::workerReload(); break; case 'stop': self::workerStop(); break; default: echo "Bad Command.".PHP_EOL; } } protected static function workerStart() { $config = get_config(); self::$serv = new swoole_websocket_server($config['ip'], $config['websocket_port']); self::$serv->set($config['set']); self::$serv->on('Start', function ($serv) use ($config) { $start = new OnStart(); $start::run($serv, $config); }); self::$serv->on('ManagerStart', function ($serv) use ($config) { $manager_start = new OnManagerStart(); $manager_start::run($serv, $config); }); self::$serv->on('WorkerStart', function ($serv, $worker_id) use ($config) { $worker_start = new OnWorkerStart(); $worker_start::run($serv, $worker_id, $config); }); //TCP $tcp = self::$serv->listen($config['ip'], $config['tcp_port'], SWOOLE_SOCK_TCP); $tcp->set($config['tcp_set']); $tcp->on('Receive', function ($serv, $fd, $reactor_id, $data) { $receive = new OnReceive(); $receive::run($serv, $fd, $reactor_id, $data); }); //UDP $udp = self::$serv->listen($config['ip'], $config['udp_port'], SWOOLE_SOCK_UDP); $udp->set($config['udp_set']); $udp->on('Packet', function ($serv, $data, $client_info) { $packet = new OnPacket(); $packet::run($serv, $data, $client_info); }); self::$serv->on('Task', function ($serv, $task_id, $src_worker_id, $data) use ($config) { $task = new OnTask(); $dataArr = json_decode($data, true); switch ($dataArr['server']) { case "tcp": $task::tcp_task_run($serv, $task_id, $src_worker_id, $data); break; case "ws": $task::ws_task_run($serv, $task_id, $src_worker_id, $data); break; } }); self::$serv->on('Open', function ($serv, $request) { echo output("onOpen: handshake success with fd={$request->fd}"); }); self::$serv->on('Message', function ($serv, $frame) { $message = new OnMessage(); $message::run($serv, $frame); }); self::$serv->on('Request', function ($request, $response) { $req = new OnRequest(); $req::run($request, $response); }); self::$serv->on('Finish', function ($serv, $task_id, $data) { $finish = new OnFinish(); $finish::run($serv, $task_id, $data); }); self::$serv->on('Close', function ($serv, $fd, $reactor_id){ try { echo output('客戶端關閉'); } catch(Exception $e) { } }); self::$serv->on('Shutdown', function ($serv) { echo output("服務關閉"); }); self::showProcessUI(); self::$serv->start(); } protected static function workerStatus() { $config = get_config(); if (!file_exists($config['master_pid_file']) || !file_exists($config['manager_pid_file']) || !file_exists($config['worker_pid_file']) ) { echo output("暫無啓動的服務"); return false; } self::showProcessUI($config); $masterPidString = trim(@file_get_contents($config['master_pid_file'])); $masterPidArr = explode( '-', $masterPidString); echo str_pad("Master", 18, ' ', STR_PAD_BOTH ). str_pad($config['master_process_name'], 26, ' ', STR_PAD_BOTH ). str_pad($masterPidArr[0], 16, ' ', STR_PAD_BOTH ). str_pad($masterPidArr[1], 16, ' ', STR_PAD_BOTH ). str_pad($masterPidArr[2], 16, ' ', STR_PAD_BOTH ).PHP_EOL; $managerPidString = trim(@file_get_contents($config['manager_pid_file'])); $managerPidArr = explode( '-', $managerPidString); echo str_pad("Manager", 20, ' ', STR_PAD_BOTH ). str_pad($config['manager_process_name'], 24, ' ', STR_PAD_BOTH ). str_pad($managerPidArr[0], 16, ' ', STR_PAD_BOTH ). str_pad($managerPidArr[1], 16, ' ', STR_PAD_BOTH ). str_pad($managerPidArr[2], 16, ' ', STR_PAD_BOTH ).PHP_EOL; $workerPidString = rtrim(@file_get_contents($config['worker_pid_file']), '|' ); $workerPidArr = explode( '|', $workerPidString ); if (isset($workerPidArr) && !empty($workerPidArr)) { foreach ($workerPidArr as $key => $val) { $v = explode( '-', $val); echo str_pad("Worker", 18, ' ', STR_PAD_BOTH ). str_pad($config['worker_process_name'], 26, ' ', STR_PAD_BOTH ). str_pad($v[0], 16, ' ', STR_PAD_BOTH ). str_pad($v[1], 16, ' ', STR_PAD_BOTH ). str_pad($v[2], 16, ' ', STR_PAD_BOTH ).PHP_EOL; } } $taskPidString = rtrim(@file_get_contents($config['task_pid_file']), '|' ); $taskPidArr = explode( '|', $taskPidString ); if (isset($taskPidArr) && !empty($taskPidArr)) { foreach ($taskPidArr as $key => $val) { $v = explode( '-', $val); echo str_pad("Task", 18, ' ', STR_PAD_BOTH ). str_pad($config['task_process_name'], 24, ' ', STR_PAD_BOTH ). str_pad($v[0], 20, ' ', STR_PAD_BOTH ). str_pad($v[1], 12, ' ', STR_PAD_BOTH ). str_pad($v[2], 20, ' ', STR_PAD_BOTH ).PHP_EOL; } } } protected static function workerReload() { $config = get_config(); if (!file_exists($config['master_pid_file'])) { echo output("暫無啓動的服務"); return false; } $masterPidString = trim(file_get_contents($config['master_pid_file'])); $masterPidArr = explode( '-', $masterPidString); if (!swoole_process::kill($masterPidArr[0], 0)) { echo output("PID:{$masterPidArr[0]} 不存在"); return false; } swoole_process::kill($masterPidArr[0], SIGUSR1); @unlink($config['worker_pid_file']); @unlink($config['task_pid_file']); echo output("熱加載成功"); return true; } protected static function workerStop() { $config = get_config(); if (!file_exists($config['master_pid_file'])) { echo output("暫無啓動的服務"); return false; } $masterPidString = trim(file_get_contents($config['master_pid_file'])); $masterPidArr = explode( '-', $masterPidString); if (!swoole_process::kill($masterPidArr[0], 0)) { echo output("PID:{$masterPidArr[0]} 不存在"); return false; } swoole_process::kill($masterPidArr[0]); $time = time(); while (true) { usleep(2000); if (!swoole_process::kill($masterPidArr[0], 0)) { unlink($config['master_pid_file']); unlink($config['manager_pid_file']); unlink($config['worker_pid_file']); unlink($config['task_pid_file']); echo output("服務關閉成功"); break; } else { if (time() - $time > 5) { echo output("服務關閉失敗,請重試"); break; } } } return true; } protected static function showProcessUI() { $config = get_config(); if ($config['set']['daemonize'] == true) { return false; } echo str_pad("-", 90, '-', STR_PAD_BOTH) . PHP_EOL; echo "|" . str_pad("啓動/關閉", 92, ' ', STR_PAD_BOTH) . "|" . PHP_EOL; echo str_pad("-", 90, '-', STR_PAD_BOTH) . PHP_EOL; echo str_pad("Start success.", 50, ' ', STR_PAD_BOTH) . str_pad("php index.php stop", 50, ' ', STR_PAD_BOTH) . PHP_EOL; echo str_pad("-", 90, '-', STR_PAD_BOTH) . PHP_EOL; echo "|" . str_pad("版本信息", 92, ' ', STR_PAD_BOTH) . "|" . PHP_EOL; echo str_pad("-", 90, '-', STR_PAD_BOTH) . PHP_EOL; echo str_pad("Swoole Version:" . SWOOLE_VERSION, 50, ' ', STR_PAD_BOTH) . str_pad("PHP Version:" . PHP_VERSION, 50, ' ', STR_PAD_BOTH) . PHP_EOL; echo str_pad("-", 90, '-', STR_PAD_BOTH) . PHP_EOL; echo "|" . str_pad("IP 信息", 90, ' ', STR_PAD_BOTH) . "|" . PHP_EOL; echo str_pad("-", 90, '-', STR_PAD_BOTH) . PHP_EOL; echo str_pad("IP:" . $config['ip'], 50, ' ', STR_PAD_BOTH) . str_pad("PORT:" . $config['websocket_port'], 50, ' ', STR_PAD_BOTH) . PHP_EOL; echo str_pad("-", 90, '-', STR_PAD_BOTH) . PHP_EOL; echo "|" . str_pad("進程信息", 92, ' ', STR_PAD_BOTH) . "|" . PHP_EOL; echo str_pad("-", 90, '-', STR_PAD_BOTH) . PHP_EOL; echo str_pad("Swoole進程", 20, ' ', STR_PAD_BOTH) . str_pad('進程別名', 30, ' ', STR_PAD_BOTH) . str_pad('進程ID', 18, ' ', STR_PAD_BOTH) . str_pad('父進程ID', 18, ' ', STR_PAD_BOTH) . str_pad('用戶', 18, ' ', STR_PAD_BOTH) . PHP_EOL; } protected static function signalHandler() { //TODO 未完成 //swoole_process::signal(SIGINT, function ($signal) { // echo $signal; // return; //}); } }
耗費了 3 個晚上的時間,終於完成了一個第一版,比較初級,但願能夠給入門的同窗一個參考吧。
固然我本身也會繼續完善它,後期的一些新知識點會集成到這裏面,作成本身迭代的小項目。
第一版比較糙,不喜勿噴。
後期會新增:
須要源碼的,加我微信吧。(菜單-> 加我微信-> 掃我)
本文歡迎轉發,轉發請註明做者和出處,謝謝!