PHP 怎么PHP流式响应

wen PHP项目 4

本文目录导读:

PHP 怎么PHP流式响应

  1. 基础准备:关闭缓冲区与设置响应头
  2. 方式一:直接使用 echo + flush()
  3. 方式二:使用 fwrite + fflush(配合 php://output)
  4. 方式三:实现 SSE(服务器推送事件)
  5. 方式四:文件下载大文件流式输出
  6. 复杂场景:AI对话流(结合HTTP客户端或Curl)
  7. 关于 Nginx 的特别说明(重要坑)

在 PHP 中实现流式响应(Streaming Response),核心思路是边生成数据边发送给客户端,而不是一次性把全部数据组装完再发送,这在处理大文件下载、长轮询、SSE(Server-Sent Events)或 AI 对话输出时非常有用。

以下是 PHP 实现流式响应的几种常见方式和详细代码示例。


基础准备:关闭缓冲区与设置响应头

无论用哪种方式,都要先确保关闭 PHP 的输出缓冲,并设置合适的响应头。

<?php
// 关闭 PHP 自动输出缓冲(关键步骤)
while (ob_get_level() > 0) {
    ob_end_clean();
}
// 设置不限制执行时间(可选,长任务用)
set_time_limit(0);
// 设置响应头:告诉浏览器内容类型和编码
header('Content-Type: text/plain; charset=utf-8');
// 对于 SSE 使用:header('Content-Type: text/event-stream');
// 对于下载文件使用:header('Content-Type: application/octet-stream');
// 必须关闭输出缓冲
ob_implicit_flush(true);
?>

方式一:直接使用 echo + flush()

这是最基础、最直接的方法,适合处理简单的内容生成。

适用场景:打印日志、逐行输出查询结果、下载大文件的部分数据。

<?php
// 设置头部等(见上方基础设置)
header('Content-Type: text/plain; charset=utf-8');
while (ob_get_level() > 0) {
    ob_end_clean();
}
echo "开始流式输出...\n";
flush();
for ($i = 1; $i <= 10; $i++) {
    // 模拟耗时操作,比如查数据库或调用API
    sleep(1);
    echo "数据块 {$i} 已经生成\n";
    flush(); // 立即将输出发送到客户端
}
echo "结束。\n";
flush();
?>

注意:

  • flush() 会将当前 PHP 缓冲区的内容推送到 Web 服务器,但要注意 Nginx 或 Apache 可能还会再缓冲。
  • 如果使用 Nginx,需要配置 fastcgi_buffering off; 或者使用 X-Accel-Buffering: no 响应头。

方式二:使用 fwrite + fflush(配合 php://output)

这种方式更底层一些,直接写 PHP 的输出流,效果和 echo 类似,但有时候更稳定。

<?php
header('Content-Type: text/plain; charset=utf-8');
while (ob_get_level() > 0) {
    ob_end_clean();
}
$out = fopen('php://output', 'w');
for ($i = 0; $i < 5; $i++) {
    fwrite($out, "写入第 {$i} 行数据\n");
    fflush($out);
    sleep(1);
}
fclose($out);
?>

方式三:实现 SSE(服务器推送事件)

这是用于实时推送消息到浏览器的标准协议,非常适合AI聊天、实时通知。

关键点:

  • 响应头必须有 Content-Type: text/event-stream
  • 数据格式必须符合规范:以 data: 开头,以 \n\n
  • 通常结合 event-source JavaScript API使用。

PHP 服务端代码 (sse.php):

<?php
// 禁用缓冲区
while (ob_get_level() > 0) { ob_end_clean(); }
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
header('X-Accel-Buffering: no'); // 针对Nginx禁用缓冲
$id = 0;
while ($id < 10) {
    $id++;
    // 模拟生成数据
    $data = json_encode(['time' => date('H:i:s'), 'id' => $id]);
    // 输出SSE格式数据
    echo "id: {$id}\n";
    echo "data: {$data}\n\n";
    ob_flush();
    flush();
    sleep(1);
}
echo "event: close\ndata: Connection Closed\n\n";
?>

前端 JavaScript 接收示例:

const eventSource = new EventSource('sse.php');
eventSource.onmessage = function(event) {
    console.log('收到数据:', event.data);
    document.getElementById('output').innerText += event.data + '\n';
};
eventSource.onerror = function() {
    eventSource.close();
};

方式四:文件下载大文件流式输出

不适合用 file_get_contents 读取整个大文件,应该用循环读取分块并输出。

<?php
$file = "/path/to/large/file.zip";
// 设置下载头
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="'.basename($file).'"');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Content-Length: '.filesize($file));
// 清空缓冲
while (ob_get_level() > 0) { ob_end_clean(); }
// 打开文件
$handle = fopen($file, 'rb');
if ($handle) {
    while (!feof($handle)) {
        // 每次读取1MB并输出
        echo fread($handle, 1024 * 1024);
        ob_flush();
        flush();
    }
    fclose($handle);
}
exit;
?>

复杂场景:AI对话流(结合HTTP客户端或Curl)

现在的AI接口(如OpenAI)大多支持流式传输,在PHP里做“流式代理”时,需要边读取上游数据边输出给客户端。

关键点:需要开启 CURLOPT_WRITEFUNCTION 回调。

<?php
header('Content-Type: text/plain; charset=utf-8');
while (ob_get_level() > 0) { ob_end_clean(); }
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://api.openai.com/v1/chat/completions');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_API_KEY'
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
    'model' => 'gpt-4',
    'stream' => true,
    'messages' => [['role' => 'user', 'content' => '写一首诗']]
]));
// 关键:设置写入回调,收到的数据立即转发给浏览器
curl_setopt($ch, CURLOPT_WRITEFUNCTION, function($curl, $data) {
    // 直接输出给客户端
    echo $data;
    ob_flush();
    flush();
    // 返回接收到的字节数
    return strlen($data);
});
curl_exec($ch);
curl_close($ch);
?>

Nginx 的特别说明(重要坑)

即使你在 PHP 里写了 flush(),Nginx 默认开启了 fastcgi_buffering会吞掉缓冲,直到数据量达到大小或请求结束才发给客户端。

解决方案:

  1. Nginx 配置(推荐):

    location ~ \.php$ {
        fastcgi_buffering off;
        # ... 其他配置
    }
  2. PHP Header 设置(如果无法改Nginx配置): 在PHP响应头中添加:

    header('X-Accel-Buffering: no');

    这样Nginx会忽略自身的缓冲设置,直接将数据传给客户端。


使用场景 推荐方法
大文件下载 fread + echo + flush()
普通日志/数据流 echo + flush()
实时通知/聊天 SSE (text/event-stream)
调用AI接口并转发 CURLOPT_WRITEFUNCTION 回调

核心三步走:

  1. 清空缓冲while (ob_get_level() > 0) { ob_end_clean(); }
  2. 设置响应头(尤其 X-Accel-Buffering: no 针对Nginx)
  3. 循环输出 + flush()

这样就能确保你的数据像水流一样源源不断地流向客户端。

抱歉,评论功能暂时关闭!