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 相关文章推荐
phpBB BBcode处理的漏洞
Oct 09 PHP
使用bcompiler对PHP文件进行加密的代码
Aug 29 PHP
php教程之phpize使用方法
Feb 12 PHP
PHP缓存机制Output Control详解
Jul 14 PHP
CodeIgniter中实现泛域名解析
Jul 19 PHP
php生成图片验证码
Jun 09 PHP
php关闭warning问题的解决方法
May 17 PHP
PHP实现批量检测网站是否能够正常打开的方法
Aug 23 PHP
php微信公众平台开发(三)订阅事件处理
Dec 06 PHP
PHP实现登录注册之BootStrap表单功能
Sep 03 PHP
PHP设计模式之策略模式原理与用法实例分析
Apr 04 PHP
Laravel 6 将新增为指定队列任务设置中间件的功能
Aug 06 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
一个简单的自动发送邮件系统(三)
2006/10/09 PHP
php简单封装了一些常用JS操作
2007/02/25 PHP
mysql_fetch_row,mysql_fetch_array,mysql_fetch_assoc的区别
2009/04/24 PHP
php遍历文件夹所有文件子文件夹函数代码
2013/11/27 PHP
ThinkPHP模板Switch标签用法示例
2014/06/30 PHP
yii2 resetful 授权验证详解
2017/05/18 PHP
laravel利用中间件防止未登录用户直接访问后台的方法
2019/09/30 PHP
Aster vs KG BO3 第二场2.18
2021/03/10 DOTA
懒就要懒到底——鼠标自动点击(含时间判断)
2007/02/20 Javascript
jQuery之选择组件的深入解析
2013/06/19 Javascript
js获取IFRAME当前的URL的方法
2013/11/13 Javascript
js在输入框屏蔽按键,只能键入数字的示例代码
2014/01/03 Javascript
鼠标滑过出现预览的大图提示效果
2014/02/26 Javascript
javascript针对cookie的基本操作实例详解
2015/11/30 Javascript
js中flexible.js实现淘宝弹性布局方案
2020/06/23 Javascript
js 将图片连接转换成base64格式的简单实例
2016/08/10 Javascript
JavaScript性能优化之函数节流(throttle)与函数去抖(debounce)
2016/08/11 Javascript
angularJs关于指令的一些冷门属性详解
2016/10/24 Javascript
Angular ng-repeat指令实例以及扩展部分
2016/12/26 Javascript
Nodejs高扩展性的模板引擎 functmpl简介
2017/02/13 NodeJs
使用javaScript实现鼠标拖拽事件
2020/04/03 Javascript
Node.js爬取豆瓣数据实例分析
2018/03/05 Javascript
基于JavaScript实现每日签到打卡轨迹功能
2018/11/29 Javascript
Node.js Event Loop各阶段讲解
2019/03/08 Javascript
vue中axios实现数据交互与跨域问题
2019/05/12 Javascript
Vue实现简单的拖拽效果
2020/08/25 Javascript
[01:32:50]DOTA2-DPC中国联赛 正赛 DLG vs XG BO3 第一场 1月25日
2021/03/11 DOTA
django实现分页的方法
2015/05/26 Python
Python中import导入上一级目录模块及循环import问题的解决
2016/06/04 Python
python如何实现int函数的方法示例
2018/02/19 Python
解决Django数据库makemigrations有变化但是migrate时未变动问题
2018/05/30 Python
应届生自荐信范文
2014/02/21 职场文书
工业自动化专业自荐信范文
2014/04/10 职场文书
教师党员个人剖析材料
2014/09/29 职场文书
楚门的世界观后感
2015/06/03 职场文书
redis实现排行榜功能
2021/05/24 Redis