php实现断点续传大文件示例代码
作者:钱海海 发布时间:2024-04-28 09:45:17
标签:php,断点续传,文件
一、断点续传原理
所谓断点续传,也就是要从文件已经下载的地方开始继续下载。在以前版本的 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);
来源:https://www.cnblogs.com/qianhaihai/p/13154321.html
0
投稿
猜你喜欢
- 一、数据集下载加州高速公路PEMS数据集这里绘制PEMS04中的交通流量数据。该数据集中包含旧金山2018年1月1日至2月28日的29条道路
- 继续pygame实现俄罗斯方块游戏(AI篇1)的代码更新一、消除后才做评价上一篇我们是对方块落下的位置和落下后出来的空洞进行了评价,但是这些
- 微信应用号(微信公众平台小程序,「应用号」的新称呼)终于来了!开源中国社区的博卡君通宵吐血赶稿写出的微信公众平台应用号开发教程!大家赶紧来学
- 有些时候我在们需要的用正则提取出html中某一个部分的文字内容,如图:获取dd部分的html文档,我们要通过它的一个属性去确定他的位置才可以
- 过程名:ManualPagination作 用:采用手动分页方式显示文章具体的内容参 数:ArticleID,strContentSub&n
- 本文以实例形式展示了Python获取电脑硬件信息及状态的实现方法,是Python程序设计中很有实用价值的技巧。分享给大家供大家参考之用。具体
- 第一段代码:#!/usr/bin/python# -*- coding: utf-8 -*-import emailimport mimet
- 描述Python time time()返回当前时间的时间戳(1970 * 后经过的浮点秒数)。突然想看时间了,打开cmd发现脑中空荡,Jav
- 随机生成四位数验证码,包括汉字,数字,英文大小写。1.Servlet类package servlet;import java.awt.Bas
- 今天突然有同事问起,如何在sqlserver中调试存储过程(我们公司使用的是sqlserver 2008 R2),猛地一看,和以前使用sql
- 本文实例讲述了Python素数检测的方法。分享给大家供大家参考。具体如下:因子检测:检测因子,时间复杂度O(n^(1/2))def is_p
- 1.在查询分析器理启动或停止SQL Agent服务启动:use mastergoxp_cmdshell 'net start SQL
- 前言本章主要讲述Windows环境下的Jenkins的自动化部署关于JenKins下载的方式有两种,1)直接下载war包,用tomcat/j
- 本文实例讲述了Python实现的读写json文件功能。分享给大家供大家参考,具体如下:相比java,python对json文件的处理就简单很
- 马上就要过节了,想把自己的项目搞得酷炫一些,对整个网站的按钮添加图标、飘花效果、首屏大图展示、顶部导航背景图,于是就写了这一遍文字,如有兴趣
- 一、复习首先将上次画的矩形做复杂一些的小程序:import pygame,sys, randompygame.init()screen =
- MySQL中有很多的基本命令,show命令也是其中之一,在很多使用者中对show命令的使用还容易产生混淆,本文主要介绍了show命令的主要用
- 本文实例讲述了Python数据类型之Tuple元组。分享给大家供大家参考,具体如下:tuple元组1.概述本质上是一种有序的集合,和列表非常
- 简介想写一个登录注册的demo,但是以前的demo数据都写在程序里面,每一关掉程序数据就没保存住。。于是想着写到配置文件里好了Python自
- 实例化对象名._类名__私有属性名 class Flylove:price = 123 def __init__(self):s