來源于群里兔子大佬 @chaz6chez 的分享,自己整理測試了下。
群友詢問如何進(jìn)程異步執(zhí)行命令行任務(wù),且有通知機(jī)制,想用來做web ssh,兔子大佬指導(dǎo)可以通過 popen
來實(shí)現(xiàn):
測試使用的是 workerman5.0 版本,基于兔佬提供是偽代碼進(jìn)行調(diào)整,使用 websocket
交互
在項(xiàng)目根目錄新建 start.php
文件,代碼如下:
<?php
require_once __DIR__ . '/vendor/autoload.php';
date_default_timezone_set('Asia/Shanghai');
//進(jìn)程池類
class ProcessPool
{
private array $processes = [];
public int $maxProcesses;
public function __construct($maxProcesses = 5)
{
$this->maxProcesses = $maxProcesses;
}
//添加進(jìn)程 popen打開的進(jìn)程執(zhí)行完會退出,不做復(fù)用
public function add(string $command)
{
$process = popen($command, 'r');
$this->processes[] = $process;
return $process;
}
// 釋放進(jìn)程
public function releaseProcess($process): void
{
$key = array_search($process, $this->processes);
if ($key !== false) {
unset($this->processes[$key]); // 從活動進(jìn)程池移除
}
}
// 獲取池中的所有進(jìn)程數(shù)量
public function processCount(): int
{
return count($this->processes);
}
}
// 創(chuàng)建一個(gè)websocket Worker
$task = new \Workerman\Worker("websocket://0.0.0.0:3232");
// 初始化進(jìn)程池
$processPool = new ProcessPool(5);
// 添加定時(shí)器,每秒打印進(jìn)程數(shù)
$task->onWorkerStart = function (\Workerman\Worker $worker) use ($processPool) {
\Workerman\Timer::add(1, function () use ($processPool) {
var_dump('[' . date('H:i:s') . '] 活動進(jìn)程數(shù):' . $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('無效命令');
}
//超過最大進(jìn)程數(shù)時(shí)阻塞等待退出
if ($processPool->processCount() >= $processPool->maxProcesses) {
return $connection->send('使用進(jìn)程數(shù)已達(dá)最大數(shù),等待中...');
}
// 獲取一個(gè)進(jìn)程資源
$process = $processPool->add($command);
if (is_resource($process)) {
// 創(chuàng)建讀事件
\Workerman\Worker::$globalEvent->onReadable($process, function ($pipe) use ($connection, $process, $processPool) {
// 讀取進(jìn)程輸出
$output = fread($pipe, 8192);
if ($output === false || feof($pipe)) {
// 進(jìn)程結(jié)束,釋放資源
fclose($pipe);
$processPool->releaseProcess($pipe);
$connection->send("進(jìn)程結(jié)束.");
\Workerman\Worker::$globalEvent->offReadable($process);
} else {
// 將輸出發(fā)送給客戶端
$connection->send($output);
}
});
return true;
} else {
return $connection->send("服務(wù)繁忙,請稍后重試.");
}
};
\Workerman\Worker::runAll();
項(xiàng)目根目錄新建一個(gè) command.php
文件,用于測試
<?php
for ($i = 0; $i < 5; $i++) {
sleep(1);
echo '測試' . $i . PHP_EOL;
}
php start.php start
啟動項(xiàng)目,前端使用 【W(wǎng)ebSocket 測試工具】進(jìn)行連接,發(fā)送消息執(zhí)行命令:/usr/bin/php8.2 command.php
,
執(zhí)行 pstree -ap | grep -C 20 /usr/bin/php8.2
,可以看到有 5 個(gè)進(jìn)程在跑,popen 打開的command.php
進(jìn)程執(zhí)行完成后,就會自動退出。
通過 workerman 和 popen 可以異步進(jìn)程執(zhí)行命令,前端再搭配 xterm.js
就可以做 web ssh了;當(dāng)然還可以有很多適用的場景,比如用 workerman 拉起think-queue進(jìn)程等等,自行拓展...
不錯(cuò)