PHP实时输出需关闭缓冲:调用ob_implicit_flush(true)和ob_end_flush(),禁用zlib/Nginx gzip;推荐EventSource(Content-Type: text/event-stream,data: xxx\n\n格式),兼容性差时改用fetch+ReadableStream手动流式读取;注意Nginx proxy_buffering off、Apache X-Accel-Buffering: no及浏览器心跳维持。
默认情况下 PHP 会缓冲全部输出再一次性发给前端,想让前端“逐段接收”,得先关掉缓冲。关键不是 echo 多少次,而是控制底层输出流。
ob_implicit_flush(true) 开启隐式刷新(否则 echo 仍被缓冲)ob_end_flush() 清空已有输出缓冲区(尤其在框架或 include 后容易残留)zlib.output_compression 或 Nginx 的 gzip,必须关闭——压缩会阻塞流式传输EventSource 是专为服务端推送设计的原生 API,自动重连、解析 data: 格式,比手动轮询或 WebSocket 更轻量,适合日志、进度、状态类实时输出。
Content-Type: text/event-stream,且响应头不能含 Cache-Control: no-cache(某些旧版浏览器要求)data: xxx\n\n 结尾(两个换行),前端 onmessage 自动截掉 data: 前缀set_time_limit(0),并避免超时中断连接EventSource 的 CORS 支持较弱,跨域时优先考虑同源或改用 fetch + response.body.getReader()
当需要更细粒度控制(比如逐字符解析、自定义分隔符),或支持 Safari/IE 等不支持 EventSource 的环境,可走底层流式读取。
Content-Type: text/plain(或任意类型),持续 echo "chunk\n" 并调用 flush()
fetch(
url).then(r => r.body.getReader()) 获取 ReadableStreamDefaultReader
reader.read(),每次拿到 { done, value },value 是 Uint8Array,需用 new TextDecoder().decode(value) 转字符串read() 可能含多行,或一行被拆成两次),建议用 \n 分割后再逐条处理很多“实时输出失效”其实和 PHP 本身无关,而是中间层或浏览器策略卡住。
proxy_buffering on,必须在 location 块中加 proxy_buffering off; 和 proxy_cache off;
mod_deflate 会拦截并缓存输出,需在响应前加 header('X-Accel-Buffering: no');
:keepalive\n\n(注释行)维持心跳flush() 在 FPM 下可能无效,确认 php-fpm.conf 中 buffer_output = no,且未启用 fastcgi_finish_request()
实际最难调的往往不是代码逻辑,而是 Nginx/Apache 的缓冲开关和浏览器对空帧的容忍度——这些地方一漏,前端就收不到第一个字节。