PHP实现文件下载断点续传详解


Posted in PHP onOctober 15, 2014

如果我们的网站提供文件下载的服务,那么通常我们都希望下载可以断点续传(Resumable Download),也就是说用户可以暂停下载,并在未来的某个时间从暂停处继续下载,而不必重新下载整个文件。

通常情况下,Web服务器(如Apache)会默认开启对断点续传的支持。因此,如果直接通过Web服务器来提供文件的下载,可以不必做特别的配置,即可享受到断点续传的好处。由于这些文件直接通过Web服务器来提供下载,后端脚本无法对这个下载过程进行控制。这对于仅提供公开、静态文件的网站来说不是问题,但对于需要提供私有、动态文件的网站来说,直接通过Web服务器来提供下载就无法满足需求了。这时,就需要在编写后台脚本程序时,加入对断点续传的支持。

本文将以PHP为例,简要介绍实现文件下载断点续传的方法。

原理

断点续传的原理还是比较直观的。

HTTP协议规定了如何传输某个资源的一部分,而不是全部。比如,有一个文件的大小是1000字节,浏览器可以只请求该文件的前300个字节,或者只请求第500到第1000个字节。通过这种方式,就可以不必在一次请求中传输某个资源的全部内容,而是发起多次请求,每次仅请求其中的一部分内容。等所有这些请求都返回之后,再把得到的内容一块一块的拼接起来得到完整的资源。

实现断点续传就是要利用HTTP协议的上述特性。当用户暂停下载的时候,浏览器会记录已经下载到什么位置,当用户在未来某一时间恢复下载时,就可以从上次暂停的位置继续下载,而不必从头开始。

实现

由于部分传输不是强制的,服务器可以支持也可以不支持,所以,我们需要在程序中告诉浏览器,它请求的资源是否支持部分传输。这可以通过设置HTTP的 Accept-Ranges 响应头信息来实现。PHP代码如下:

header('Accept-Ranges: bytes');

Accept-Ranges: bytes 告诉浏览器,该资源支持以字节为单位的部分传输。这个响应头需要附加在支持部分传输的所有资源上。

当接受到一个请求时,我们需要从浏览器的请求中提取浏览器具体是在请求资源的哪一个部分。这个信息是通过 Range 请求头来传递的。在PHP中,它被存储在$_SERVER['HTTP_RANGE']中。我们需要检查这个变量是否定义了,如果定义了,则使用该值,否则,就将range设为整个资源。

$range = "0-". ($content_length-1);

if(isset($_SERVER['HTTP_RANGE'])){

    $range = $_SERVER['HTTP_RANGE'];

}

接下来,就需要分析 $range 的值,来决定返回资源的哪一部分内容。可能的取值示例:
100-200 // 第100到第200字节

500-    // 第500字节到文件末尾

-1000   // 最后的1000个字节

这里需要注意,得到一个Range之后,你需要对它的取值进行检验,包括:

1.开始位置非负
2.结束位置需要大于开始位置
3.开始位置需要小于文件长度减一 (因为这里的位置索引是从0开始的)
4.若结束位置大于文件长度减一,则需要把它的值设置为文件长度减一

如果Range的取值不合法,则需要终止程序并告知浏览器:

header('HTTP/1.1 416 Requested Range Not Satisfiable');

为了保持文章简洁,具体的校验代码这里就不提供了。下面假定你已经校验了Range的取值,并得到了 $start 和 $end 两个变量,分别表示开始位置和结束位置。

接下来要做的就是把文件的对应部分的内容发送给浏览器。不过要注意的是,这里涉及到需要发送多个HTTP响应头信息,具体如下:

header('HTTP/1.1 206 Partial Content');

header('Accept-Ranges: bytes');

header("Content-Range: bytes $start-$end/$filesize");

$length = $end - $start + 1;

header("Content-Length: $length");
/* 输出文件的指定部分 */

这里的$length需要注意一下,它的取值是本次传输的内容的长度,而不是整个文件的长度。另外需要注意的一点是,这里的HTTP状态码是206,不是200。

总结

文件下载的断点续传实际上是利用了HTTP协议中对传输部分文件的支持。而HTTP协议的这一特性不仅可以用于实现断点续传,客户端程序也可以利用它来实现多线程下载。

在实现断点续传的过程中,需要注意正确设置各种HTTP头信息。错误的头信息将导致用户下载到的文件损坏,无法使用。

PHP 相关文章推荐
php+dojo 的数据库保存拖动布局的一个方法dojo 这里下载
Mar 07 PHP
删除数组元素实用的PHP数组函数
Aug 18 PHP
php中一个有意思的日期逻辑处理
Mar 25 PHP
PHP数据类型之布尔型的介绍
Apr 28 PHP
php中json_encode处理gbk与gb2312中文乱码问题的解决方法
Jul 10 PHP
PHP之预定义接口详解
Jul 29 PHP
解析WordPress中的post_class与get_post_class函数
Jan 04 PHP
PHP预定义变量9大超全局数组用法详解
Apr 23 PHP
php禁用函数设置及查看方法详解
Jul 25 PHP
CI框架(CodeIgniter)公共模型类定义与用法示例
Aug 10 PHP
PHP实现文件上传操作和封装
Mar 04 PHP
PHP的imageTtfText()函数深入详解
Mar 03 PHP
PHP多进程编程实例
Oct 15 #PHP
PHP实现采集中国天气网未来7天天气
Oct 15 #PHP
跟我学Laravel之视图 & Response
Oct 15 #PHP
跟我学Laravel之请求与输入
Oct 15 #PHP
跟我学Laravel之路由
Oct 15 #PHP
跟我学Laravel之请求(Request)的生命周期
Oct 15 #PHP
跟我学Laravel之配置Laravel
Oct 15 #PHP
You might like
php a simple smtp class
2007/11/26 PHP
PHP SQLite类
2009/05/07 PHP
如何使用php判断所处服务器操作系统的类型
2013/06/20 PHP
PHP使用pcntl_fork实现多进程下载图片的方法
2014/12/16 PHP
thinkphp多表查询两表有重复相同字段的完美解决方法
2016/09/22 PHP
Ajax+Jpgraph实现的动态折线图功能示例
2019/02/11 PHP
Javascript 作用域使用说明
2009/08/13 Javascript
document.getElementById方法在Firefox与IE中的区别
2010/05/18 Javascript
jquery下实现overlay遮罩层代码
2010/08/25 Javascript
json数据的列循环示例
2013/09/06 Javascript
jqGrid日期格式的判断示例代码(开始日期与结束日期)
2013/11/08 Javascript
JavaScript中的函数重载深入理解
2014/08/04 Javascript
javascript实现状态栏文字首尾相接循环滚动的方法
2015/07/22 Javascript
js+css实现上下翻页相册代码分享
2015/08/18 Javascript
Javascript中的迭代、归并方法详解
2016/06/14 Javascript
canvas绘制表盘时钟
2017/01/23 Javascript
常用的js方法合集
2017/03/10 Javascript
vue组件间通信六种方式(总结篇)
2019/05/15 Javascript
python实现的生成随机迷宫算法核心代码分享(含游戏完整代码)
2014/07/11 Python
python模拟事件触发机制详解
2018/01/19 Python
遗传算法python版
2018/03/19 Python
python3中类的继承以及self和super的区别详解
2019/06/26 Python
Python Pandas中根据列的值选取多行数据
2019/07/08 Python
Python从入门到精通之环境搭建教程图解
2019/09/26 Python
Python接口自动化测试框架运行原理及流程
2020/11/30 Python
使用 css3 transform 属性来变换背景图的方法
2019/05/07 HTML / CSS
英国露营设备和户外服装购物网站:Simply Hike
2019/05/05 全球购物
自动化职业生涯规划书范文
2014/01/03 职场文书
音乐教学反思
2014/02/02 职场文书
2014年党务公开实施方案
2014/02/27 职场文书
闭幕式主持词
2014/04/02 职场文书
贷款承诺书
2015/01/20 职场文书
教师党员个人总结
2015/02/10 职场文书
秋季运动会加油词
2015/07/18 职场文书
2019年教师入党申请书
2019/06/27 职场文书
Mysql中调试存储过程最简单的方法
2021/06/30 MySQL