用 stream_select 监听非阻塞流实现低延迟实时读取:先 stream_set_blocking($fp, false),再循环调用 stream_select 等待可读事件,有数据时 fgets 读取,feof 判断结束;需确保子进程及时 flush(如 stdbuf -oL 或 fflush)。

PHP popen 怎么持续读取子进程的实时输出?
不能靠 fread 一次性读完,得用循环 + 非阻塞或轮询方式持续捞数据。默认 popen 返回的是阻塞流,且底层命令(比如 curl、ffmpeg、自研 HTTP 服务)若未主动 flush,PHP 也收不到缓冲区里的内容。
关键点有三个:子进程是否边写边 flush、PHP 流是否设为非阻塞、读取逻辑是否避免卡死。
- 确保被调用命令本身支持实时输出,例如
curl -N(禁用缓存)、php -S服务加ob_flush()和flush() - 用
stream_set_blocking($fp, false)关闭阻塞,否则fread会一直等 - 用
stream_select监听可读事件更稳妥,比轮询feof+fread更准,尤其在子进程尚未退出但暂无新数据时
为什么 fread($fp, 1024) 会卡住或读不到实时数据?
因为 fread 在阻塞模式下,只要流没关闭、也没新数据,就会挂起整个 PHP 进程;而很多命令(如 python3 server.py)默认行缓冲或全缓冲,不显式 sys.stdout.flush() 就不会把日志吐到管道里。
常见错误现象:fread 卡住几秒才返回一大段、或者直接 EOF —— 其实是子进程早写了,但 PHP 没收到,或子进程已退出但缓冲区还压着数据。
立即学习“PHP免费学习笔记(深入)”;
- 检查子进程是否用了
stdbuf -oL(行缓冲)或-u(Python 无缓冲),例如:stdbuf -oL python3 streamer.py | php script.php - PHP 端别依赖
feof($fp)判定“还有没有”,它只在流关闭后才返回 true;应结合stream_select或超时控制 -
fread第二个参数不是“最少读多少”,而是“最多读多少”,读不满不报错也不阻塞(在非阻塞模式下)
用 stream_select 实现低延迟实时读取的最小可行代码
这是最接近“实时”的做法:监听流就绪状态,有数据就读,没数据就跳过,不等、不卡、可控超时。
$cmd = 'php -r "for(\$i=0;\$i<5;\$i++){ echo \"line \$i\\n\"; sleep(1); fflush(STDOUT); }"';
$fp = popen($cmd, 'r');
if (!$fp) die("popen failed");
stream_set_blocking($fp, false);
$reads = [$fp];
$writes = $excepts = [];
// 每次最多等 0.1 秒,避免空转耗 CPU
while (true) {
$num = stream_select($reads, $writes, $excepts, 0, 100000);
if ($num === false) break;
if ($num > 0 && in_array($fp, $reads)) {
$line = fgets($fp);
if ($line !== false) {
echo ">> " . rtrim($line) . "\n";
}
if (feof($fp)) break;
}
}
pclose($fp);
注意:stream_select 的第四个参数是秒,第五个是微秒;这里设为 0, 100000 表示每次最多等 0.1 秒 —— 太短会频繁轮询,太长则延迟高。实际中可根据服务响应节奏调整。
替代方案:什么时候不该硬扛 popen?
当你要调的服务本身是 HTTP 接口、WebSocket 或需要重连/鉴权/超时控制时,popen 很快会变成黑洞:无法优雅中断、不好传 header、没法处理 302 或 chunked 编码。
容易被忽略的坑:
-
popen启动的进程是 PHP 进程的子进程,一旦 PHP 主进程崩溃或超时(比如 Nginxfastcgi_read_timeout),子进程可能变成孤儿,继续跑着占资源 - Windows 下
popen对管道行为和信号支持弱,pclose可能不触发子进程退出 - 无法获取子进程真实 exit code(
pclose返回值需右移 8 位,且仅当子进程正常终止才可靠)
如果目标是“调用一个长期运行的服务并收流”,优先考虑 cURL + CURLOPT_WRITEFUNCTION 接 chunk、或用 ReactPHP/amphp 做异步 HTTP 流式消费 —— popen 适合短平快的本地命令粘合,不适合做服务网关。











