来源于群里兔子大佬 @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
,
执行 pstree -ap | grep -C 20 /usr/bin/php8.2
,可以看到有 5 个进程在跑,popen 打开的command.php
进程执行完成后,就会自动退出。
通过 workerman 和 popen 可以异步进程执行命令,前端再搭配 xterm.js
就可以做 web ssh了;当然还可以有很多适用的场景,比如用 workerman 拉起think-queue进程等等,自行拓展...
不错
很强