要點:Swoole伺服器若想實現不重啟伺服器更新程式碼,則Swoole伺服器若想實現不重啟伺服器更新程式碼,只有在WorkerStart後才載入的程式碼檔案,才可以實現熱更新。
最終效果:
透過 php websocket.php start/reload/restart/shutdown
控制websocket程式, 透過reload命令實現程式碼熱更新
程式碼檔案:
- websocket.php 程式控制檔案
- WebsocketHandler.php Websocket事件處理器
- App\Services\WebsocketService 業務邏輯處理(非必須)
websocket.php
<?php
require_once __DIR__.'/vendor/autoload.php';
if (php_sapi_name() != 'cli') die('請用cli模式啟動.');
$type = $argv[1];
switch ($type) {
case 'start':
echo "websocket server start\n";
start();
break;
case 'reload':
echo "websocket server reload\n";
reload();
break;
case 'shutdown':
echo "websocket server shutdown\n";
shutdown();
break;
case 'restart':
echo "websocket server restart\n";
restart();
break;
}
function start()
{
$dotenv = new Dotenv\Dotenv(__DIR__);
$dotenv->load();
$ssl = getenv('WS_SSL');
$server_sock_type = SWOOLE_SOCK_TCP;
$server_setting = [
'max_wait_time' => 60,
'reload_async' => true
];
if($ssl) {
// $server_sock_type = SWOOLE_SOCK_TCP | SWOOLE_SSL;
$server_setting['ssl_cert_file'] = getenv('WS_SSL_CERT_FILE');
$server_setting['ssl_key_file'] = getenv('WS_SSL_CERT_KEY');
$server_setting['ssl_verify_peer'] = false;
}
$ws = new \Swoole\WebSocket\Server(getenv('WS_ADDRESS'), getenv('WS_PORT'), SWOOLE_PROCESS, $server_sock_type);
// 配置
$ws->set($server_setting);
$ws->on('start', function ($server) {
swoole_set_process_name("jadeite-websocket");
echo "主程式PID: {$server->master_pid}\n";
echo "管理程式PID: {$server->manager_pid}\n";
$content = <<<EOF
MASTER_PID={$server->master_pid}
MANAGER_PID={$server->manager_pid}
EOF;
file_put_contents(__DIR__ . '/storage/app/websocket', $content);
});
$ws->on('WorkerStart', function (\Swoole\Server $server, int $workerId) {
var_dump(get_included_files()); //此陣列中的檔案表示程式啟動前就載入了,所以無法reload
require_once __DIR__ . '/public/index.php';
register_shutdown_function(function () {
$error = error_get_last();
Log::emergency("[Websocket Worker shutdown]:\n".$error);
});
});
require_once 'WebsocketHandler.php';
$handler = new WebsocketHandler($ws);
$ws->on('open', function ($ws, $request) use ($handler) {
$handler->handleOpen($request);
});
//監聽WebSocket訊息事件
$ws->on('message', function ($ws, $frame) use ($handler) {
$handler->handleMessage($frame);
});
//監聽WebSocket連線關閉事件
$ws->on('close', function ($ws, $fd) use ($handler) {
$handler->handleClose($fd);
});
$ws->start();
}
function reload()
{
$content = file_get_contents(__DIR__ . '/storage/app/websocket');
echo "{$content}\n";
$master_pid = explode('=', explode("\n", $content)[0])[1];
echo "kill -USR1 {$master_pid}\n";
exec("kill -USR1 {$master_pid}");
}
function shutdown()
{
$content = file_get_contents(__DIR__ . '/storage/app/websocket');
echo "{$content}\n";
$master_pid = explode('=', explode("\n", $content)[0])[1];
echo "kill -15 {$master_pid}\n";
exec("kill -15 {$master_pid}");
}
function restart()
{
shutdown();
start();
}
WebsocketHandler.php
<?php
/**
* Created by PhpStorm.
* User: liuxiaofeng
* Date: 2020-03-24
* Time: 14:24
*/
use App\Services\WebsocketService;
class WebsocketHandler
{
protected $ws = null;
protected $service = null;
public function __construct(\Swoole\WebSocket\Server $ws) {
$this->ws = $ws;
}
public function handleOpen($request) {
if($this->service === null) {
$this->service = new WebsocketService($this->ws);
}
$this->service->handleOpen($request);
}
public function handleMessage($frame) {
if($this->service === null) {
$this->service = new WebsocketService($this->ws);
}
$this->service->handleMessage($frame);
}
public function handleClose($fd) {
if($this->service === null) {
$this->service = new WebsocketService($this->ws);
}
$this->service->handleClose($fd);
}
}
App\Services\WebsocketService
<?php
/**
* Created by PhpStorm.
* User: liuxiaofeng
* Date: 2020-03-23
* Time: 16:07
*/
namespace App\Services;
use Carbon\Carbon;
use Illuminate\Support\Facades\Redis;
class WebsocketService
{
protected $ws = null;
protected $redis = null;
public function __construct(\Swoole\WebSocket\Server $ws) {
$this->ws = $ws;
$this->redis = Redis::connection();
}
public function handleOpen($request) {
echo "[handling open]\n";
echo "client-{$request->fd} is opened.\n";
}
public function handleMessage($frame) {
try{
if($frame->data === 'heartbeat.') return;
echo "[handling client-{$frame->fd} message]\n";
echo "[Data]: {$frame->data}\n";
$data = json_decode($frame->data, true);
if(!is_array($data)) {
return;
}
if(isset($data['type'])) {
$type = $data['type'];
$data = $data['data'];
switch ($type) {
case 'index':
// 進入首頁
break;
}
}
} catch (\Throwable $e) {
\Log::error("[Websocket Error on handling Message]:\n". $e->getMessage());
}
}
public function handleClose($fd) {
try{
echo "[handling client-{$fd} close]\n";
} catch (\Throwable $e) {
\Log::error("[Websocket Error on handling Close]:\n". $e->getMessage());
}
}
/**
* 單獨傳送
* @param $fd
* @param $data
*/
public function send($fd, $data) {
if($this->ws->isEstablished($fd)) {
$this->ws->push($fd, json_encode($data));
}
}
}
本作品採用《CC 協議》,轉載必須註明作者和本文連結