PHP实现文件下载断点续传功能详解
在Web开发中,大文件下载若因网络波动中断,用户需重新下载十分不便。通过PHP处理HTTP Range请求头并返回206 Partial Content状态,可实现高效的断点续传功能。本文将解析其核心原理并提供可直接使用的PHP代码实现。
断点续传的实现原理
断点续传的核心在于客户端与服务器对HTTP协议中特定字段的交互:
- 客户端发起请求:若之前已下载部分数据,客户端会在请求头中包含
Range: bytes=xxxx-字段,指示期望从哪个字节位置开始接收。 - 服务器响应:PHP脚本检测到该头部后,不发送完整的200 OK,而是设置
HTTP/1.1 206 Partial Content状态码。 - 数据分段传输:配合
Content-Range头(格式为bytes start-end/total)和正确的Content-Length,告知浏览器当前传输的范围及总大小。
PHP 断点续传代码实现
以下代码演示了如何构建一个支持断点续传的下载函数 dl_file_resume。该函数会检测文件、确定MIME类型,并根据是否有Range头分别处理完整下载和断点续传逻辑。
第1步:初始化环境与参数检查
首先定义入口函数,接收文件路径作为参数。验证文件是否存在,获取文件大小、文件名及扩展名,用于后续的内容类型判断。
function dl_file_resume($file) {
// 1. 检测文件是否存在
if (!is_file($file)) { die("<b>404 File not found!</b>"); }
$len = filesize($file); // 获取文件大小 (Total Size)
$filename = basename($file); // 获取文件名
// 获取文件扩展名并转为小写
$file_extension = strtolower(substr(strrchr($filename, "."), 1));
}
第2步:设置 MIME 内容类型
根据文件扩展名设置对应的 Content-Type,以确保浏览器能正确识别文件格式。对于未知类型,默认使用通用的强制下载类型。
// 2. 根据扩展名指定输出浏览器格式
switch ($file_extension) {
case "exe": $ctype = "application/octet-stream"; break;
case "zip": $ctype = "application/zip"; break;
case "mp3": $ctype = "audio/mpeg"; break;
case "mpg": $ctype = "video/mpeg"; break;
case "avi": $ctype = "video/x-msvideo"; break;
default: $ctype = "application/force-download";
}
第3步:处理 HTTP 请求头(核心逻辑)
此步骤区分“首次下载”和“断点续传”。关键在于解析 $_SERVER['HTTP_RANGE']。
注意:原代码中 `str_replace` 用法存在逻辑瑕疵,在实际应用中应直接解析字符串结构。下方展示了修正后的健壮性写法:
// 3. 缓存控制与基础 Header
header("Cache-Control: public");
header("Content-Type: $ctype");
// 处理 IE 浏览器文件名编码问题
if (strstr($_SERVER['HTTP_USER_AGENT'], "MSIE")) {
$iefilename = preg_replace('/\./', '%2e', $filename, substr_count($filename, '.') - 1);
header("Content-Disposition: attachment; filename=\"$iefilename\"");
} else {
header("Content-Disposition: attachment; filename=\"$filename\"");
}
header("Accept-Ranges: bytes");
$size = filesize($file);
// 4. 判断是否为断点续传请求
if (isset($_SERVER['HTTP_RANGE'])) {
// --- 断点续传模式 (Partial Content) ---
// 解析格式如: bytes=4390912-
list($a, $range) = explode("=", $_SERVER['HTTP_RANGE']);
// 清理 range 字符串,去除可能的非数字字符或空格
$range = trim($range, ' -');
// 如果 range 为空或无效,通常意味着从最后开始或全量,这里简化处理
if ($range === '' || !is_numeric($range)) {
// 简单的 fallback,实际项目建议更严格的校验
$start_pos = 0;
$range_str = "0-";
} else {
$start_pos = intval($range);
$range_str = $range . "-";
}
$end_pos = $size - 1; // 文件最后一个字节索引
$new_length = $end_pos - $start_pos + 1; // 待下载的长度
// 发送 206 状态码及相关头信息
header("HTTP/1.1 206 Partial Content");
header("Content-Length: $new_length");
header("Content-Range: bytes $start_pos-$end_pos/$size");
} else {
// --- 首次完整下载模式 ---
$end_pos = $size - 1;
header("Content-Range: bytes 0-$end_pos/$size");
header("Content-Length: $size");
}
第4步:读取并输出文件流
使用 fopen 以二进制只读模式打开文件,利用 fseek 定位到起始字节,然后通过循环读取并输出,同时刷新缓冲区以保证实时传输效果。
// 5. 打开文件并定位指针
$fp = fopen($file, "rb");
// 如果有断点续传,跳过已下载部分
$start_offset = isset($_SERVER['HTTP_RANGE']) ?
(list($a, $r)=explode('=', $_SERVER['HTTP_RANGE']), intval(trim($r))) : 0;
if ($fp) {
fseek($fp, $start_offset);
// 关闭执行时间限制,防止大文件下载超时
set_time_limit(0);
// 循环输出数据块
while(!feof($fp)) {
print(fread($fp, 1024 * 8)); // 每次输出 8KB
flush(); // 刷新 PHP 缓冲
ob_flush(); // 刷新 Web Server 缓冲
}
fclose($fp);
}
exit;
}
使用注意事项
- 兼容性:大多数现代浏览器和下载工具(如 Thunderbird, IDM)均支持
Range头。但对于某些旧式或非标准客户端,可能仍需降级处理。 - 安全性:在生产环境中,务必对传入的
$file进行严格的路径校验,防止目录遍历攻击(Path Traversal),例如过滤掉..等字符。 - 性能:对于超大文件(GB级别),直接使用PHP逐字节读取可能占用较多内存和时间。可考虑结合操作系统命令如
system("cat \"$file\" | tail -c +$offset")或直接配置 Nginx/Apache 处理静态文件下载,PHP 仅做权限验证。
以上就是PHP实现文件下载断点续传的详细内容,更多关于PHP断点续传的资料请关注本站其它相关文章!
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









