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 将字符串按大写字母分隔成字符串数组
Apr 30 PHP
解析thinkphp import 文件内容变量失效的问题
Jun 20 PHP
浅析php中如何在有限的内存中读取大文件
Jul 02 PHP
Smarty中常用变量操作符汇总
Oct 27 PHP
php实现修改新闻时删除图片的方法
May 12 PHP
PHP调试的强悍利器之PHPDBG
Feb 22 PHP
php微信支付接口开发程序
Aug 02 PHP
使用ThinkPHP的自动完成实现无限级分类实例详解
Sep 02 PHP
对PHP依赖注入的理解实例分析
Oct 09 PHP
php 解决扫描二维码下载跳转问题
Jan 13 PHP
PHPExcel实现表格导出功能示例【带有多个工作sheet】
Jun 13 PHP
Laravel 连接(Join)示例
Oct 16 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
Terran剧情介绍
2020/03/14 星际争霸
上海地方志办公室-上海电子仪表工业志
2021/03/04 无线电
PHP生成短网址的3种方法代码实例
2014/07/08 PHP
PHP将回调函数作用到给定数组单元的方法
2014/08/19 PHP
jQuery实现页面滚动时层智能浮动定位实例探讨
2013/03/29 Javascript
Javascript简单实现可拖动的div
2013/10/22 Javascript
简要了解jQuery移动web开发的响应式布局设计
2015/12/04 Javascript
Bootstrap模态框水平垂直居中与增加拖拽功能
2016/11/09 Javascript
js调用屏幕宽度的简单方法
2016/11/14 Javascript
AngularJS常见过滤器用法实例总结
2017/07/06 Javascript
javaScript中&quot;==&quot;和&quot;===&quot;的区别详解
2018/03/16 Javascript
Javascript如何递归遍历本地文件夹
2020/08/06 Javascript
[53:44]DOTA2-DPC中国联赛 正赛 PSG.LGD vs Magma BO3 第一场 1月31日
2021/03/11 DOTA
[01:00:49]DOTA2-DPC中国联赛 正赛 Ehome vs iG BO3 第二场 1月31日
2021/03/11 DOTA
Python原始字符串(raw strings)用法实例
2014/10/13 Python
python根据出生年份简单计算生肖的方法
2015/03/27 Python
Python下rrdtool模块的基本使用方法
2015/11/13 Python
详解python并发获取snmp信息及性能测试
2017/03/27 Python
Python实现随机选择元素功能
2017/09/14 Python
python实现堆和索引堆的代码示例
2018/03/19 Python
python 处理string到hex脚本的方法
2018/10/26 Python
Python最小二乘法矩阵
2019/01/02 Python
Python容器使用的5个技巧和2个误区总结
2019/09/26 Python
浅谈Python中range与Numpy中arange的比较
2020/03/11 Python
Python3.7下安装pyqt5的方法步骤(图文)
2020/05/12 Python
python中requests模拟登录的三种方式(携带cookie/session进行请求网站)
2020/11/17 Python
scrapy-splash简单使用详解
2021/02/21 Python
SKECHERS官方旗舰店:美国舒适运动休闲品牌
2017/12/22 全球购物
日本乐天德国站:Rakuten.de
2019/05/16 全球购物
Myholidays美国:在线旅游网站
2019/08/16 全球购物
介绍一下MD5加密算法
2016/11/12 面试题
计算机专业自我鉴定
2013/10/15 职场文书
好家长事迹材料
2014/01/23 职场文书
信访工作经验交流材料
2014/05/23 职场文书
2016秋季校长开学典礼致辞
2015/11/26 职场文书
python 实现mysql自动增删分区的方法
2021/04/01 Python