用 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 实现低延迟实时读取的最小可行代码
这是最接近“实时”的做法:监听流就绪状态,有数据就读,没数据就跳过,不等、不卡、可控超时。