通過分塊讀取文件、控制每秒輸出位元組數、配合 sleep() 暫停,實現應用層下載限速。適用於共享頻寬環境防止單用戶佔滿帶寬。
<?php /** * 文件限速下載 * * @param string $local_file 服務器本地文件路徑 * @param string $download_file 用戶下載時的文件名 * @param float $download_rate_kbps 限速 KB/s (如 20.5 = 20.5 KB/s) */ function downloadWithThrottle($local_file, $download_file, $download_rate_kbps = 20.5) { // 1. 文件存在性檢查 if (!file_exists($local_file) || !is_file($local_file)) { header('HTTP/1.1 404 Not Found'); die('Error: File not found!'); } $file_size = filesize($local_file); // 2. 發送 HTTP 頭 header('Cache-Control: private, must-revalidate'); header('Content-Type: application/octet-stream'); header('Content-Length: ' . $file_size); header('Content-Disposition: attachment; filename="' . $download_file . '"'); header('Accept-Ranges: bytes'); // 支援斷點續傳(需額外實現 Range 處理) // 3. 刷新輸出緩衝 flush(); // 4. 打開文件流 $handle = fopen($local_file, 'rb'); if (!$handle) { die('Error: Cannot open file!'); } // 5. 計算每次讀取塊大小(位元組) $chunk_size = (int)round($download_rate_kbps * 1024); if ($chunk_size < 1024) $chunk_size = 1024; // 最小 1KB // 6. 分塊輸出循環 while (!feof($handle)) { // 讀取塊 $buffer = fread($handle, $chunk_size); if ($buffer === false) break; // 輸出到瀏覽器 echo $buffer; // 強制刷新輸出緩衝 flush(); // 限速:暫停 1 秒(根據實際需求可調整為 usleep 微秒級控制) sleep(1); // 檢查客戶端是否斷開連接 if (connection_aborted()) { break; } } // 7. 關閉文件句柄 fclose($handle); } // 使用範例 $local_file = '/data/downloads/large-file.zip'; $download_file = 'user-download-name.zip'; $download_rate = 50; // 50 KB/s downloadWithThrottle($local_file, $download_file, $download_rate);
0) { $read_size = min($chunk_size, $remaining); $buffer = fread($handle, $read_size); if ($buffer === false) break; echo $buffer; flush(); $remaining -= strlen($buffer); if ($remaining > 0) { sleep(1); // 或 usleep 精確控制 } if (connection_aborted()) break; } fclose($handle); }
| 參數 | 建議值 | 說明 |
|---|---|---|
| chunk_size | rate_kbps × 1024 | 每秒讀取位元組數,太小增加系統調用開銷 |
| sleep(1) | 1 秒 | 粒度較粗,精確控制可用 usleep(1000000 * chunk / rate) |
| flush() | 每次循環 | 強制輸出緩衝區數據到客戶端 |
| connection_aborted() | 每次循環檢查 | 客戶端取消下載時及時釋放資源 |
⚠ 重要限制:
max_execution_time,需 set_time_limit(0)output_buffering、nginx proxy_buffering)可能抵消 flush() 效果🏭 企業級方案:
limit_rate / limit_rate_after 模組(內核級,高效)0) { usleep((int)$sleep_time); } } // 在循環中使用 $start_time = microtime(true); $total_sent = 0; $rate_bps = 50 * 1024; // 50 KB/s while (!feof($handle)) { $buffer = fread($handle, 8192); echo $buffer; flush(); $total_sent += strlen($buffer); preciseThrottle($total_sent, $rate_bps, $start_time); if (connection_aborted()) break; }