概述
這是關於 Swoole 學習的第六篇文章:Swoole 整合成一個小框架。
- 第五篇:Swoole 多協議 多埠 的應用
- 第四篇:Swoole HTTP 的應用
- 第三篇:Swoole WebSocket 的應用
- 第二篇:Swoole Task 的應用
- 第一篇:Swoole Timer 的應用
寫了關於 Swoole 入門的 5 篇文章後,增加了不少的關注者,也得到了一些大佬的鼓勵,也有很多關注者都加了微信好友,交流之後發現一些朋友比我優秀還比我努力,也得到了一些大佬的建議。
發現持續寫文章真的不是件容易的事,擔心別人認為沒價值,擔心想法太幼稚或有漏洞被別人笑話,擔心肚子裡墨水太少,寫不出來... 知道自己思路還不夠清晰,邏輯還不夠嚴謹,告訴自己沒關係,一些都會好起來的,逆境才能成長嘛,敢寫就是好的開始,以此來激勵自己持續的學習和思考。
跑題了,說回正題。
這篇文章其實是讀者的小小要求,事情是這樣的:
讀者:“亮哥,看了你的文章很有收穫,將文章 Demo 放在本地直接就能執行了,太感謝了”
本人:“哈哈。。。有收穫就好,感謝支援 ~ ”
讀者:“我有一個小小的要求,現在每個檔案都是獨立的,我想部署到生產環境,想操作上更便捷,有日誌...”
本人:“你說的不是框架嗎?現在有很多現成的,看 Swoole 官網推薦的 Swoft、EasySwoole、MixPHP 等。詳細的參考這個地址:https://wiki.swoole.com/wiki/page/p-open_source.html”
讀者:“看了,發現檔案太多了,看不懂,你能幫忙講解下嗎?”
本人:“What?我也是入門呀,要不我搞個簡單的輪子吧”
......
於是就有了這篇文章,正好也是對前面 5 篇文章的複習吧。
效果
命令如下:
- php index.php 可以檢視到上圖
- php index.php start 開啟服務(Debug模式)
- php index.php start -d 開啟服務(Daemon模式)
- php index.php status 檢視服務狀態
- php index.php reload 服務熱載入
- php index.php stop 關閉服務
index.php 這是檔名稱,大家叫什麼都可以。
目錄結構如下:
├─ controller
│ ├── ...
├─ client
│ ├─ websocket
│ ├── ...
│ ├─ tcp
│ ├── ...
├─ server
│ ├─ config
│ ├── config.php
│ ├─ core
│ ├── Common.php
│ ├── Core.php
│ ├── HandlerException.php
│ ├─ log -- 需要 讀/寫 許可權
│ ├── ...
├─ index.php
目前就這幾個檔案,後期研究新的知識點會直接整合到這裡面。
說說實現了什麼:
1、啟動了 WebSocket、HTTP、TCP、UDP 等服務。
2、WebSocket 例子,在 client/websocket 資料夾,實現了視訊彈幕。
3、HTTP 例子,在瀏覽器直接訪問:http://ip:port,邏輯程式碼在 controller 資料夾。
4、TCP 例子,在 client/tcp 資料夾。
5、UDP 例子,直接執行 netcat -u ip port
即可。
6、相關配置,在 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 個晚上的時間,終於完成了一個初版,比較初級,希望可以給入門的同學一個參考吧。
當然我自己也會繼續完善它,後期的一些新知識點會整合到這裡面,做成自己迭代的小專案。
初版比較糙,不喜勿噴。
後期會新增:
- RPC
- Coroutine - MySQL
- Coroutine - Redis
- Process
- ...
需要原始碼的,加我微信吧。(選單-> 加我微信-> 掃我)
本文歡迎轉發,轉發請註明作者和出處,謝謝!