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 相关文章推荐
一个简单实现多条件查询的例子
Oct 09 PHP
了解Joomla 这款来自国外的php网站管理系统
Mar 11 PHP
php GeoIP的使用教程
Mar 09 PHP
PHP内核介绍及扩展开发指南―基础知识
Sep 11 PHP
PHP filter_var() 函数 Filter 函数
Apr 25 PHP
input file获得文件根目录简单实现
Apr 26 PHP
PHP引用符&amp;的用法详细解析
Aug 22 PHP
php简单的留言板与回复功能具体实现
Feb 19 PHP
PHP中IP地址与整型数字互相转换详解
Aug 20 PHP
MySql数据库查询结果用表格输出PHP代码示例
Mar 20 PHP
PHP实现在数据库百万条数据中随机获取20条记录的方法
Apr 19 PHP
Laravel 5.5官方推荐的Nginx配置学习教程
Oct 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
用php将任何格式视频转为flv的代码
2009/09/03 PHP
PHP无限分类(树形类)的深入分析
2013/06/02 PHP
php查询mssql出现乱码的解决方法
2014/12/29 PHP
ThinkPHP3.1.x修改成功与失败跳转页面的方法
2017/09/29 PHP
javascript 操作Word和Excel的实现代码
2009/10/26 Javascript
jQuery实现等比例缩放大图片让大图片自适应页面布局
2013/10/16 Javascript
JavaScript对象反射用法实例
2015/04/17 Javascript
JQuery+EasyUI轻松实现步骤条效果
2016/02/22 Javascript
JS打开摄像头并截图上传示例
2017/02/18 Javascript
Angular2的管道Pipe的使用方法
2017/11/07 Javascript
webpack构建的详细流程探底
2018/01/08 Javascript
nodejs简单读写excel内容的方法示例
2018/03/16 NodeJs
jQuery实现动态加载select下拉列表项功能示例
2018/05/31 jQuery
VUE 3D轮播图封装实现方法
2018/07/03 Javascript
VUE.CLI4.0配置多页面入口的实现
2019/11/25 Javascript
jQuery使用hide()、toggle()函数实现相机品牌展示隐藏功能
2021/01/29 jQuery
[47:18]完美世界DOTA2联赛循环赛 IO vs FTD BO2第一场 11.05
2020/11/06 DOTA
通过数据库向Django模型添加字段的示例
2015/07/21 Python
Python利用Beautiful Soup模块搜索内容详解
2017/03/29 Python
TensorFlow实现非线性支持向量机的实现方法
2018/04/28 Python
pandas通过loc生成新的列方法
2018/11/28 Python
QML使用Python的函数过程解析
2019/09/26 Python
在tensorflow中实现屏蔽输出的log信息
2020/02/04 Python
CSS3效果:自定义“W”形运行轨迹实例
2017/03/29 HTML / CSS
美国高端婴童品牌:Hanna Andersson
2016/10/30 全球购物
Under Armour安德玛法国官网:美国高端运动科技品牌
2018/06/29 全球购物
俄罗斯最大的在线手表商店:Bestwatch.ru
2020/01/11 全球购物
给同学的道歉信
2014/01/16 职场文书
护士自我鉴定怎么写
2014/02/07 职场文书
《蜗牛的奖杯》教后反思
2014/04/24 职场文书
质量承诺书格式
2014/05/20 职场文书
特教教师先进事迹
2014/05/21 职场文书
文秘专业应届生求职信
2014/05/26 职场文书
排查整治工作方案
2014/06/09 职场文书
python如何利用traceback获取详细的异常信息
2021/06/05 Python
Mysql分库分表之后主键处理的几种方法
2022/02/15 MySQL