PHP 輸出緩衝 是理解 echo 何時真正發送到瀏覽器、實現長輪詢/流式響應、捕獲輸出內容、壓縮輸出的核心機制。本文系統梳理 output_buffering 配置、ob_* 函數族、flush() 與 Web 服務器/瀏覽器緩衝的交互,並給出生產級實戰模式。
PHP 腳本
│
▼
PHP 內部輸出緩衝 (output_buffering / ob_start)
│ 可巢狀多層 (ob_get_level())
▼
PHP 輸出到 SAPI (PHP-FPM/Apache/CLI)
│
▼
Web 服務器緩衝 (Nginx fastcgi_buffering / Apache mod_deflate)
│
▼
網絡傳輸 (TCP 滑動窗口)
│
▼
瀏覽器緩衝 (渲染引擎決定何時繪制)
│
▼
用戶看到內容
output_buffering; php.ini
output_buffering = 4096 ; 默認 4KB 內部緩衝
; output_buffering = Off ; 關閉 (僅 CLI 建議)
; output_buffering = 0 ; 同 Off
echo/print 先寫入 PHP 內部緩衝區,達到 4KB 或腳本結束才刷新到 SAPI。ob_start() 會在當前層級之上創建新緩衝層,output_buffering 視為底層隱式層。output_buffering = 0 (無緩衝)。ob_* 函數族速查表| 函數 | 作用 | 返回值 | 常用場景 |
|---|---|---|---|
ob_start(callable $callback=null, int $chunk_size=0, int $flags=0) | 開啟新緩衝層 | bool | 捕獲輸出、壓縮、過濾 |
ob_get_contents() | 獲取當前緩衝內容 | string|false | 模板引擎渲染捕獲、API 響應構建 |
ob_get_clean() | 獲取內容並關閉緩衝層 | string|false | 最常用:捕獲並清理 |
ob_get_flush() | 獲取內容、發送到上層、關閉層 | string|false | 需要立即發送並捕獲 |
ob_flush() | 發送當前緩衝內容到上層 (不關閉) | bool | 流式輸出、長輪詢、進度條 |
ob_end_flush() | 發送內容並關閉緩衝層 | bool | 正常結束輸出 |
ob_end_clean() | 丟棄內容並關閉緩衝層 | bool | 錯誤處理、重定向前清理 |
ob_get_level() | 當前緩衝層級數 | int | 調試、防止過度嵌套 |
ob_get_status(bool $full=true) | 獲取所有層狀態信息 | array | 調試、監控 |
ob_implicit_flush(bool $flag=true) | 每次輸出自動 flush | void | CLI 實時日誌、長連接 |
ob_flush() vs flush()// 正確的流式輸出順序 (PHP 緩衝開啟時)
echo "data chunk 1";
ob_flush(); // 1. PHP 內部緩衝 → SAPI
flush(); // 2. SAPI → Web Server → TCP → 瀏覽器
sleep(1);
echo "data chunk 2";
ob_flush();
flush();
// ...
ob_flush():釋放 PHP 內部緩衝區到 SAPI 層。flush():通知 SAPI/Web Server 將數據發送到客戶端。output_buffering=Off 且無 ob_start,只需 flush()。// 禁用所有緩衝 (生產環境建議在入口文件統一設置)
if (ob_get_level()) ob_end_clean();
ini_set('output_buffering', '0');
ini_set('zlib.output_compression', '0'); // 禁用壓縮
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
header('X-Accel-Buffering: no'); // 禁用 Nginx fastcgi_buffering
$lastEventId = $_SERVER['HTTP_LAST_EVENT_ID'] ?? 0;
while (true) {
// 檢查客戶端斷開
if (connection_aborted()) break;
$data = getNewEventsSince($lastEventId);
if ($data) {
echo "id: {$data['id']}\n";
echo "data: " . json_encode($data['payload']) . "\n\n";
$lastEventId = $data['id'];
ob_flush();
flush();
}
sleep(1); // 或 usleep(200000)
}
// 關鍵:Nginx 配置
// location /events {
// proxy_buffering off;
// proxy_cache off;
// fastcgi_buffering off;
// }
function render(string $template, array $data = []): string {
extract($data);
ob_start();
include $template;
return ob_get_clean();
}
// 使用
$html = render('user_profile.php', ['user' => $user, 'posts' => $posts]);
echo $html; // 或返回 API 響應
// 自動壓縮 (推薦:Web 服務器層面處理)
// nginx: gzip on; gzip_types text/css application/json application/javascript;
// PHP 層面 (不推薦,性能差)
ob_start('ob_gzhandler');
// 或手動
ob_start(function($buffer) {
if (strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== false) {
return gzencode($buffer, 6);
}
return $buffer;
});
| 瀏覽器 | 行為 | 應對 |
|---|---|---|
| Chrome/Firefox/Edge | 收到內容較快渲染 (無需完整 HTML) | 正常流式輸出即可 |
| Safari | 較保守,需一定數據量才渲染 | 發送足夠初始數據 (>1KB) |
| IE/舊版 Opera | 需完整 HTML 結構或特定標籤才渲染 | 避免流式輸出 HTML 片段 |
output_buffering、zlib.output_compression、Nginx fastcgi_buffering、gzip、CDN 緩存。ob_start() 必須在任何輸出 (含空白字符、BOM) 之前調用。ob_get_contents(),直接 echo + ob_flush() + flush()。ob_gzhandler / zlib.output_compression 會緩衝整個響應體,禁用之。精通輸出緩衡機制,是構建高性能流式 API、實時儀表板、大文件下載、模板引擎的基石技能。