整理分享Workerman中利用popen实现多进程web ssh

Jinson

来源于群里兔子大佬 @chaz6chez 的分享,自己整理测试了下。

起源

群友询问如何进程异步执行命令行任务,且有通知机制,想用来做web ssh,兔子大佬指导可以通过 popen 来实现:

群友讨论

实践

测试使用的是 workerman5.0 版本,基于兔佬提供是伪代码进行调整,使用 websocket 交互

在项目根目录新建 start.php 文件,代码如下:

<?php

require_once __DIR__ . '/vendor/autoload.php';

date_default_timezone_set('Asia/Shanghai');

//进程池类
class ProcessPool
{
    private array $processes = [];
    public int $maxProcesses;

    public function __construct($maxProcesses = 5)
    {
        $this->maxProcesses = $maxProcesses;
    }

    //添加进程 popen打开的进程执行完会退出,不做复用
    public function add(string $command)
    {
        $process           = popen($command, 'r');
        $this->processes[] = $process;
        return $process;
    }

    // 释放进程
    public function releaseProcess($process): void
    {
        $key = array_search($process, $this->processes);
        if ($key !== false) {
            unset($this->processes[$key]); // 从活动进程池移除
        }
    }

    // 获取池中的所有进程数量
    public function processCount(): int
    {
        return count($this->processes);
    }
}

// 创建一个websocket Worker
$task = new \Workerman\Worker("websocket://0.0.0.0:3232");

// 初始化进程池
$processPool = new ProcessPool(5);

// 添加定时器,每秒打印进程数
$task->onWorkerStart = function (\Workerman\Worker $worker) use ($processPool) {
    \Workerman\Timer::add(1, function () use ($processPool) {
        var_dump('[' . date('H:i:s') . '] 活动进程数:' . $processPool->processCount());
    });
};

$task->onMessage = function (Workerman\Connection\TcpConnection $connection, $str) use ($processPool) {
    $data = json_decode($str, JSON_OBJECT_AS_ARRAY);
    $command = $data['command'] ?? '';
    if (empty($command)) {
        return $connection->send('无效命令');
    }

    //超过最大进程数时阻塞等待退出
    if ($processPool->processCount() >= $processPool->maxProcesses) {
        return $connection->send('使用进程数已达最大数,等待中...');
    }

    // 获取一个进程资源
    $process = $processPool->add($command);
    if (is_resource($process)) {

        // 创建读事件
        \Workerman\Worker::$globalEvent->onReadable($process, function ($pipe) use ($connection, $process, $processPool) {
            // 读取进程输出
            $output = fread($pipe, 8192);
            if ($output === false || feof($pipe)) {
                // 进程结束,释放资源
                fclose($pipe);
                $processPool->releaseProcess($pipe);
                $connection->send("进程结束.");
                \Workerman\Worker::$globalEvent->offReadable($process);
            } else {
                // 将输出发送给客户端
                $connection->send($output);
            }
        });

        return true;

    } else {
        return $connection->send("服务繁忙,请稍后重试.");
    }
};

\Workerman\Worker::runAll();

测试

项目根目录新建一个 command.php文件,用于测试

<?php

for ($i = 0; $i < 5; $i++) {
    sleep(1);
    echo '测试' . $i . PHP_EOL;
}

php start.php start 启动项目,前端使用 【WebSocket 测试工具】进行连接,发送消息执行命令:/usr/bin/php8.2 command.php

websocket 调试工具

执行 pstree -ap | grep -C 20 /usr/bin/php8.2,可以看到有 5 个进程在跑,popen 打开的command.php进程执行完成后,就会自动退出。

进程查看

总结

通过 workerman 和 popen 可以异步进程执行命令,前端再搭配 xterm.js 就可以做 web ssh了;当然还可以有很多适用的场景,比如用 workerman 拉起think-queue进程等等,自行拓展...

150 2 0
2个评论

Tinywan

不错

  • 暂无评论
hunma

很强

  • 暂无评论

Jinson

670
积分
0
获赞数
0
粉丝数
2023-06-06 加入
×
🔝