提供大型檔案下載時,避免單一連線佔滿頻寬,影響其他服務;或依方案等級限制免費用戶下載速度。
<?php
$local_file = 'test-file.zip'; // 伺服器實體檔案
$download_file = 'your-download-name.zip'; // 使用者下載時的檔名
$download_rate = 20.5; // 限速 KB/s
if (file_exists($local_file) && is_file($local_file)) {
// 1. 發送下載 Header
header('Cache-control: private');
header('Content-Type: application/octet-stream');
header('Content-Length: ' . filesize($local_file));
header('Content-Disposition: filename=' . $download_file);
// 2. 立即刷新 Header
flush();
// 3. 開啟檔案流
$fp = fopen($local_file, 'r');
// 4. 分塊讀取、輸出、休眠
while (!feof($fp)) {
// 每次讀取 $download_rate KB
echo fread($fp, round($download_rate * 1024));
flush(); // 強制輸出到瀏覽器
sleep(1); // 暫停 1 秒
}
fclose($fp);
} else {
die('Error: The file ' . $local_file . ' does not exist!');
}
?>
20.5 * 1024 ≈ 21000 位元組,配合 sleep(1) 形成約 20.5 KB/s 平均速率。flush() 雙重保險:PHP 輸出緩衝 → Web Server 緩衝 → TCP 發送緩衝。flush() 推送至 Web Server,若啟用 output_buffering 需搭配 ob_flush()。Content-Length 必填:瀏覽器才能顯示進度條、支援斷點續傳(需額外處理 Range Header)。// 解析 Range Header
$range = $_SERVER['HTTP_RANGE'] ?? '';
if ($range) {
list(, $range) = explode('=', $range);
list($start, $end) = explode('-', $range);
$end = $end ?: filesize($local_file) - 1;
header('HTTP/1.1 206 Partial Content');
header('Content-Range: bytes ' . $start . '-' . $end . '/' . filesize($local_file));
header('Content-Length: ' . ($end - $start + 1));
fseek($fp, $start);
// 迴圈中 fread 長度改為 min($chunk, $end - $pos + 1)
}
proxy_buffering 或 mod_deflate,flush() 可能不生效,需關閉或調整緩衝參數。set_time_limit(0) 防止腳本超時。pm.max_children 或改用 Nginx limit_rate 於 Web Server 層限速(效能更好)。$local_file 路徑,防止目錄遍歷攻擊(../etc/passwd)。