php实现断点续传大文件示例代码


Posted in PHP onJune 19, 2020

一、断点续传原理

所谓断点续传,也就是要从文件已经下载的地方开始继续下载。在以前版本的 HTTP 协议是不支持断点的,HTTP/1.1 开始就支持了。一般断点下载时才用到 Range 和 Content-Range 实体头。

不使用断点续传

get /down.zip http/1.1
accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-
excel, application/msword, application/vnd.ms-powerpoint, */*
accept-language: zh-cn
accept-encoding: gzip, deflate
user-agent: mozilla/4.0 (compatible; msie 5.01; windows nt 5.0)
connection: keep-alive

 服务器收到请求后,按要求寻找请求的文件,提取文件的信息,然后返回给浏览器,返回信息如下:

HTTP/1.1 200 Ok
content-length=106786028
accept-ranges=bytes
date=mon, 30 apr 2001 12:56:11 gmt
etag=w/"02ca57e173c11:95b"
content-type=application/octet-stream
server=microsoft-iis/5.0
last-modified=mon, 30 apr 2001 12:56:11 gmt

使用断点续传

GET /down.zip HTTP/1.0
User-Agent: NetFox
RANGE: bytes=2000070-
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2

多了这么一行Range: bytes=2000070-

这一行的意思就是告诉服务器down.zip这个文件从2000070字节开始传,前面的字节不用传了。
Range的完整格式是:

Range: bytes=startOffset-targetOffset/sum [表示从startOffset读取,一直读取到targetOffset位置,读取总数为sum直接]
 
Range: bytes=startOffset-targetOffset [字节总数也可以去掉]

服务器收到这个请求以后,返回的信息如下:

HTTP/1.1 206 Partial Content
content-length=106786028
content-range=bytes 2000070-106786027/106786028
date=mon, 30 apr 2001 12:55:20 gmt
etag=w/"02ca57e173c11:95b"
content-type=application/octet-stream
server=microsoft-iis/5.0
last-modified=mon, 30 apr 2001 12:55:20 gmt

和前面服务器返回的信息比较一下,就会发现增加了一行:

Content-Range=bytes 2000070-106786027/106786028

返回的代码也改为206了,而不再是200了。

HTTP/1.1 206 Partial Content

知道了以上原理,就可以进行断点续传的编程了。

二、PHP实现

/** php下载类,支持断点续传
 * download: 下载文件
 * setSpeed: 设置下载速度
 * getRange: 获取header中Range
 */
 
class FileDownload{
 
 /** 下载
 * @param String $file 要下载的文件路径
 * @param String $name 文件名称,为空则与下载的文件名称一样
 * @param boolean $reload 是否开启断点续传
 */
 public function download($file, $name='', $reload=false){
 $fp = @fopen($file, 'rb');
 if($fp){
 if($name==''){
 $name = basename($file);
 }
 $header_array = get_headers($file, true);
 //var_dump($header_array);die;
 // 下载本地文件,获取文件大小
 if (!$header_array) {
 $file_size = filesize($file);
 } else {
 $file_size = $header_array['Content-Length'];
 }
 $ranges = $this->getRange($file_size);
 $ua = $_SERVER["HTTP_USER_AGENT"];//判断是什么类型浏览器
 header('cache-control:public');
 header('content-type:application/octet-stream'); 
 
 $encoded_filename = urlencode($name);
 $encoded_filename = str_replace("+", "%20", $encoded_filename);
 
 //解决下载文件名乱码
 if (preg_match("/MSIE/", $ua) || preg_match("/Trident/", $ua) ){ 
 header('Content-Disposition: attachment; filename="' .$encoded_filename . '"');
 } else if (preg_match("/Firefox/", $ua)) {
 header('Content-Disposition: attachment; filename*="utf8\'\'' . $name . '"');
 }else if (preg_match("/Chrome/", $ua)) {
 header('Content-Disposition: attachment; filename="' . $encoded_filename . '"');
 } else {
 header('Content-Disposition: attachment; filename="' . $name . '"');
 }
 //header('Content-Disposition: attachment; filename="' . $name . '"');
 
 if($reload && $ranges!=null){ // 使用续传
 header('HTTP/1.1 206 Partial Content');
 header('Accept-Ranges:bytes');
 
 // 剩余长度
 header(sprintf('content-length:%u',$ranges['end']-$ranges['start']));
 
 // range信息
 header(sprintf('content-range:bytes %s-%s/%s', $ranges['start'], $ranges['end'], $file_size));
 //file_put_contents('test.log',sprintf('content-length:%u',$ranges['end']-$ranges['start']),FILE_APPEND);
 // fp指针跳到断点位置
 fseek($fp, sprintf('%u', $ranges['start']));
 }else{
 file_put_contents('test.log','2222',FILE_APPEND);
 header('HTTP/1.1 200 OK');
 header('content-length:'.$file_size);
 }
 
 while(!feof($fp)){
 //echo fread($fp, round($this->_speed*1024,0));
 //echo fread($fp, $file_size);
 echo fread($fp, 4096);
 ob_flush();
 }
 
 ($fp!=null) && fclose($fp);
 }else{
 return '';
 }
 }
 
 /** 设置下载速度
 * @param int $speed
 */
 public function setSpeed($speed){
 if(is_numeric($speed) && $speed>16 && $speed<4096){
 $this->_speed = $speed;
 }
 }
 
 /** 获取header range信息
 * @param int $file_size 文件大小
 * @return Array
 */
 private function getRange($file_size){
 //file_put_contents('range.log', json_encode($_SERVER), FILE_APPEND);
 if(isset($_SERVER['HTTP_RANGE']) && !empty($_SERVER['HTTP_RANGE'])){
 $range = $_SERVER['HTTP_RANGE'];
 $range = preg_replace('/[\s|,].*/', '', $range);
 $range = explode('-', substr($range, 6));
 if(count($range)<2){
 $range[1] = $file_size;
 }
 $range = array_combine(array('start','end'), $range);
 if(empty($range['start'])){
 $range['start'] = 0;
 }
 if(empty($range['end'])){
 $range['end'] = $file_size;
 }
 return $range;
 }
 return null;
 }
}
 
$obj = new FileDownload();
$obj->download('http://down.golaravel.com/laravel/laravel-master.zip','', true);

以上就是php实现断点续传大文件示例代码的详细内容,更多关于php 断点续传大文件的资料请关注三水点靠木其它相关文章!

PHP 相关文章推荐
发挥语言的威力--融合PHP与ASP
Oct 09 PHP
社区(php&amp;&amp;mysql)六
Oct 09 PHP
PHP+DBM的同学录程序(3)
Oct 09 PHP
PHP操作MongoDB时的整数问题及对策说明
May 02 PHP
PHP的pcntl多进程用法实例
Mar 19 PHP
PHP微信开发之查询城市天气
Jun 23 PHP
PHP获取数组中单列值的方法
Jun 10 PHP
PHP设计模式之单例模式原理与实现方法分析
Apr 25 PHP
PHP扩展mcrypt实现的AES加密功能示例
Jan 29 PHP
php根据地址获取百度地图经纬度的实例方法
Sep 03 PHP
PHP Swoole异步Redis客户端实现方法示例
Oct 24 PHP
详解Go与PHP的语法对比
May 29 PHP
PHP基于openssl实现非对称加密代码实例
Jun 19 #PHP
如何在PHP环境中使用ProtoBuf数据格式
Jun 19 #PHP
基于PHP实现堆排序原理及实例详解
Jun 19 #PHP
深入分析PHP设计模式
Jun 15 #PHP
Laravel6.18.19如何优雅的切换发件账户
Jun 14 #PHP
Laravel服务容器绑定的几种方法总结
Jun 14 #PHP
Laravel如何实现适合Api的异常处理响应格式
Jun 14 #PHP
You might like
wiki-shan写的php在线加密的解密程序
2008/09/07 PHP
PHP 长文章分页函数 带使用方法,不会分割段落,翻页在底部
2009/10/22 PHP
PHP中spl_autoload_register()和__autoload()区别分析
2014/05/10 PHP
laravel 4安装及入门图文教程
2014/10/29 PHP
php中PDO方式实现数据库的增删改查
2015/05/17 PHP
php析构函数的简单使用说明
2015/08/24 PHP
PHP实现的通过参数生成MYSQL语句类完整实例
2016/04/11 PHP
Fleaphp常见函数功能与用法示例
2016/11/15 PHP
Python中使用django form表单验证的方法
2017/01/16 PHP
php面向对象程序设计中self与static的区别分析
2019/05/21 PHP
phpstudy隐藏index.php的方法
2020/09/21 PHP
使用微信内置浏览器点击下拉框出现页面乱跳转现象(iphone),该怎么办
2016/01/04 Javascript
jQuery基于Ajax方式提交表单功能示例
2017/02/10 Javascript
jquery实现全选、全不选以及单选功能
2017/03/23 jQuery
使用Bootrap和Vue实现仿百度搜索功能
2017/10/26 Javascript
如何去除vue项目中的#及其ie9兼容性
2018/01/11 Javascript
使用webpack构建应用的方法步骤
2019/03/04 Javascript
对于Python的Django框架使用的一些实用建议
2015/04/03 Python
Python 批量合并多个txt文件的实例讲解
2018/05/08 Python
pyQt4实现俄罗斯方块游戏
2018/06/26 Python
Pycharm之快速定位到某行快捷键的方法
2019/01/20 Python
Python字典遍历操作实例小结
2019/03/05 Python
python列表每个元素同增同减和列表元素去空格的实例
2019/07/20 Python
python读取文件指定行内容实例讲解
2020/03/02 Python
英国和世界各地鲜花速递专家:Arena Flowers
2018/02/10 全球购物
京东奢侈品:全球奢侈品牌
2018/03/17 全球购物
为什么说Ruby是一种真正的面向对象程序设计语言
2012/10/30 面试题
店长岗位职责
2013/11/21 职场文书
食堂个人先进事迹
2014/01/22 职场文书
高二历史教学反思
2014/01/25 职场文书
公务员年度个人总结
2015/02/12 职场文书
工程合作意向书范本
2015/05/09 职场文书
学生病假条怎么写
2015/08/17 职场文书
详解Python牛顿插值法
2021/05/11 Python
详解vue中v-for的key唯一性
2021/05/15 Vue.js
python 命令行传参方法总结
2021/05/25 Python