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());
}
]]>Swoole是一个面向生产环境的 PHP 异步网络通信引擎。使 PHP 开发人员可以编写高性能的异步并发 TCP、UDP、Unix Socket、HTTP,WebSocket 服务。
安装
首先按照Swoole
官网说明安装swoole
扩展,然后安装think-swoole
扩展。
composer require topthink/think-swoole=2.0.*
安装之后会在 config 目录下生成两个配置文件 swoole.php
swoole_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']
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 形式
]]>