Deprecated: htmlspecialchars(): Passing null to parameter #1 ($string) of type string is deprecated in /webData/hua/www.ascwh.com/var/Typecho/Feed.php on line 221
ASCWH - Swoole https://www.ascwh.com/tag/Swoole/ EasySwoole 服务热重启 https://www.ascwh.com/380.html 2021-01-03T15:10:00+08:00 由于 swoole 常驻内存的特性,修改文件后需要重启worker进程才能将被修改的文件重新载入内存中,我们可以自定义Process的方式实现文件变动自动进行服务重载新建文件 app/Process/HotReload.php 并添加如下内容,也可以放在其他位置,请对应命名空间<?php namespace App\Process; use EasySwoole\Component\Process\AbstractProcess; use EasySwoole\EasySwoole\ServerManager; use EasySwoole\Utility\File; use Swoole\Table; use Swoole\Timer; /** * Class HotReload * @package App\Process */ class HotReload extends AbstractProcess { protected $table; protected $isReady = false; protected $monitorDir; // 需要监控的目录 protected $monitorExt; // 需要监控的后缀 /** * 启动定时器进行循环扫描 */ public function run($arg) { // 此处指定需要监视的目录 建议只监视App目录下的文件变更 $this->monitorDir = !empty($arg['monitorDir']) ? $arg['monitorDir'] : EASYSWOOLE_ROOT . '/App'; // 指定需要监控的扩展名 不属于指定类型的的文件 无视变更 不重启 $this->monitorExt = !empty($arg['monitorExt']) && is_array($arg['monitorExt']) ? $arg['monitorExt'] : ['php']; if (extension_loaded('inotify') && empty($arg['disableInotify'])) { // 扩展可用 优先使用扩展进行处理 $this->registerInotifyEvent(); echo "server hot reload start : use inotify\n"; } else { // 扩展不可用时 进行暴力扫描 $this->table = new Table(512); $this->table->column('mtime', Table::TYPE_INT, 4); $this->table->create(); $this->runComparison(); Timer::tick(1000, function () { $this->runComparison(); }); echo "server hot reload start : use timer tick comparison\n"; } } /** * 扫描文件变更 */ private function runComparison() { $startTime = microtime(true); $doReload = false; $dirIterator = new \RecursiveDirectoryIterator($this->monitorDir); $iterator = new \RecursiveIteratorIterator($dirIterator); $inodeList = []; // 迭代目录全部文件进行检查 foreach ($iterator as $file) { /** @var \SplFileInfo $file */ $ext = $file->getExtension(); if (!in_array($ext, $this->monitorExt)) { continue; // 只检查指定类型 } else { // 由于修改文件名称 并不需要重新载入 可以基于inode进行监控 $inode = $file->getInode(); $mtime = $file->getMTime(); array_push($inodeList, $inode); if (!$this->table->exist($inode)) { // 新建文件或修改文件 变更了inode $this->table->set($inode, ['mtime' => $mtime]); $doReload = true; } else { // 修改文件 但未发生inode变更 $oldTime = $this->table->get($inode)['mtime']; if ($oldTime != $mtime) { $this->table->set($inode, ['mtime' => $mtime]); $doReload = true; } } } } foreach ($this->table as $inode => $value) { // 迭代table寻找需要删除的inode if (!in_array(intval($inode), $inodeList)) { $this->table->del($inode); $doReload = true; } } if ($doReload) { $count = $this->table->count(); $time = date('Y-m-d H:i:s'); $usage = round(microtime(true) - $startTime, 3); if (!$this->isReady == false) { // 监测到需要进行热重启 echo "severReload at {$time} use : {$usage} s total: {$count} files\n"; ServerManager::getInstance()->getSwooleServer()->reload(); } else { // 首次扫描不需要进行重启操作 echo "hot reload ready at {$time} use : {$usage} s total: {$count} files\n"; $this->isReady = true; } } } /** * 注册Inotify监听事件 */ private function registerInotifyEvent() { // 因为进程独立 且当前是自定义进程 全局变量只有该进程使用 // 在确定不会造成污染的情况下 也可以合理使用全局变量 global $lastReloadTime; global $inotifyResource; $lastReloadTime = 0; $files = File::scanDirectory(EASYSWOOLE_ROOT . '/App'); $files = array_merge($files['files'], $files['dirs']); $inotifyResource = inotify_init(); // 为当前所有的目录和文件添加事件监听 foreach ($files as $item) { inotify_add_watch($inotifyResource, $item, IN_CREATE | IN_DELETE | IN_MODIFY); } // 加入事件循环 swoole_event_add($inotifyResource, function () { global $lastReloadTime; global $inotifyResource; $events = inotify_read($inotifyResource); if ($lastReloadTime < time() && !empty($events)) { // 限制1s内不能进行重复reload $lastReloadTime = time(); ServerManager::getInstance()->getSwooleServer()->reload(); } }); } public function onShutDown() { // TODO: Implement onShutDown() method. } public function onReceive(string $str) { // TODO: Implement onReceive() method. } }添加好后在全局的 EasySwooleEvent.php 中,注册该自定义进程引入use App\Process\HotReload;mainServerCreate 方法中public static function mainServerCreate(EventRegister $register) { /***************** 服务热重启 *****************/ $swooleServer = ServerManager::getInstance()->getSwooleServer(); $swooleServer->addProcess((new HotReload('HotReload', ['disableInotify' => false]))->getProcess()); } ThinkPHP5.1+ Swoole 实现 websocket https://www.ascwh.com/338.html 2019-11-25T21:17:00+08:00 SwooleSwoole是一个面向生产环境的 PHP 异步网络通信引擎。使 PHP 开发人员可以编写高性能的异步并发 TCP、UDP、Unix Socket、HTTP,WebSocket 服务。安装首先按照Swoole官网说明安装swoole扩展,然后安装think-swoole扩展。composer require topthink/think-swoole=2.0.*安装之后会在 config 目录下生成两个配置文件 swoole.phpswoole_server.php两者 作用不用 http 跟 socket使用 socket 看 swoole_server.php这是默认配置return [ // 扩展自身配置 'host' => '0.0.0.0', // 监听地址 'port' => 9501, // 监听端口 'type' => 'socket', // 服务类型 支持 socket http server 'mode' => SWOOLE_PROCESS, 'socket_type' => SWOOLE_SOCK_TCP, // 可以支持swoole的所有配置参数 'daemonize' => false, // 事件回调定义 'onOpen' => function ($server, $request) { echo "server: handshake success with fd{$request->fd}\n"; }, 'onMessage' => function ($server, $frame) { echo "receive from {$frame->fd}:{$frame->data},opcode:{$frame->opcode},fin:{$frame->finish}\n"; $server->push($frame->fd, "this is server"); }, 'onRequest' => function ($request, $response) { $response->end("<h1>Hello Swoole. #" . rand(1000, 9999) . "</h1>"); }, 'onClose' => function ($ser, $fd) { echo "client {$fd} closed\n"; }, ];也可自行定义return [ 'swoole_class' => 'socket\Swoole\PustServer', ];自定义类需继承 \think\swoole\Server支持响应事件['Start', 'Shutdown', 'WorkerStart', 'WorkerStop', 'WorkerExit', 'Connect', 'Receive', 'Packet', 'Close', 'BufferFull', 'BufferEmpty', 'Task', 'Finish', 'PipeMessage', 'WorkerError', 'ManagerStart', 'ManagerStop', 'Open', 'Message', 'HandShake', 'Request']简单的 DEMO namespace socket\Swoole; use \think\swoole\Server; use socket\Swoole\libs\MessageHandler; class PustServer extends Server { protected $host = '0.0.0.0'; //监听所有地址 protected $port = 9501; //监听9501端口 protected $serverType = 'socket'; protected $option = [ 'worker_num' => 4, //设置启动的Worker进程数 'daemonize' => false, //守护进程化(上线改为true) 'backlog' => 128, //Listen队列长度 'dispatch_mode' => 2, //固定模式,保证同一个连接发来的数据只会被同一个worker处理 //心跳检测:每60秒遍历所有连接,强制关闭10分钟内没有向服务器发送任何数据的连接 'heartbeat_check_interval' => 60, 'heartbeat_idle_time' => 600 ]; private $messageHandler; /** * @title init 启动之前执行 */ protected function init() { parent::init(); } /** * @title onWorkerStart 此事件在Worker进程/Task进程启动时发生。这里创建的对象可以在进程生命周期内使用 */ public function onWorkerStart() { $this->messageHandler = new MessageHandler(); } /** * @title onOpen 建立连接时回调函数 * * @param $server * @param $req */ public function onOpen($server, $request) { echo "server: handshake success with fd{$request->fd}\n"; } /** * @title onMessage 接收数据时回调函数 * * @param $server * @param $frame */ public function onMessage($server, $frame) { // 重新执行 WorkerStart 不再需要在此命令重启 用户开发模式 $this->swoole->reload(); $data = json_decode($frame->data, true); $fd = $frame->fd; if (empty($data) && !is_object($data)) { //仅推送给当前连接用户 $server->push($fd, json_encode(['error' => 422, 'msg' => '参数错误'])); } if (method_exists($this->messageHandler, $data['act'])) { call_user_func([$this->messageHandler, $data['act']], $server, $frame); } } /** * @title onClose 连接关闭时回调函数 * * @param $server * @param $fd */ public function onClose($server, $fd) { echo "标识{$fd}关闭了连接\n"; } }messageHandler 为自定义处理逻辑namespace socket\Swoole\libs; class MessageHandler { public function single($server, $frame) { //仅推送给当前连接用户 $server->push($frame->fd, json_encode(['OK'])); } /** * @title group * * @param $server * @param $frame */ public function group($server, $frame) { //推送给全部连接用户 foreach ($server->connections as $fd) { $server->push($fd, json_encode([$fd . '===>OK'])); } } }客户端 websocket 自行处理 可以自行定义客户端认证等操作 自行定义 json 或 get 形式