laravel-swoole 热更新

以下仅对 laravel 8进行说明

swoole的安装方法就不在重复了

测试环境php7.4 CentOS 8.1.1911 

解决方案一

修改swoole的配置选项 'max_request'=>1    

官方的解释是 设置worker进程的最大任务数,默认为0,一个worker进程在处理完超过此数值的任务后将自动退出,进程退出后会释放所有内存和资源。

如果每一个worker执行一次就释放,每次都要重新加载,自然更新就管用了。

解决方案二

1.安装拓展

安装inotify扩展,官方地址

https://pecl.php.net/package/inotify

 请选择自己的对应版本

编译安装

tar -zxvf inotify-3.0.0.tgz

cd inotify-3.0.0/

/www/server/php/74/bin/phpize 

./configure --with-php-config=/www/server/php/74/bin/php-config --enable-inotify

make && make install

请选择自己的php相关路径,修改/www/server/php/74/etc/php.ini 在最后面加入拓展

echo "extension= inotify.so" >> /www/server/php/74/etc/php.ini

如不生效,请重新载入php配置,或者重启php服务 

2.使用

以下仅以config下的swoole_http.php配置文件举例,没有此文件的同学请运行命令生成

php artisan vendor:publish --tag=laravel-swoole

 在confing/swoole_http.php中填写(任意不报错的位置)

//监控文件更新
    'watch_file' => [
        'enabled' => env('SWOOLE_HOT_RELOAD_ENABLE', true),
        'input_file' => [
            //包含的单独文件路径
           base_path('config/app.php'),
           base_path('config/cache.php'),
        ],
        'update_dir' => [
            //需要监控的文件目录
            app_path(),
        ],
        'ext' => ['php']
    ],

需要在 vendor\swooletw\laravel-swoole\src\Server 中修改Manager.php

分别添加了watch($server)getDir($dir,&$file_list)两个方法,在onWorkerStart 中执行watch方法

<?php

namespace SwooleTW\Http\Server;

use Exception;
use Throwable;
use Swoole\Process;
use Swoole\Server\Task;
use Illuminate\Support\Str;
use Illuminate\Support\Arr;
use SwooleTW\Http\Helpers\OS;
use SwooleTW\Http\Server\Sandbox;
use SwooleTW\Http\Server\PidManager;
use SwooleTW\Http\Task\SwooleTaskJob;
use Illuminate\Support\Facades\Facade;
use SwooleTW\Http\Websocket\Websocket;
use SwooleTW\Http\Transformers\Request;
use SwooleTW\Http\Server\Facades\Server;
use SwooleTW\Http\Transformers\Response;
use SwooleTW\Http\Concerns\WithApplication;
use Illuminate\Contracts\Container\Container;
use Illuminate\Contracts\Debug\ExceptionHandler;
use SwooleTW\Http\Concerns\InteractsWithWebsocket;
use Symfony\Component\Console\Output\ConsoleOutput;
use SwooleTW\Http\Concerns\InteractsWithSwooleQueue;
use SwooleTW\Http\Concerns\InteractsWithSwooleTable;
use Symfony\Component\ErrorHandler\Error\FatalError;

/**
 * Class Manager
 */
class Manager
{
    use InteractsWithWebsocket,
        InteractsWithSwooleTable,
        InteractsWithSwooleQueue,
        WithApplication;

    /**
     * Container.
     *
     * @var \Illuminate\Contracts\Container\Container
     */
    protected $container;

    /**
     * @var string
     */
    protected $framework;

    /**
     * @var string
     */
    protected $basePath;

    /**
     * Server events.
     *
     * @var array
     */
    protected $events = [
        'start',
        'shutDown',
        'workerStart',
        'workerStop',
        'packet',
        'bufferFull',
        'bufferEmpty',
        'task',
        'finish',
        'pipeMessage',
        'workerError',
        'managerStart',
        'managerStop',
        'request',
    ];

    /**
     * HTTP server manager constructor.
     *
     * @param \Illuminate\Contracts\Container\Container $container
     * @param string $framework
     * @param string $basePath
     *
     * @throws \Exception
     */
    public function __construct(Container $container, $framework, $basePath = null)
    {
        $this->container = $container;
        $this->setFramework($framework);
        $this->setBasepath($basePath);
        $this->initialize();
    }

    /**
     * Run swoole server.
     */
    public function run()
    {
        $this->container->make(Server::class)->start();
    }

    /**
     * Stop swoole server.
     */
    public function stop()
    {
        $this->container->make(Server::class)->shutdown();
    }

    /**
     * Initialize.
     */
    protected function initialize()
    {
        $this->createTables();
        $this->prepareWebsocket();

        if (! $this->container->make(Server::class)->taskworker) {
            $this->setSwooleServerListeners();
        }
    }

    /**
     * Set swoole server listeners.
     */
    protected function setSwooleServerListeners()
    {
        $server = $this->container->make(Server::class);
        foreach ($this->events as $event) {
            $listener = Str::camel("on_$event");
            $callback = method_exists($this, $listener) ? [$this, $listener] : function () use ($event) {
                $this->container->make('events')->dispatch("swoole.$event", func_get_args());
            };

            $server->on($event, $callback);
        }
    }

    /**
     * "onStart" listener.
     */
    public function onStart()
    {
        $this->setProcessName('master process');

        $server = $this->container->make(Server::class);
        $this->container->make(PidManager::class)->write($server->master_pid, $server->manager_pid ?? 0);

        $this->container->make('events')->dispatch('swoole.start', func_get_args());
    }

    /**
     * The listener of "managerStart" event.
     *
     * @return void
     */
    public function onManagerStart()
    {
        $this->setProcessName('manager process');

        $this->container->make('events')->dispatch('swoole.managerStart', func_get_args());
    }
 
    /**
     * 对文件和目录进行监听
     *
     * @return void
     */
    protected function watch($server)
    {
        $watch = Arr::get(app()->make('config')->get('swoole_http'), 'watch_file');
        if ($watch['enabled']==false)
        {
            return ;
        }
        $notify = inotify_init();
        $dir = $watch['update_dir'];
        $file_list = $watch['input_file'];
        $this->fileExt = $watch['ext'];
        $this->getDir($dir,$file_list);
        // print_r($file_list);
        foreach ($file_list as $item) {
            inotify_add_watch($notify, $item, IN_CREATE | IN_DELETE | IN_MODIFY);
        }
        swoole_event_add($notify, function () use ($notify, $server) {
            $events = inotify_read($notify);
            if (!empty($events)) 
            {
                $server->reload();
            }
        });
    }
     
    /**
     * 获得文件路径
     *
     * @return void
     */
    protected function getDir($dir,&$file_list)
    {
        foreach ($dir as $dr) {
            foreach (array_diff(scandir($dr), array('.', '..')) as $item) 
            {
                if(is_dir($dr . '/' . $item)){
                    $dirArr = [$dr . '/' . $item];
                    $this->getDir($dirArr,$file_list);
                }else{
                    $ext = pathinfo($dr . '/' . $item, PATHINFO_EXTENSION);

                    if(in_array($ext,$this->fileExt)){
                        $file_list[] = $dr . '/' . $item;
                    }
                }
            }
        }
      }
    /**
     * "onWorkerStart" listener.
     *
     * @param \Swoole\Http\Server|mixed $server
     *
     * @throws \Exception
     */
    public function onWorkerStart($server)
    {
        $this->clearCache();

        $this->container->make('events')->dispatch('swoole.workerStart', func_get_args());

        $this->setProcessName($server->taskworker ? 'task process' : 'worker process');

        // 调用监视函数;
        $this->watch($server);
        // clear events instance in case of repeated listeners in worker process
        Facade::clearResolvedInstance('events');

        // prepare laravel app
        $this->getApplication();

        // bind after setting laravel app
        $this->bindToLaravelApp();

        // prepare websocket handler and routes
        if ($this->isServerWebsocket) {
            $this->prepareWebsocketHandler();
            $this->loadWebsocketRoutes();
        }
    }

    /**
     * "onRequest" listener.
     *
     * @param \Swoole\Http\Request $swooleRequest
     * @param \Swoole\Http\Response $swooleResponse
     */
    public function onRequest($swooleRequest, $swooleResponse)
    {
        $this->app->make('events')->dispatch('swoole.request');

        $this->resetOnRequest();
        $sandbox = $this->app->make(Sandbox::class);
        $handleStatic = $this->container->make('config')->get('swoole_http.server.handle_static_files', true);
        $publicPath = $this->container->make('config')->get('swoole_http.server.public_path', base_path('public'));

        try {
            // handle static file request first
            if ($handleStatic && Request::handleStatic($swooleRequest, $swooleResponse, $publicPath)) {
                return;
            }
            // transform swoole request to illuminate request
            $illuminateRequest = Request::make($swooleRequest)->toIlluminate();

            // set current request to sandbox
            $sandbox->setRequest($illuminateRequest);

            // enable sandbox
            $sandbox->enable();

            // handle request via laravel/lumen's dispatcher
            $illuminateResponse = $sandbox->run($illuminateRequest);

            // send response
            Response::make($illuminateResponse, $swooleResponse, $swooleRequest)->send();
        } catch (Throwable $e) {
            try {
                $exceptionResponse = $this->app
                    ->make(ExceptionHandler::class)
                    ->render(
                        $illuminateRequest,
                        $this->normalizeException($e)
                    );
                Response::make($exceptionResponse, $swooleResponse, $swooleRequest)->send();
            } catch (Throwable $e) {
                $this->logServerError($e);
            }
        } finally {
            // disable and recycle sandbox resource
            $sandbox->disable();
        }
    }

    /**
     * Reset on every request.
     */
    protected function resetOnRequest()
    {
        // Reset websocket data
        if ($this->isServerWebsocket) {
            $this->app->make(Websocket::class)->reset(true);
        }
    }

    /**
     * Set onTask listener.
     *
     * @param mixed $server
     * @param string|\Swoole\Server\Task $taskId or $task
     * @param string|null $srcWorkerId
     * @param mixed|null $data
     */
    public function onTask($server, $task, $srcWorkerId = null, $data = null)
    {
        if ($task instanceof Task) {
            $data = $task->data;
            $srcWorkerId = $task->worker_id;
            $taskId = $task->id;
        } else {
            $taskId = $task;
        }

        $this->container->make('events')->dispatch('swoole.task', func_get_args());

        try {
            // push websocket message
            if ($this->isWebsocketPushPayload($data)) {
                $this->pushMessage($server, $data['data']);
            // push async task to queue
            } elseif ($this->isAsyncTaskPayload($data)) {
                (new SwooleTaskJob($this->container, $server, $data, $taskId, $srcWorkerId))->fire();
            }
        } catch (Throwable $e) {
            $this->logServerError($e);
        }
    }

    /**
     * Set onFinish listener.
     *
     * @param mixed $server
     * @param string $taskId
     * @param mixed $data
     */
    public function onFinish($server, $taskId, $data)
    {
        // task worker callback
        $this->container->make('events')->dispatch('swoole.finish', func_get_args());

        return;
    }

    /**
     * Set onShutdown listener.
     */
    public function onShutdown()
    {
        $this->container->make(PidManager::class)->delete();
    }

    /**
     * Set bindings to Laravel app.
     */
    protected function bindToLaravelApp()
    {
        $this->bindSandbox();
        $this->bindSwooleTable();

        if ($this->isServerWebsocket) {
            $this->bindRoom();
            $this->bindWebsocket();
        }
    }

    /**
     * Bind sandbox to Laravel app container.
     */
    protected function bindSandbox()
    {
        $this->app->singleton(Sandbox::class, function ($app) {
            return new Sandbox($app, $this->framework);
        });

        $this->app->alias(Sandbox::class, 'swoole.sandbox');
    }

    /**
     * Clear APC or OPCache.
     */
    protected function clearCache()
    {
        if (extension_loaded('apc')) {
            apc_clear_cache();
        }

        if (extension_loaded('Zend OPcache')) {
            opcache_reset();
        }
    }

    /**
     * Set process name.
     *
     * @codeCoverageIgnore
     *
     * @param $process
     */
    protected function setProcessName($process)
    {
        // MacOS doesn't support modifying process name.
        if (OS::is(OS::MAC_OS, OS::CYGWIN) || $this->isInTesting()) {
            return;
        }
        $serverName = 'swoole_http_server';
        $appName = $this->container->make('config')->get('app.name', 'Laravel');

        $name = sprintf('%s: %s for %s', $serverName, $process, $appName);

        swoole_set_process_name($name);
    }

    /**
     * Add process to http server
     *
     * @param \Swoole\Process $process
     */
    public function addProcess(Process $process): void
    {
        $this->container->make(Server::class)->addProcess($process);
    }

    /**
     * Indicates if it's in phpunit environment.
     *
     * @return bool
     */
    protected function isInTesting()
    {
        return defined('IN_PHPUNIT') && IN_PHPUNIT;
    }

    /**
     * Log server error.
     *
     * @param \Throwable|\Exception $e
     */
    public function logServerError(Throwable $e)
    {
        if ($this->isInTesting()) {
            return;
        }

        $exception = $this->normalizeException($e);
        $this->container->make(ConsoleOutput::class)
            ->writeln(sprintf("<error>%s</error>", $exception));

        $this->container->make(ExceptionHandler::class)
            ->report($exception);
    }

    /**
     * Normalize a throwable/exception to exception.
     *
     * @param \Throwable|\Exception $e
     */
    protected function normalizeException(Throwable $e)
    {
        if (! $e instanceof Exception) {
            if ($e instanceof \ParseError) {
                $severity = E_PARSE;
            } elseif ($e instanceof \TypeError) {
                $severity = E_RECOVERABLE_ERROR;
            } else {
                $severity = E_ERROR;
            }

            $error = [
                'type' => $severity,
                'message' => $e->getMessage(),
                'file' => $e->getFile(),
                'line' => $e->getLine(),
            ];

            $e = new FatalError($e->getMessage(), $e->getCode(), $error, null, true, $e->getTrace());
        }

        return $e;
    }

    /**
     * Indicates if the payload is async task.
     *
     * @param mixed $payload
     *
     * @return boolean
     */
    protected function isAsyncTaskPayload($payload): bool
    {
        $data = json_decode($payload, true);

        if (JSON_ERROR_NONE !== json_last_error()) {
            return false;
        }

        return isset($data['job']);
    }
}


 

以上,基本是遍历了整个需要监听的文件夹,如果改动让swoole reload

希望有更好、更简单的方法。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,下面是详细的步骤: 1. 安装 swoole 扩展 在终端中运行以下命令进行 swoole 扩展的安装: ``` pecl install swoole ``` 2. 创建 Laravel 项目 使用以下命令在终端中创建 Laravel 项目: ``` composer create-project --prefer-dist laravel/laravel chat-app ``` 3. 安装 Laravel-Swoole 在终端中运行以下命令进行 Laravel-Swoole 的安装: ``` composer require swooletw/laravel-swoole ``` 然后,运行以下命令生成配置文件: ``` php artisan vendor:publish --tag=laravel-swoole ``` 4. 创建聊天控制器 在终端中运行以下命令创建聊天控制器: ``` php artisan make:controller ChatController ``` 在 `ChatController` 中添加以下方法: ```php public function index() { return view('chat'); } public function sendMessage(Request $request) { $message = $request->input('message'); // 发送消息给所有连接的客户端 app('swoole')->sendMessage($message); return response()->json(['status' => 'success']); } ``` `index` 方法用于返回聊天页面视图,`sendMessage` 方法用于接收客户端发送的消息并将其发送给所有已连接的客户端。 5. 创建聊天视图 在 `resources/views` 目录下创建 `chat.blade.php` 文件,添加以下代码: ```html <!DOCTYPE html> <html> <head> <title>Laravel Swoole Chat App</title> </head> <body> <h1>Welcome to Laravel Swoole Chat App</h1> <div id="messages"></div> <input type="text" id="message" placeholder="Type your message here..."> <button id="send">Send</button> <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script> <script> var ws = new WebSocket('ws://' + window.location.hostname + ':9501'); ws.onmessage = function(event) { var message = event.data; var div = document.createElement('div'); div.innerHTML = message; document.getElementById('messages').appendChild(div); }; $('#send').click(function() { var message = $('#message').val(); ws.send(message); $('#message').val(''); }); </script> </body> </html> ``` 6. 设置路由 在 `routes/web.php` 文件中添加以下代码: ```php use App\Http\Controllers\ChatController; Route::get('/chat', [ChatController::class, 'index']); Route::post('/chat/send-message', [ChatController::class, 'sendMessage']); ``` 7. 启动服务器 在终端中运行以下命令启动服务器: ``` php artisan swoole:http start ``` 现在,你可以在浏览器中访问 `http://localhost:1215/chat` 查看聊天页面了。可以在多个浏览器窗口中打开该页面并尝试发送消息,消息将会实时显示在其他窗口中。 希望这些步骤可以帮助到你实现 Laravel8.5 swoole 实现网页聊天。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值