本文目录导读:

在PHP后端实现下载限速,核心思路是控制每次读取文件并输出到客户端的字节数,并在每次输出后暂停一定时间,这需要关闭PHP的输出缓冲,并使用 usleep() 控制时间间隔。
以下是几种实现方式,从简单到复杂:
基础版(最常用)
<?php
/**
* 限速下载文件
* @param string $filePath 文件路径
* @param int $kbps 限速速度,单位 KB/s
*/
function downloadWithLimit($filePath, $kbps = 100) { // 默认 100KB/s
if (!file_exists($filePath)) {
http_response_code(404);
echo "File not found.";
exit;
}
// 清理之前的输出
ob_clean();
flush();
// 设置下载头
$fileName = basename($filePath);
$fileSize = filesize($filePath);
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . $fileName . '"');
header('Content-Length: ' . $fileSize);
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Expires: 0');
header('Pragma: public');
// 打开文件
$fp = @fopen($filePath, 'rb');
if (!$fp) {
http_response_code(500);
echo "Failed to open file.";
exit;
}
// 计算每次读取的字节数 (例如每次读取 1024 字节)
$bytesPerSecond = $kbps * 1024; // 转换为字节/秒
$chunkSize = 1024; // 每次读取 1KB
// 计算暂停时间 (微秒)
$usleepTime = ($chunkSize / $bytesPerSecond) * 1000000;
// 发送文件内容
while (!feof($fp) && (connection_status() == CONNECTION_NORMAL)) {
echo fread($fp, $chunkSize);
flush(); // 强制刷新输出缓冲区
// 暂停以控制速度
usleep($usleepTime);
}
fclose($fp);
exit;
}
// 使用示例
downloadWithLimit('/path/to/your/file.zip', 200); // 200KB/s
更精确的速度控制
上面的方法在文件较小时可能不够精确,这个版本通过时间计算来更精确地控制:
<?php
function downloadWithLimitPrecise($filePath, $kbps = 100) {
if (!file_exists($filePath)) {
http_response_code(404);
exit;
}
// 设置头信息(同上)
$fileName = basename($filePath);
$fileSize = filesize($filePath);
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . $fileName . '"');
header('Content-Length: ' . $fileSize);
header('Cache-Control: no-cache');
$fp = fopen($filePath, 'rb');
if (!$fp) {
http_response_code(500);
exit;
}
// 关闭输出缓冲
if (ob_get_level()) {
ob_end_clean();
}
$bytesPerSecond = $kbps * 1024;
$chunkSize = 8192; // 8KB chunk, 减少频繁的循环
$startTime = microtime(true);
$totalSent = 0;
while (!feof($fp) && (connection_status() == CONNECTION_NORMAL)) {
$data = fread($fp, $chunkSize);
echo $data;
flush();
$totalSent += strlen($data);
$elapsed = microtime(true) - $startTime;
// 计算应该已经发送了多少字节
$expectedSent = $bytesPerSecond * $elapsed;
// 如果发送过快,就暂停
if ($totalSent > $expectedSent) {
$sleepTime = ($totalSent - $expectedSent) / $bytesPerSecond * 1000000;
usleep($sleepTime);
}
}
fclose($fp);
exit;
}
支持断点续传 + 限速
这个版本可以在支持断点续传的同时进行速度限制:
<?php
function downloadWithResumeAndLimit($filePath, $kbps = 100) {
if (!file_exists($filePath)) {
http_response_code(404);
exit;
}
$fileName = basename($filePath);
$fileSize = filesize($filePath);
$fp = fopen($filePath, 'rb');
if (!$fp) {
http_response_code(500);
exit;
}
// 处理断点续传
$start = 0;
$end = $fileSize - 1;
if (isset($_SERVER['HTTP_RANGE'])) {
preg_match('/bytes=(\d+)-(\d*)/', $_SERVER['HTTP_RANGE'], $matches);
$start = intval($matches[1]);
if (!empty($matches[2])) {
$end = intval($matches[2]);
}
fseek($fp, $start);
header('HTTP/1.1 206 Partial Content');
header("Content-Range: bytes $start-$end/$fileSize");
header('Content-Length: ' . ($end - $start + 1));
} else {
header("Content-Length: $fileSize");
}
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . $fileName . '"');
header('Accept-Ranges: bytes');
// 关闭输出缓冲
if (ob_get_level()) {
ob_end_clean();
}
$bytesPerSecond = $kbps * 1024;
$chunkSize = 8192;
$bytesSent = 0;
$bytesToSend = $end - $start + 1;
$lastTime = microtime(true);
while (!feof($fp) && $bytesSent < $bytesToSend) {
$remaining = $bytesToSend - $bytesSent;
$readSize = min($chunkSize, $remaining);
$data = fread($fp, $readSize);
echo $data;
flush();
$bytesSent += strlen($data);
// 速度控制
$currentTime = microtime(true);
$elapsed = $currentTime - $lastTime;
if ($elapsed < 1) {
// 在1秒内控制发送量
usleep(($chunkSize / $bytesPerSecond) * 1000000);
} else {
$lastTime = $currentTime;
}
}
fclose($fp);
exit;
}
关键参数优化建议
| 场景 | chunkSize | 建议限速值 | 说明 |
|---|---|---|---|
| 小文件 (<10MB) | 1024 | 100-500 KB/s | 文件小,响应快即可 |
| 大文件 (>100MB) | 8192 | 500-2000 KB/s | 提高吞吐量 |
| 视频/音频 | 16384 | 用户网络带宽的80% | 保证流畅播放 |
| 同时下载用户多 | 4096 | 根据用户数平均分配 | 避免服务器过载 |
注意事项
-
服务器配置:需要在
php.ini设置:max_execution_time = 0 # 取消时间限制 memory_limit = -1 # 取消内存限制 output_buffering = Off # 关闭输出缓冲
-
Nginx/Apache 配置:如果使用反向代理,需要禁用缓冲:
# Nginx proxy_buffering off; fastcgi_buffering off;
-
更适合的实现方式:对于生产环境,建议使用专门的下载管理或流媒体服务器,PHP更适合做下载调度和权限控制,实际传输交给 Nginx 的
X-Accel-Redirect或 Apache 的mod_xsendfile。
动态控制不同用户的速度
<?php
// 根据用户类型获取速度限制
function getUserSpeedLimit($userId) {
// 从数据库或缓存获取
$userSpeeds = [
'vip' => 5000, // VIP 5MB/s
'normal' => 2000, // 普通 2MB/s
'guest' => 500 // 访客 500KB/s
];
$userType = getUserType($userId); // 获取用户类型
return $userSpeeds[$userType] ?? 1000;
}
// 在下载时调用
$speed = getUserSpeedLimit($currentUserId);
downloadWithLimit($filePath, $speed);
选择哪种方法取决于你的具体需求,基础版对于大多数场景已经足够,而精确控制版和断点续传版适用于更专业的下载场景。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播