首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

PHP readfile 大檔案下載卡死解決方案:ob_end_clean 關鍵作用

     編輯:Java 綜合教學

問題現象

使用 readfile() 提供大檔案下載時,瀏覽器進度條卡住、記憶體飆升、甚至 PHP 進程 OOM。

核心解決方案

@set_time_limit(0);           // 移除執行時間限制
ini_set('memory_limit', '16M'); // 限制 PHP 記憶體使用
ob_end_clean();                // 【關鍵】清空並關閉輸出緩衝區
@readfile($path);              // 直接輸出檔案流

原理深度解析

輸出緩衝區機制

  • PHP 預設開啟輸出緩衝 (output_buffering)
  • readfile() 讀取檔案內容寫入緩衝區,而非直接發送給客戶端
  • 大檔案會撐爆緩衝區,導致記憶體耗盡、回應延遲

ob_end_clean() 作用

php.net 官方文檔:This function discards the contents of the topmost output buffer and turns off this output buffering.

  • 丟棄緩衝區現有內容
  • **關閉** 當前緩衝層級
  • 後續輸出直接發送給 SAPI (不經緩衝)

為何要先關閉緩衝再 readfile?

// 錯誤順序:readfile 先寫入緩衝,ob_end_clean 再清空 → 已佔用記憶體
readfile($path);
ob_end_clean();

// 正確順序:先關閉緩衝,readfile 直接流式傳輸
ob_end_clean();
readfile($path);

完整生產級下載函數

function downloadFile($filePath, $fileName = '') {
    if (!is_file($filePath)) {
        header('HTTP/1.1 404 Not Found');
        exit('File not found');
    }
    
    $fileName = $fileName ?: basename($filePath);
    $fileSize = filesize($filePath);
    
    // 關鍵設定
    @set_time_limit(0);
    @ini_set('memory_limit', '16M');
    ob_end_clean(); // 必須在 header 之前
    
    // HTTP 標頭
    header('Content-Description: File Transfer');
    header('Content-Type: application/octet-stream');
    header('Content-Disposition: attachment; filename="' . rawurlencode($fileName) . '"');
    header('Content-Transfer-Encoding: binary');
    header('Content-Length: ' . $fileSize);
    header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
    header('Pragma: public');
    header('Expires: 0');
    
    // 分塊讀取輸出 (更穩健)
    $handle = fopen($filePath, 'rb');
    while (!feof($handle)) {
        echo fread($handle, 8192); // 8KB 塊
        flush(); // 強制刷新到客戶端
    }
    fclose($handle);
    exit;
}

進階優化:支援斷點續傳 (Range Requests)

if (isset($_SERVER['HTTP_RANGE'])) {
    $range = $_SERVER['HTTP_RANGE'];
    // 解析 Range: bytes=start-end
    // 回應 206 Partial Content
    // header('Content-Range: bytes start-end/filesize');
    // fseek($handle, $start);
}

常見誤區對照

錯誤做法問題正確做法
只用 readfile()大檔案 OOM、卡死ob_end_clean() + 分塊讀取
ob_start() 再 readfile()雙重緩衝,記憶體雙倍ob_end_clean() 關閉緩衝
未設 memory_limit受限於 php.ini 128M 預設值ini_set('memory_limit', '16M')
未設 set_time_limit(0)大檔案超過 max_execution_time 中斷移除時間限制

總結

ob_end_clean() 是 PHP 大檔案下載的**核心關鍵**:它徹底關閉輸出緩衝,讓 readfile() 或分塊 fread/echo/flush 實現真正的流式傳輸,避免記憶體堆積與回應延遲。配合記憶體限制與時間限制移除,構建穩健的下載服務。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved