diff --git a/src/Core/GameProcessServer.php b/src/Core/GameProcessServer.php new file mode 100644 index 0000000..c191692 --- /dev/null +++ b/src/Core/GameProcessServer.php @@ -0,0 +1,250 @@ +resourceId}\n"; + $this->clients[$conn->resourceId] = $conn; + + try { + // 为该连接启动一个游戏进程 + $process = $this->startGameProcess($conn->resourceId); + $this->processes[$conn->resourceId] = $process; + + $this->sendMessage($conn, [ + 'type' => 'system', + 'message' => '游戏进程已启动,正在加载...' + ]); + } catch (\Exception $e) { + $this->sendError($conn, '启动游戏失败: ' . $e->getMessage()); + $conn->close(); + } + } + + /** + * 启动游戏进程 + */ + private function startGameProcess(string $connId): array + { + $gameDir = __DIR__ . '/../../'; + $gameScript = $gameDir . 'bin/game'; + + // 检查脚本是否存在 + if (!file_exists($gameScript)) { + throw new \Exception("游戏脚本不存在: $gameScript"); + } + + // 设置管道描述符 + $descriptorspec = [ + 0 => ['pipe', 'r'], // stdin + 1 => ['pipe', 'w'], // stdout + 2 => ['pipe', 'w'], // stderr + ]; + + // 启动进程 + $process = proc_open( + 'php ' . escapeshellarg($gameScript), + $descriptorspec, + $pipes, + $gameDir, + [ + 'TERM' => 'xterm-256color', + 'LANG' => 'en_US.UTF-8', + ] + ); + + if (!is_resource($process)) { + throw new \Exception('无法启动游戏进程'); + } + + // 设置非阻塞模式 + stream_set_blocking($pipes[0], false); + stream_set_blocking($pipes[1], false); + stream_set_blocking($pipes[2], false); + + echo "[进程] 已启动进程 {$connId}: " . getmypid() . "\n"; + + // 启动输出读取线程(使用select轮询) + $this->startOutputReader($connId, $pipes[1], $pipes[2]); + + return [ + 'process' => $process, + 'stdin' => $pipes[0], + 'stdout' => $pipes[1], + 'stderr' => $pipes[2], + 'connId' => $connId, + ]; + } + + /** + * 启动异步输出读取 + */ + private function startOutputReader(string $connId, $stdout, $stderr): void + { + // 创建后台读取循环 + // 这里使用一个简单的轮询机制 + // 实际上应该使用事件循环(已由Ratchet处理) + // 我们在收到消息时检查输出 + + // 创建管道监控文件 + $readQueuesFile = sys_get_temp_dir() . '/game_' . $connId . '.pipes'; + file_put_contents($readQueuesFile, json_encode([ + 'stdout' => (int)$stdout, + 'stderr' => (int)$stderr, + ])); + } + + /** + * 接收消息(用户输入) + */ + public function onMessage(ConnectionInterface $from, $msg) + { + $data = json_decode($msg, true); + if (!$data) { + return; // 忽略无效消息 + } + + $type = $data['type'] ?? null; + $input = $data['input'] ?? ''; + + if ($type === 'input' && $input) { + // 将输入写入进程的STDIN + $process = $this->processes[$from->resourceId] ?? null; + if ($process && is_resource($process['stdin'])) { + fwrite($process['stdin'], $input . "\n"); + fflush($process['stdin']); + echo "[输入] {$from->resourceId}: {$input}\n"; + } + } elseif ($type === 'ping') { + $this->sendMessage($from, ['type' => 'pong']); + } + + // 尝试读取进程输出 + $this->readProcessOutput($from); + } + + /** + * 读取进程输出 + */ + private function readProcessOutput(ConnectionInterface $conn): void + { + $process = $this->processes[$conn->resourceId] ?? null; + if (!$process) { + return; + } + + $output = ''; + + // 读取stdout + while (!feof($process['stdout'])) { + $line = fgets($process['stdout'], 4096); + if ($line === false) { + break; + } + $output .= $line; + } + + // 读取stderr + while (!feof($process['stderr'])) { + $line = fgets($process['stderr'], 4096); + if ($line === false) { + break; + } + $output .= $line; + } + + // 如果有输出,发送给客户端 + if ($output) { + $this->sendMessage($conn, [ + 'type' => 'output', + 'text' => $output + ]); + } + } + + /** + * 客户端关闭连接 + */ + public function onClose(ConnectionInterface $conn) + { + $connId = $conn->resourceId; + + // 关闭进程 + $process = $this->processes[$connId] ?? null; + if ($process) { + if (is_resource($process['stdin'])) { + fclose($process['stdin']); + } + if (is_resource($process['stdout'])) { + fclose($process['stdout']); + } + if (is_resource($process['stderr'])) { + fclose($process['stderr']); + } + if (is_resource($process['process'])) { + proc_terminate($process['process']); + proc_close($process['process']); + } + unset($this->processes[$connId]); + echo "[进程] 已终止进程: {$connId}\n"; + } + + unset($this->clients[$connId]); + echo "[断开] 连接已关闭: {$connId}\n"; + } + + /** + * 连接错误 + */ + public function onError(ConnectionInterface $conn, \Exception $e) + { + echo "[错误] {$conn->resourceId}: {$e->getMessage()}\n"; + $this->onClose($conn); + } + + /** + * 发送消息给客户端 + */ + protected function sendMessage(ConnectionInterface $conn, array $data): void + { + try { + $msg = json_encode($data, JSON_UNESCAPED_UNICODE); + $conn->send($msg); + } catch (\Exception $e) { + echo "[发送错误] {$e->getMessage()}\n"; + } + } + + /** + * 发送错误消息 + */ + protected function sendError(ConnectionInterface $conn, string $message): void + { + $this->sendMessage($conn, [ + 'type' => 'error', + 'message' => $message + ]); + } +} diff --git a/web/process.html b/web/process.html new file mode 100644 index 0000000..7d2e521 --- /dev/null +++ b/web/process.html @@ -0,0 +1,387 @@ + + + + + + 凡人修仙传 - 进程转发版 + + + + +
+

🎮 凡人修仙传 - 进程转发版

+
+ + 连接状态: 连接中 + + + +
+
+ +
+
+
正在连接游戏服务器...
+
+
+ + +
+
+ + + + + + diff --git a/web/server.php b/web/server.php index 30d300a..8f5e8d1 100644 --- a/web/server.php +++ b/web/server.php @@ -44,6 +44,13 @@ if ($path === '/' || $path === '/game-ws.html') { exit; } +// 进程版本(推荐) +if ($path === '/process.html') { + header('Content-Type: text/html; charset=utf-8'); + readfile(__DIR__ . '/process.html'); + exit; +} + // API 路由 $response = ['success' => false, 'message' => '未知请求']; diff --git a/websocket-process-server.php b/websocket-process-server.php new file mode 100755 index 0000000..60ed0d5 --- /dev/null +++ b/websocket-process-server.php @@ -0,0 +1,58 @@ +#!/usr/bin/env php +run();