diff --git a/src/ws_server.php b/src/ws_server.php index 086d07fc8..34979c18a 100644 --- a/src/ws_server.php +++ b/src/ws_server.php @@ -10,6 +10,89 @@ error_reporting(E_ALL); require __DIR__ . '/vendor/autoload.php'; +// 引入应用公共文件,包含log_write函数 +require __DIR__ . '/app/common.php'; + +/** + * WebSocket服务器日志封装函数 + * 简化log_write函数的调用,自动添加WebSocket服务器前缀 + * + * @param string $message 日志内容 + * @param string $level 日志级别:info, warning, error + * @param int $callerDep 调用栈深度,默认1 + * @return void + */ +function ws_log(string $message, string $level = 'info', int $callerDep = 1): void +{ + log_write($message, $level, 'websocket-server.log', $callerDep + 1); +} + +/** + * WebSocket服务器错误日志封装函数 + * 简化错误日志的记录,自动添加WebSocket服务器前缀 + * + * @param string $message 错误信息 + * @param int $callerDep 调用栈深度,默认1 + * @return void + */ +function ws_error(string $message, int $callerDep = 1): void +{ + log_write($message, 'error', 'websocket-server.log', $callerDep + 1); +} + +/** + * WebSocket服务器警告日志封装函数 + * 简化警告日志的记录,自动添加WebSocket服务器前缀 + * + * @param string $message 警告信息 + * @param int $callerDep 调用栈深度,默认1 + * @return void + */ +function ws_warning(string $message, int $callerDep = 1): void +{ + log_write($message, 'warning', 'websocket-server.log', $callerDep + 1); +} + +/** + * WebSocket服务器信息日志封装函数 + * 简化信息日志的记录,自动添加WebSocket服务器前缀 + * + * @param string $message 信息内容 + * @param int $callerDep 调用栈深度,默认1 + * @return void + */ +function ws_info(string $message, int $callerDep = 1): void +{ + log_write($message, 'info', 'websocket-server.log', $callerDep + 1); +} + +/** + * WebSocket服务器输出封装函数 + * 同时处理日志记录和控制台输出,使代码更简洁 + * + * @param string $message 输出内容 + * @param string $level 日志级别:info, warning, error + * @param int $callerDep 调用栈深度,默认1 + * @return void + */ +function ws_echo(string $message, string $level = 'info', int $callerDep = 1): void +{ + // 调用相应的日志函数 + switch ($level) { + case 'error': + ws_error($message, $callerDep + 1); + break; + case 'warning': + ws_warning($message, $callerDep + 1); + break; + default: + ws_info($message, $callerDep + 1); + break; + } + // 输出到控制台 + echo $message . "\n"; +} + // 初始化ThinkPHP应用环境,加载配置和环境变量,主要用于数据库连接和其他配置 use think\App; @@ -77,8 +160,13 @@ if ($httpHost === '') { $port = getenv('WS_PORT') ?: 8080; // WebSocket服务器端口 $address = '0.0.0.0'; // 监听所有网络接口 +// 记录服务器启动信息 +ws_echo("[WebSocket服务器] 正在启动..."); +ws_echo("[WebSocket服务器] 配置信息: HTTP_HOST={$httpHost}, PORT={$port}, ADDRESS={$address}"); + // 创建WebSocket服务器应用 - 参数顺序:httpHost, port, address $ratchetApp = new RatchetApp($httpHost, $port, $address); +ws_echo("[WebSocket服务器] 应用创建成功"); // 获取所有addon(与InitAddon.php逻辑保持一致) // 1. 从数据库获取插件列表 @@ -97,10 +185,10 @@ try { $cachedAddons = $cache->get($cacheKey); if ($cachedAddons !== null && !empty($cachedAddons)) { - echo "[WebSocket服务器] 从缓存获取插件列表\n"; + ws_echo("[WebSocket服务器] 从缓存获取插件列表"); $current_addons = $cachedAddons; } else { - echo "[WebSocket服务器] 从数据库获取插件列表\n"; + ws_echo("[WebSocket服务器] 从数据库获取插件列表"); // 尝试获取数据库连接并确保连接有效 try { @@ -111,10 +199,10 @@ try { // 将结果存入缓存 $cache->set($cacheKey, $current_addons, $cacheExpire); - echo "[WebSocket服务器] 插件列表已缓存(有效期: {$cacheExpire}秒)\n"; + ws_echo("[WebSocket服务器] 插件列表已缓存(有效期: {$cacheExpire}秒)"); } catch (\Exception $dbEx) { - echo "[WebSocket服务器] 数据库操作失败: {$dbEx->getMessage()}\n"; - echo "[WebSocket服务器] 尝试重新初始化数据库连接...\n"; + ws_echo("[WebSocket服务器] 数据库操作失败: {$dbEx->getMessage()}", 'error'); + ws_echo("[WebSocket服务器] 尝试重新初始化数据库连接..."); // 尝试重新初始化应用和数据库连接 try { @@ -129,10 +217,10 @@ try { // 将结果存入缓存 $cache->set($cacheKey, $current_addons, $cacheExpire); - echo "[WebSocket服务器] 重新连接数据库成功,插件列表已缓存\n"; + ws_echo("[WebSocket服务器] 重新连接数据库成功,插件列表已缓存"); } catch (\Exception $retryEx) { - echo "[WebSocket服务器] 重新连接数据库失败: {$retryEx->getMessage()}\n"; - echo "[WebSocket服务器] 回退到直接扫描目录获取插件列表\n"; + ws_echo("[WebSocket服务器] 重新连接数据库失败: {$retryEx->getMessage()}", 'error'); + ws_echo("[WebSocket服务器] 回退到直接扫描目录获取插件列表"); // 回退到直接扫描目录 $addonNames = []; @@ -183,25 +271,25 @@ try { } } - echo "[WebSocket服务器] 从数据库获取的插件列表: " . implode(', ', $current_addon_names) . "\n"; - echo "[WebSocket服务器] 已启用的插件: " . implode(', ', $enabled_addons) . "\n"; - echo "[WebSocket服务器] 目录中存在的插件: " . implode(', ', $dir_addon_names) . "\n"; + ws_echo("[WebSocket服务器] 从数据库获取的插件列表: " . implode(', ', $current_addon_names)); + ws_echo("[WebSocket服务器] 已启用的插件: " . implode(', ', $enabled_addons)); + ws_echo("[WebSocket服务器] 目录中存在的插件: " . implode(', ', $dir_addon_names)); // 5. 比较数据库和目录插件列表的差异 $db_only_addons = array_diff($db_addon_names, $dir_addon_names); $dir_only_addons = array_diff($dir_addon_names, $db_addon_names); if (!empty($db_only_addons)) { - echo "[WebSocket服务器] 数据库中存在但目录中不存在的插件: " . implode(', ', $db_only_addons) . "\n"; + ws_echo("[WebSocket服务器] 数据库中存在但目录中不存在的插件: " . implode(', ', $db_only_addons)); } if (!empty($dir_only_addons)) { - echo "[WebSocket服务器] 目录中存在但数据库中不存在的插件: " . implode(', ', $dir_only_addons) . "\n"; + ws_echo("[WebSocket服务器] 目录中存在但数据库中不存在的插件: " . implode(', ', $dir_only_addons)); } } catch (\Exception $e) { - echo "[WebSocket服务器] 获取插件列表失败: " . $e->getMessage() . "\n"; - echo "[WebSocket服务器] 回退到直接扫描目录获取插件列表\n"; + ws_echo("[WebSocket服务器] 获取插件列表失败: " . $e->getMessage(), 'error'); + ws_echo("[WebSocket服务器] 回退到直接扫描目录获取插件列表"); // 回退到直接扫描目录 $addonNames = []; @@ -229,14 +317,14 @@ $missingDirAddons = []; foreach ($current_addon_names as $addonName) { // 检查插件是否已启用 if (!in_array($addonName, $enabled_addons)) { - echo "[{$addonName}] 插件未启用,跳过WebSocket控制器注册\n"; + ws_echo("[{$addonName}] 插件未启用,跳过WebSocket控制器注册"); $disabledAddons[] = $addonName; continue; } // 检查插件目录是否存在 if (!is_dir($addonDir . '/' . $addonName)) { - echo "[{$addonName}] 插件目录不存在,跳过WebSocket控制器注册\n"; + ws_echo("[{$addonName}] 插件目录不存在,跳过WebSocket控制器注册"); $missingDirAddons[] = $addonName; continue; } @@ -250,14 +338,14 @@ foreach ($current_addon_names as $addonName) { $path = '/ws/' . $addonName; // 允许任意 Origin,并且不限制 Host(支持通过任意 IP/域名访问) $ratchetApp->route($path, new $webSocketClass(), array('*'), ''); - echo "已注册WebSocket控制器:{$webSocketClass} 到路径 {$path}\n"; + ws_echo("已注册WebSocket控制器:{$webSocketClass} 到路径 {$path}"); $registeredAddons[] = $addonName; } else { - echo "[{$addonName}] WebSocket控制器不存在 ({$webSocketClass})\n"; + ws_echo("[{$addonName}] WebSocket控制器不存在 ({$webSocketClass})"); $unregisteredAddons[] = $addonName; } } catch (\Exception $e) { - echo "[{$addonName}] 检查WebSocket控制器时出错:{$e->getMessage()}\n"; + ws_echo("[{$addonName}] 检查WebSocket控制器时出错:{$e->getMessage()}", 'error'); $unregisteredAddons[] = $addonName; } } @@ -275,7 +363,7 @@ class DefaultWebSocketController implements MessageComponentInterface public function onOpen(ConnectionInterface $conn) { $this->clients->attach($conn); - echo "[默认路径] New connection! ({$conn->resourceId})\n"; + ws_echo("[默认路径] New connection! ({$conn->resourceId})"); $conn->send(json_encode([ 'type' => 'welcome', 'message' => '欢迎连接到默认WebSocket测试路径', @@ -285,7 +373,7 @@ class DefaultWebSocketController implements MessageComponentInterface public function onMessage(ConnectionInterface $conn, $msg) { - echo "[默认路径] Received message from {$conn->resourceId}: $msg\n"; + ws_echo("[默认路径] Received message from {$conn->resourceId}: $msg"); try { $data = json_decode($msg, true); if (isset($data['action']) && $data['action'] === 'ping') { @@ -298,6 +386,7 @@ class DefaultWebSocketController implements MessageComponentInterface ])); } } catch (\Exception $e) { + ws_echo("[默认路径] 解析消息失败: {$e->getMessage()}", 'error'); $conn->send(json_encode(['type' => 'error', 'message' => '解析消息失败: ' . $e->getMessage()])); } } @@ -305,12 +394,12 @@ class DefaultWebSocketController implements MessageComponentInterface public function onClose(ConnectionInterface $conn) { $this->clients->detach($conn); - echo "[默认路径] Connection {$conn->resourceId} has disconnected\n"; + ws_echo("[默认路径] Connection {$conn->resourceId} has disconnected"); } public function onError(ConnectionInterface $conn, \Exception $e) { - echo "[默认路径] An error has occurred: {$e->getMessage()}\n"; + ws_echo("[默认路径] An error has occurred: {$e->getMessage()}", 'error'); $conn->close(); } } @@ -318,7 +407,7 @@ class DefaultWebSocketController implements MessageComponentInterface // 注册默认的/ws路径测试控制器 // 默认测试路径同样不限制 Host $ratchetApp->route('/ws', new DefaultWebSocketController(), array('*'), ''); -echo "已注册默认WebSocket测试控制器到路径 /ws\n"; +ws_echo("已注册默认WebSocket测试控制器到路径 /ws"); // 缓存WebSocket服务器信息(可选,用于其他服务查询) $serverInfoKey = 'websocket_server_info'; @@ -330,123 +419,124 @@ $cache->set($serverInfoKey, [ 'registered_addons' => $registeredAddons ], 0); // 0表示永久缓存,直到手动删除 -echo "WebSocket服务器已启动,监听地址: ws://{$httpHost}:{$port}\n"; +ws_echo("WebSocket服务器已启动,监听地址: ws://{$httpHost}:{$port}"); // 显示已注册WebSocket控制器的addon路径 -echo "\n已注册WebSocket控制器的addon路径:\n"; +ws_echo("\n已注册WebSocket控制器的addon路径:"); if (!empty($registeredAddons)) { foreach ($registeredAddons as $addonName) { - echo " - ws://{$httpHost}:{$port}/ws/{$addonName} (已注册)\n"; + ws_echo(" - ws://{$httpHost}:{$port}/ws/{$addonName} (已注册)"); } } else { - echo " - 暂无已注册的WebSocket控制器\n"; + ws_echo(" - 暂无已注册的WebSocket控制器"); } // 显示未注册WebSocket控制器的addon路径 -echo "\n未注册WebSocket控制器的addon路径:\n"; +ws_echo("\n未注册WebSocket控制器的addon路径:"); if (!empty($unregisteredAddons)) { foreach ($unregisteredAddons as $addonName) { - echo " - ws://{$httpHost}:{$port}/ws/{$addonName} (未注册)\n"; + ws_echo(" - ws://{$httpHost}:{$port}/ws/{$addonName} (未注册)"); } } else { - echo " - 所有已启用且目录存在的addon都已注册WebSocket控制器\n"; + ws_echo(" - 所有已启用且目录存在的addon都已注册WebSocket控制器"); } // 显示未启用的addon -echo "\n未启用的addon:\n"; +ws_echo("\n未启用的addon:"); if (!empty($disabledAddons)) { foreach ($disabledAddons as $addonName) { - echo " - {$addonName} (未启用)\n"; + ws_echo(" - {$addonName} (未启用)"); } } else { - echo " - 所有addon都已启用\n"; + ws_echo(" - 所有addon都已启用"); } // 显示目录不存在的addon -echo "\n目录不存在的addon:\n"; +ws_echo("\n目录不存在的addon:"); if (!empty($missingDirAddons)) { foreach ($missingDirAddons as $addonName) { - echo " - {$addonName} (目录不存在)\n"; + ws_echo(" - {$addonName} (目录不存在)"); } } else { - echo " - 所有已启用的addon目录都存在\n"; + ws_echo(" - 所有已启用的addon目录都存在"); } // 添加定期检查数据库连接的机制 // 创建一个单独的进程来定期检查和维护数据库连接 // 添加信号处理,确保当父进程停止时,子进程也会被终止 if (extension_loaded('pcntl')) { - // 记录子进程PID - $dbMaintenancePid = null; - - // 创建数据库连接维护子进程 - $pid = pcntl_fork(); - if ($pid == -1) { - echo "[WebSocket服务器] 无法创建子进程来维护数据库连接\n"; - } elseif ($pid == 0) { - // 子进程:定期检查数据库连接 - echo "[WebSocket服务器] 启动数据库连接维护进程\n"; + // 记录子进程PID + $dbMaintenancePid = null; - // 每30秒检查一次数据库连接 - $checkInterval = 30; // 秒 - - // 设置子进程的信号处理 - pcntl_signal(SIGTERM, function() { - echo "[数据库维护子进程] 收到终止信号,正在退出...\n"; - exit(0); - }); - - while (true) { - // 检查是否有信号需要处理 - pcntl_signal_dispatch(); + // 创建数据库连接维护子进程 + $pid = pcntl_fork(); + if ($pid == -1) { + ws_echo("[WebSocket服务器] 无法创建子进程来维护数据库连接", 'error'); + } elseif ($pid == 0) { + // 子进程:定期检查数据库连接 + ws_echo("[WebSocket服务器] 启动数据库连接维护进程"); - try { - // 尝试执行一个简单的数据库查询来测试连接 - $addon_model = new Addon(); - $addon_model->getAddonList([], 'name', 1, 1); // 只查询一条记录 - echo "[数据库维护子进程] 数据库连接正常\n"; - } catch (\Exception $e) { - echo "[数据库维护子进程] 数据库连接异常: {$e->getMessage()}\n"; - echo "[数据库维护子进程] 尝试重新初始化数据库连接...\n"; + // 每30秒检查一次数据库连接 + $checkInterval = 30; // 秒 + + // 设置子进程的信号处理 + pcntl_signal(SIGTERM, function() { + ws_echo("[数据库维护子进程] 收到终止信号,正在退出..."); + exit(0); + }); + + while (true) { + // 检查是否有信号需要处理 + pcntl_signal_dispatch(); try { - // 重新初始化应用和数据库连接 - $app->initialize(); - $cache = $app->cache; - echo "[数据库维护子进程] 重新初始化应用成功\n"; - } catch (\Exception $retryEx) { - echo "[数据库维护子进程] 重新初始化应用失败: {$retryEx->getMessage()}\n"; + // 尝试执行一个简单的数据库查询来测试连接 + $addon_model = new Addon(); + $addon_model->getAddonList([], 'name', 1, 1); // 只查询一条记录 + ws_echo("[数据库维护子进程] 数据库连接正常"); + } catch (\Exception $e) { + ws_echo("[数据库维护子进程] 数据库连接异常: {$e->getMessage()}", 'error'); + ws_echo("[数据库维护子进程] 尝试重新初始化数据库连接..."); + + try { + // 重新初始化应用和数据库连接 + $app->initialize(); + $cache = $app->cache; + ws_echo("[数据库维护子进程] 重新初始化应用成功"); + } catch (\Exception $retryEx) { + ws_echo("[数据库维护子进程] 重新初始化应用失败: {$retryEx->getMessage()}", 'error'); + } } + + // 等待指定的时间间隔 + sleep($checkInterval); } + } else { + // 父进程:记录子进程PID并设置信号处理 + $dbMaintenancePid = $pid; - // 等待指定的时间间隔 - sleep($checkInterval); + // 设置父进程的信号处理 + pcntl_signal(SIGINT, function() use ($dbMaintenancePid) { + ws_echo("[WebSocket服务器] 收到终止信号,正在停止..."); + + // 如果子进程存在,发送终止信号 + if ($dbMaintenancePid) { + ws_echo("[WebSocket服务器] 停止数据库连接维护进程"); + posix_kill($dbMaintenancePid, SIGTERM); + // 等待子进程退出 + pcntl_wait($status); + } + + ws_echo("[WebSocket服务器] 已停止"); + exit(0); + }); } - } else { - // 父进程:记录子进程PID并设置信号处理 - $dbMaintenancePid = $pid; - - // 设置父进程的信号处理 - pcntl_signal(SIGINT, function() use ($dbMaintenancePid) { - echo "[WebSocket服务器] 收到终止信号,正在停止...\n"; - - // 如果子进程存在,发送终止信号 - if ($dbMaintenancePid) { - echo "[WebSocket服务器] 停止数据库连接维护进程\n"; - posix_kill($dbMaintenancePid, SIGTERM); - // 等待子进程退出 - pcntl_wait($status); - } - - echo "[WebSocket服务器] 已停止\n"; - exit(0); - }); } -} // 运行服务器 -echo "[WebSocket服务器] 启动主服务器进程\n"; -echo "\n默认测试路径:\n"; -echo " - ws://{$httpHost}:{$port}/ws (默认路径,用于连接测试)\n"; -echo "按 Ctrl+C 停止服务器\n"; +ws_echo("[WebSocket服务器] 启动主服务器进程"); +ws_echo("\n默认测试路径:"); +ws_echo(" - ws://{$httpHost}:{$port}/ws (默认路径,用于连接测试)"); +ws_echo("按 Ctrl+C 停止服务器"); +ws_info("WebSocket服务器已启动,监听地址: ws://{$httpHost}:{$port}"); $ratchetApp->run();