PHP项目下载限速如何后端动态控制速度

wen PHP项目 28

本文目录导读:

PHP项目下载限速如何后端动态控制速度

  1. 方法一:基础版(最常用)
  2. 方法二:更精确的速度控制
  3. 方法三:支持断点续传 + 限速
  4. 关键参数优化建议
  5. 注意事项
  6. 动态控制不同用户的速度

在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 根据用户数平均分配 避免服务器过载

注意事项

  1. 服务器配置:需要在 php.ini 设置:

    max_execution_time = 0      # 取消时间限制
    memory_limit = -1           # 取消内存限制
    output_buffering = Off      # 关闭输出缓冲
  2. Nginx/Apache 配置:如果使用反向代理,需要禁用缓冲:

    # Nginx
    proxy_buffering off;
    fastcgi_buffering off;
  3. 更适合的实现方式:对于生产环境,建议使用专门的下载管理或流媒体服务器,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);

选择哪种方法取决于你的具体需求,基础版对于大多数场景已经足够,而精确控制版和断点续传版适用于更专业的下载场景。

抱歉,评论功能暂时关闭!