【已解决】有四个webman的二进制项目,怎么在一个项目内启动其他三个项目

awen

问题描述

我的tools、service、common、oss四个项目二制文件,想在tools启动后在执行安装方法时,去启动其他三个二制文件,让他们运行起来,我现在的方法可以启动其他三个服务,但是tools的项目的进程与其他三个项目进程间有依赖关联,好像是文件句柄被监听了,结果是tools停止后再重启会报错,提示 地址已使用,如图
截图
必须要把其他三个服务全部停止后,tools才能正常重启

代码

 public function startService()
    {
        $rootDir = dirname(base_path(false));

        echo "\n========== 开始启动服务 ==========\n";

        // Linux/Unix 环境 - 关键:使用 setsid 确保完全独立
            // nohup: 忽略挂起信号
            // setsid: 创建新会话,脱离控制终端和进程组
            // &: 后台运行
            // 重定向所有标准流到 /dev/null
            $commands = [
                "cd {$rootDir}/oss && nohup setsid ./yifanfan-localOss.bin start -d < /dev/null > /dev/null 2>&1 &",
                "sleep 2",
                "cd {$rootDir}/common && nohup setsid ./yifanfan-private-common.bin start -d < /dev/null > /dev/null 2>&1 &",
                "sleep 2",
                "cd {$rootDir}/service && nohup setsid ./yifanfan.bin start -d < /dev/null > /dev/null 2>&1 &"
            ];

        foreach ($commands as $index => $cmd) {
            echo "\n[步骤 " . ($index + 1) . "] 执行命令:{$cmd}\n";

            // 使用 exec 执行命令,非阻塞
            exec($cmd, $output, $returnCode);

            if ($returnCode === 0 || $returnCode === null) {
                echo "命令执行成功\n";
            } else {
                echo "命令执行失败,返回码:{$returnCode}\n";
            }

            // 启动后进行检查
            if (strpos($cmd, 'yifanfan-localOss.bin') !== false) {
                sleep(2); // 稍微等待,让进程起来
                $this->verifyProcessStarted('yifanfan-localOss');
            } elseif (strpos($cmd, 'yifanfan-private-common.bin') !== false) {
                sleep(2);
                $this->verifyProcessStarted('yifanfan-private-common');
            } elseif (strpos($cmd, 'yifanfan.bin') !== false) {
                sleep(10); // 主服务可能需要更长时间
                $this->verifyProcessStarted('yifanfan');
            }
        }

        //在这里添加一个延迟,确保所有子进程完全独立
        sleep(10);

        echo "\n========== 服务启动完成 ==========\n";
    }

    /**
     * 验证进程是否启动成功
     * @param string $processName 进程名称
     * @return bool
     */
    private function verifyProcessStarted(string $processName): bool
    {
        if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
            // Windows: 使用 tasklist
            $output = shell_exec("tasklist /FI \"IMAGENAME eq {$processName}.exe\" 2>&1");
            if (strpos($output, $processName) !== false) {
                echo "✅ 进程 {$processName} 启动成功\n";
                return true;
            }
        } else {
            // Linux: 使用 pgrep
            $output = shell_exec("pgrep -f {$processName} 2>&1");
            if (!empty(trim($output))) {
                echo "✅ 进程 {$processName} 启动成功 (PID: " . trim($output) . ")\n";
                return true;
            }
        }

        echo "❌ 进程 {$processName} 启动失败\n";
        return false;
    }

想要实现的效果是tools内部启动完其他三个服务后,各自可以独立,没有关联要怎么实现?

201 1 0
1个回答

awen

主要是其他三个服务的进程关联了tools的文件描述符,在tools启动其他三个服务时,把文件描述符清理掉

修改后的代码:

 public function startService()
    {
        $rootDir = dirname(base_path(false));

        echo "\n========== 开始启动服务 ==========\n";

        // Linux/Unix 环境 - 使用临时脚本方法
        $scripts = [
            [
                'dir' => $rootDir . '/oss',
                'bin' => 'yifanfan-localOss.bin',
                'step' => 'LocalOSS'
            ],
            [
                'dir' => $rootDir . '/common',
                'bin' => 'yifanfan-private-common.bin',
                'step' => 'PrivateCommon'
            ],
            [
                'dir' => $rootDir . '/service',
                'bin' => 'yifanfan.bin',
                'step' => 'MainService'
            ]
        ];

        foreach ($scripts as $idx => $scriptConf) {
            $dir = $scriptConf['dir'];
            $bin = $scriptConf['bin'];
            $step = $scriptConf['step'];

            // 创建临时脚本内容 - 使用 \n 作为换行符,确保 Unix 风格
            $scriptContent = "#!/bin/bash\n" .
                "set -e # 遇到错误立即退出\n" .
                "\n" .
                "# 关闭除标准流外的所有文件描述符\n" .
                "fd_dir=\"/proc/\$\$/fd\"\n" .
                "if [ ! -d \"\$fd_dir\" ]; then\n" .
                "    fd_dir=\"/dev/fd\"\n" .
                "fi\n" .
                "\n" .
                "for fd_path in \"\$fd_dir\"/*; do\n" .
                "    [ -e \"\$fd_path\" ] || continue\n" .
                "    fd=\${fd_path##*/}\n" .
                "    case \"\$fd\" in\n" .
                "        0|1|2) continue ;;\n" .
                "    esac\n" .
                "    eval \"exec \${fd}>&-\" 2>/dev/null || true\n" .
                "done\n" .
                "\n" .
                "# 重定向标准输入/输出/错误\n" .
                "exec </dev/null >/dev/null 2>&1\n" .
                "\n" .
                "# 切换目录并启动程序\n" .
                "cd \"$dir\" || { echo \"无法切换到目录 $dir\"; exit 1; }\n" .
                "if [ ! -f \"./$bin\" ]; then\n" .
                "    echo \"找不到可执行文件 ./$bin 在 $dir\"\n" .
                "    exit 1\n" .
                "fi\n" .
                "\n" .
                "# 使用 setsid 启动,确保进程完全独立\n" .
                "setsid ./$bin start -d &\n" .
                "\n" .
                "# 确保脚本退出\n" .
                "exit 0\n";

            // 生成临时脚本文件
            $tempScriptPath = sys_get_temp_dir() . "/start_service_{$bin}_" . uniqid() . ".sh";
            file_put_contents($tempScriptPath, $scriptContent);
            chmod($tempScriptPath, 0755); // 设置可执行权限

            echo "\n[步骤 " . ($idx + 1) . "] 执行脚本:{$tempScriptPath}\n";

            // 执行临时脚本
            $output = shell_exec("bash {$tempScriptPath} 2>&1"); // 捕获输出和错误
            $returnCode = proc_close(proc_open("bash {$tempScriptPath}", [], $pipes)); // 获取实际返回码

            // 删除临时脚本
            //unlink($tempScriptPath);

            if ($returnCode === 0) {
                echo "脚本执行成功\n";
                if ($output) {
                    echo "脚本输出:{$output}\n";
                }
            } else {
                echo "脚本执行失败,返回码:{$returnCode}\n";
                if ($output) {
                    echo "脚本输出:{$output}\n";
                }
            }

            // 验证进程是否启动成功
            if ($step === 'LocalOSS') {
                sleep(2);
                $this->verifyProcessStarted('yifanfan-localOss');
            } elseif ($step === 'PrivateCommon') {
                sleep(2);
                $this->verifyProcessStarted('yifanfan-private-common');
            } elseif ($step === 'MainService') {
                sleep(10); // Main service might take longer
                $this->verifyProcessStarted('yifanfan');
            }

            sleep(2); // 稍微间隔
        }

        sleep(20);

        echo "\n========== 服务启动完成 ==========\n";
        return ['status' => 'success', 'message' => 'Services started successfully and detached from parent process'];
    }

    /**
     * 验证进程是否启动成功
     * @param string $processName 进程名称
     * @return bool
     */
    private function verifyProcessStarted(string $processName): bool
    {
        if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
            // Windows: 使用 tasklist
            $output = shell_exec("tasklist /FI \"IMAGENAME eq {$processName}.exe\" 2>&1");
            if (strpos($output, $processName) !== false) {
                echo "✅ 进程 {$processName} 启动成功\n";
                return true;
            }
        } else {
            // Linux: 使用 pgrep
            $output = shell_exec("pgrep -f {$processName} 2>&1");
            if (!empty(trim($output))) {
                echo "✅ 进程 {$processName} 启动成功 (PID: " . trim($output) . ")\n";
                return true;
            }
        }

        echo "❌ 进程 {$processName} 启动失败或未能立即验证\n";
        return false;
    }
  • 暂无评论
🔝