Laravel 中引入 Swoole Websocket 并实现热更新 Reload 代码

要点:Swoole服务器若想实现不重启服务器更新代码,则Swoole服务器若想实现不重启服务器更新代码,只有在WorkerStart后才载入的代码文件,才可以实现热更新。

最终效果:
通过 php websocket.php start/reload/restart/shutdown
控制websocket进程, 通过reload命令实现代码热更新

代码文件:

  1. websocket.php 进程控制文件
  2. WebsocketHandler.php Websocket事件处理器
  3. 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 协议》,转载必须注明作者和本文链接
刘晓峰
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!