You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PHP脚本下载public_html外文件时,低速网络下强制下载中途失败

解决PHP低速网络下下载外部文件中断的问题

这种低速网络下的下载中断问题我之前帮朋友排查过,核心原因基本都是超时限制或者输出缓冲机制拖了后腿——毕竟低速环境下下载大文件的耗时会远超PHP和Web服务器的默认超时阈值,另外分块读取的策略也可能需要调整。下面给你一步步拆解解决方案:

1. 彻底禁用PHP的执行超时

默认情况下PHP的max_execution_time一般设为30秒或者60秒,低速下载大文件肯定不够用。你需要在脚本开头添加这几行,完全取消超时限制:

set_time_limit(0);
ini_set('max_execution_time', 0);
ini_set('max_input_time', 0);

注意:如果你的服务器有全局的超时强制限制(比如某些托管服务商),可能需要联系服务商调整,但大部分情况下这几行就能解决PHP层面的超时问题。

2. 关闭输出缓冲,实时刷新内容

输出缓冲会把内容攒到一定量才发送给客户端,低速网络下缓冲满了之后可能会触发连接中断。你需要禁用缓冲并开启实时刷新:

ob_end_clean(); // 清空并关闭之前的缓冲
ob_implicit_flush(true); // 自动刷新输出

在分块输出文件内容的时候,还要手动调用flush()和ob_flush()确保内容立刻发送给客户端。

3. 优化分块读取策略,适配低速网络

之前的分块如果太大,可能会导致客户端接收超时;太小又会增加服务器的IO压力。建议用16KB-32KB的分块大小,同时每次输出后检查客户端连接状态,如果用户断开连接就停止输出,避免浪费资源:

$chunkSize = 1024 * 16; // 16KB分块
$file = fopen($filePath, 'rb');

while (!feof($file)) {
    // 检查客户端是否已断开连接
    if (connection_aborted()) {
        fclose($file);
        exit;
    }
    echo fread($file, $chunkSize);
    flush();
    ob_flush();
    // 给服务器一点缓冲时间,避免IO过载
    usleep(1000);
}
fclose($file);

4. 调整Web服务器的超时设置

光PHP超时取消还不够,Web服务器(Apache/Nginx)本身也有超时限制,比如:

  • Apache:修改httpd.conf里的Timeout指令,设为300秒以上(比如Timeout 600)
  • Nginx:在配置文件里添加或修改proxy_read_timeout和fastcgi_read_timeout,比如proxy_read_timeout 600s;
    这些设置确保服务器不会主动断开长时间的下载连接。

5. 可选但推荐:添加断点续传支持

低速网络下用户很可能中途断开后重新下载,支持断点续传不仅能避免从头开始,还能降低连接中断的概率。通过处理HTTP_RANGE请求头来实现,完整的示例代码整合了上面所有优化点:

<?php
// 禁用超时限制
set_time_limit(0);
ini_set('max_execution_time', 0);
ini_set('max_input_time', 0);

// 关闭输出缓冲
ob_end_clean();
ob_implicit_flush(true);

// 目标文件路径(public_html外)
$filePath = '/absolute/path/to/your/file';
$fileName = basename($filePath);

if (!file_exists($filePath) || !is_readable($filePath)) {
    http_response_code(404);
    exit("File not found or unreadable");
}

$fileSize = filesize($filePath);
$chunkSize = 1024 * 16; // 16KB分块

// 处理断点续传
$start = 0;
$end = $fileSize - 1;

if (isset($_SERVER['HTTP_RANGE'])) {
    if (preg_match('/bytes=(\d+)-(\d*)/', $_SERVER['HTTP_RANGE'], $matches)) {
        $start = intval($matches[1]);
        $end = !empty($matches[2]) ? intval($matches[2]) : $fileSize - 1;
        // 确保范围合法
        $end = min($end, $fileSize - 1);
        $start = max($start, 0);
        http_response_code(206); // 部分内容响应
        header("Content-Range: bytes $start-$end/$fileSize");
    }
}

// 设置下载响应头
header("Content-Type: application/octet-stream");
header("Content-Disposition: attachment; filename=\"$fileName\"");
header("Content-Length: " . ($end - $start + 1));
header("Cache-Control: no-cache");
header("Pragma: no-cache");
header("Accept-Ranges: bytes"); // 声明支持断点续传

// 读取并输出文件
$file = fopen($filePath, 'rb');
fseek($file, $start);

$currentPos = $start;
while ($currentPos <= $end && !feof($file)) {
    if (connection_aborted()) {
        fclose($file);
        exit;
    }
    $readBytes = min($chunkSize, $end - $currentPos + 1);
    echo fread($file, $readBytes);
    flush();
    ob_flush();
    $currentPos += $readBytes;
    usleep(1000);
}

fclose($file);
exit;
?>

最后还要排查一下服务器的防火墙、安全插件(比如ModSecurity),有些会拦截长时间的HTTP请求;另外确保PHP的memory_limit足够,不过分块读取的方式不会占用太多内存,一般默认值就够。

内容的提问来源于stack exchange,提问作者Eduardo Amaral

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 08:03:37