首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 大檔案下載優化:從 readfile 到 X-Sendfile 零拷貝實戰

     編輯:PHP 相關知識

痛點:傳統 PHP 下載大檔的效能瓶頸

最直觀的做法是讓 PHP 讀取檔案再輸出:

$file = '/tmp/large.zip';
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="'.basename($file).'"');
header('Content-Length: '.filesize($file));
readfile($file); // 或 fread 迴圈

缺點:

  • PHP 進程需完整讀取檔案到使用者空間緩衝區,再寫回核心 Socket 緩衝區 → 兩次拷貝,佔用大量記憶體與 CPU。
  • 大檔案易觸發 memory_limit、max_execution_time 限制。
  • Nginx + PHP-FPM 架構下,資料還需經過 Unix Socket/TCP 傳給 Nginx → 額外網路 I/O。

進階:中文檔名跨瀏覽器相容處理

$filename = basename($file);
$ua = $_SERVER['HTTP_USER_AGENT'];
$encoded = rawurlencode($filename);
$encoded = str_replace('+', '%20', $encoded); // RFC 5987 空格處理

if (preg_match('/MSIE|Trident|Edge/', $ua)) {
    // IE/Edge (舊版)
    header('Content-Disposition: attachment; filename="'.$encoded.'"');
} elseif (preg_match('/Firefox/', $ua)) {
    // Firefox 支援 RFC 5987
    header("Content-Disposition: attachment; filename*=utf-8''$filename");
} else {
    // Chrome, Safari, 現代瀏覽器
    header('Content-Disposition: attachment; filename="'.$filename.'"');
}

關鍵點:filename* 使用 UTF-8 編碼,避免中文亂碼;舊版 IE 需 urlencode 並將 + 換成 %20。

終極優化:X-Sendfile / X-Accel-Redirect 零拷貝

讓 Web Server(Apache/Nginx/Lighttpd)直接以核心層級 sendfile() 系統呼叫將檔案推送給客戶端,PHP 僅負責權限檢查、Header 產生,完全不讀取檔案內容。

Apache + mod_xsendfile

// 安裝:apt-get install libapache2-mod-xsendfile / yum install mod_xsendfile
// 啟用:a2enmod xsendfile
// 配置:XSendFile On / XSendFilePath /tmp/

$file = '/tmp/中文名.tar.gz';
$filename = basename($file);
// ... 中文檔名 Header 處理同上 ...

// 關鍵:只發送 Header,不讀檔
header('X-Sendfile: '.$file);
// 可選:關閉 PHP 輸出緩衝
// header('Content-Length: '.filesize($file)); // 由 Apache 自動補上

Nginx + X-Accel-Redirect

// Nginx 配置
location /protected/ {
    internal;           // 只允許內部重導向
    alias /data/files/; // 實際檔案根目錄
}

// PHP
$file = '/data/files/secret.zip';
header('X-Accel-Redirect: /protected/secret.zip');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="secret.zip"');

Lighttpd

同理支援 X-Sendfile 或 X-LIGHTTPD-send-file。

效能對比(百 MB 檔案下載)

方案PHP 記憶體CPU 佔用網路 I/O適用場景
readfile()高(檔案大小)高PHP→Client / PHP→Nginx→Client小檔、簡單專案
fread 迴圈 + flush低(緩衝區大小)中同上中型檔、需進度控制
X-Sendfile / X-Accel極低(只跑 Header)極低Kernel→Client (零拷貝)大檔、高併發、生產環境

安全性檢查清單

  1. 路徑遍歷防護:$file = realpath($baseDir . '/' . $userInput); if (strpos($file, $baseDir) !== 0) die('非法路徑');
  2. 權限驗證:下載前檢查用戶登入狀態、購買記錄、下載次數限制。
  3. 日誌記錄:記錄 IP、User-Agent、檔案、時間,便於審計。
  4. 禁止直接存取:檔案置於 Web Root 外部,Nginx internal、Apache XSendFilePath 限制路徑。

總結

對於大檔案下載,X-Sendfile (Apache) / X-Accel-Redirect (Nginx) 是標準解法。PHP 僅處理業務邏輯與 Header,將檔案傳輸交給 Web Server 核心層級零拷貝機制,極大降低記憶體、CPU 壓力,並支援斷點續傳(Web Server 原生支援 Range Request)。搭配完善的中文檔名編碼與安全防護,即可構建企業級下載服務。

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