iOS Safari 实时输出需服务端禁用缓冲、首块≥1KB、Nginx关proxy_buffering,客户端用fetch+ReadableStream逐chunk解码;否则ob_flush()+flush()无效。

PHP 实时输出在 iOS Safari 上默认不生效,必须配合特定响应头、缓冲控制和客户端处理才能工作。
为什么 ob_flush() + flush() 在 iOS 上经常“没反应”
iOS Safari(包括微信内置浏览器)对流式响应极其保守:它会等待足够多数据(通常 ≥ 1KB)或遇到完整 HTTP 响应头后才开始解析,且默认启用代理级缓冲(如 Cloudflare、CDN 或 Nginx 的 proxy_buffering on)。单纯调用 ob_flush() 和 flush() 几乎无效。
- 必须禁用 PHP 输出缓冲链:
ob_end_clean()或全程关闭output_buffering - 需发送明确的
Content-Type和Transfer-Encoding: chunked(由服务器自动添加,但需确保不被中间件覆盖) - 首块输出建议 ≥ 1024 字节(可用空格/注释填充),绕过 iOS 的初始缓冲阈值
- Nginx 需显式关闭代理缓冲:
proxy_buffering off;,并设置chunked_transfer_encoding on;
PHP 端最小可行实时输出代码(iOS 兼容)
以下代码在 CLI 模式下不可用,仅适用于 Web SAPI(如 FPM),且依赖服务端配置配合:
注意:sleep() 仅用于演示;生产中应替换为实际耗时逻辑(如 API 调用、数据库轮询)。
立即学习“PHP免费学习笔记(深入)”;
iOS 客户端必须用 fetch() + ReadableStream 接收
XMLHttpRequest 不支持流式读取;iOS Safari 16.4+ 才稳定支持 Response.body.getReader()。必须用 fetch 并手动解析 chunk:
- 不能用
response.text()—— 它会等全部响应结束 - 需逐 chunk 解码:
decoder.decode(chunk, {stream: true}),否则中文乱码 - 要主动检查
done标志,避免死循环 - 推荐使用
text-encodingpolyfill 或原生TextDecoder
简易 JS 接收示例:
const decoder = new TextDecoder('utf-8');
const response = await fetch('/stream.php');
const reader = response.body.getReader();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const text = decoder.decode(value, { stream: true });
console.log(text); // 或更新 DOM
}
真正卡住的地方往往不在 PHP 代码里
最常被忽略的是部署层:Nginx 默认开启 proxy_buffering,且很多 CDN(如 Cloudflare)强制缓冲整个响应;PHP-FPM 的 buffer_output 也可能开启;甚至某些 iOS 微信版本会拦截非 HTTPS 的流式请求。验证是否生效,最直接的方式是用 Safari 开发者工具 → Network → 查看响应的 Content-Length 是否为 chunked,且 Timeline 中有持续的“接收中”状态。没有这个,前端再怎么写 fetch 也收不到实时数据。











