文件下载限速

首先,我们写一段使用php输出文件给浏览器下载的代码<?php

/**

* Created by PhpStorm.

* User: tioncico

* Date: 19-2-4

* Time: 下午4:30

*/

$filePath = './hyxd.zip';//文件

$fp=fopen($filePath,"r");

//取得文件大小

$fileSize=filesize($filePath);

header("Content-type:application/octet-stream");//设定header头为下载

header("Accept-Ranges:bytes");

header("Accept-Length:".$fileSize);//响应大小

header("Content-Disposition: attachment; filename=testNaame");//文件名

$buffer=1024;

$bufferCount=0;

while(!feof($fp)&&$fileSize-$bufferCount>0){//循环读取文件数据

$data=fread($fp,$buffer);

$bufferCount+=$buffer;

echo $data;//输出文件

}

fclose($fp);

可以看出,php实现浏览器下载文件,主要是靠header头的支持以及echo 文件数据,那么,该如何限制速度呢?可以通过限制输出频率吗?例如每次读取1024之后,就进行一次sleep?<?php

/**

* Created by PhpStorm.

* User: tioncico

* Date: 19-2-4

* Time: 下午4:30

*/

$filePath = './hyxd.zip';//文件

$fp=fopen($filePath,"r");

//取得文件大小

$fileSize=filesize($filePath);

header("Content-type:application/octet-stream");//设定header头为下载

header("Accept-Ranges:bytes");

header("Accept-Length:".$fileSize);//响应大小

header("Content-Disposition: attachment; filename=testName");//文件名

$buffer=1024;

$bufferCount=0;

while(!feof($fp)&&$fileSize-$bufferCount>0){//循环读取文件数据

$data=fread($fp,$buffer);

$bufferCount+=$buffer;

echo $data;//输出文件

sleep(1);//增加了一个sleep

}

fclose($fp);

但是通过浏览器访问,我们发现是不行的,甚至造成了浏览器只有在n秒之后才会出现下载确认框,是哪里出了问题呢?

其实,这是因为php的buffer引起的,php buffer缓冲区,会使php不会马上输出数据,而是需要等缓冲区满之后才会响应到web服务器,通过web服务器再响应到浏览器中,详细请看:关于php的buffer(缓冲区)

那该怎么改呢?其实很简单,只需要使用ob系列函数就可解决:<?php

/**

* Created by PhpStorm.

* User: tioncico

* Date: 19-2-4

* Time: 下午4:30

*/

$filePath = './hyxd.zip';//文件

$fp=fopen($filePath,"r");

//取得文件大小

$fileSize=filesize($filePath);

header("Content-type:application/octet-stream");//设定header头为下载

header("Accept-Ranges:bytes");

header("Accept-Length:".$fileSize);//响应大小

header("Content-Disposition: attachment; filename=testName");//文件名

ob_end_clean();//缓冲区结束

ob_implicit_flush();//强制每当有输出的时候,即刻把输出发送到浏览器

header('X-Accel-Buffering: no'); // 不缓冲数据

$buffer=1024;

$bufferCount=0;

while(!feof($fp)&&$fileSize-$bufferCount>0){//循环读取文件数据

$data=fread($fp,$buffer);

$bufferCount+=$buffer;

echo $data;//输出文件

sleep(1);

}

fclose($fp);

这样,我们就已经实现了,每秒只输出1024字节的数据:

我们可以增加下载速度,把buffer改成更大的值,例如102400,那么就会变成每秒下载100kb:

文件断点续传

那么,我们该如何实现文件断点续传呢?首先,我们要了解http协议中,关于请求头的几个参数:

content-range和range,

在文件断点续传中,必须包含一个断点续传的参数,例如:

请求下载头:

Range: bytes=0-801 //一般请求下载整个文件是bytes=0- 或不用这个头

响应文件头:

Content-Range: bytes 0-800/801 //801:文件总大小

正常下载文件时,不需要使用range头,而当断点续传时,由于再之前已经获得了n字节数据,所以可以直接请求

Range: bytes=n字节-总文件大小,代表着n字节之前的数据不再下载

响应头也是如此,那么,我们通过之前的限速下载,进行暂停,然后继续下载试试吧:

可看到,我们下载到600kb之后暂停了,然后我们代码记录下下次请求的请求数据:<?php

/**

* Created by PhpStorm.

* User: tioncico

* Date: 19-2-4

* Time: 下午4:30

*/

$filePath = './hyxd.zip';//文件

$fp=fopen($filePath,"r");

set_time_limit(1);

//取得文件大小

$fileSize=filesize($filePath);

file_put_contents('1.txt',json_encode($_SERVER));

//下面的代码直接忽略了,主要看server

当我点击继续下载时,浏览器会报出下载失败,原因是我们没有正确的响应它需要的数据,然后我们看下1.txt并打印成数组:

可看到,浏览器增加了一个range的请求头参数,想请求61400字节-文件尾的文件数据,那么,我们后端该如何处理呢?

我们只需要输出61400之后的文件内容即可

为了方便测试查看,我将文件改为了2.txt,内容如下:

编写可断点续传代码:<?php

/**

* Created by PhpStorm.

* User: tioncico

* Date: 19-2-4

* Time: 下午4:30

*/

$filePath = './2.txt';//文件

$fp=fopen($filePath,"r");

//set_time_limit(1);

//取得文件大小

$fileSize=filesize($filePath);

$buffer=5000;

$bufferCount=0;

header("Content-type:application/octet-stream");//设定header头为下载

header("Content-Disposition: attachment; filename=2.txt");//文件名

if (!empty($_SERVER['HTTP_RANGE'])){

//切割字符串

$range = explode('-',substr($_SERVER['HTTP_RANGE'],6));

fseek($fp,$range[0]);//移动文件指针到range上

header('HTTP/1.1 206 Partial Content');

header("Content-Range: bytes $range[0]-$fileSize/$fileSize");

header("content-length:".$fileSize-$range[0]);

}else{

header("Accept-Length:".$fileSize);//响应大小

}

ob_end_clean();//缓冲区结束

ob_implicit_flush();//强制每当有输出的时候,即刻把输出发送到浏览器

header('X-Accel-Buffering: no'); // 不缓冲数据

while(!feof($fp)&&$fileSize-$bufferCount>0){//循环读取文件数据

$data=fread($fp,$buffer);

$bufferCount+=$buffer;

echo $data;//输出文件

sleep(1);

}

fclose($fp);

使用谷歌浏览器进行下载并暂停

查看当前下载内容:

可看到,最后下载到的字符串为13517x,恢复浏览器下载,继续暂停

成功对接,并看到现在断点在51017x中,继续下载直到完成:

使用代码验证:$txt = file_get_contents('/home/tioncico/Downloads/2.txt');

$arr = explode('x',$txt);

var_dump(count($arr));

var_dump($arr[count($arr)-2]);

成功下载

多线程下载

通过前面,我们或许发现了什么:

1:限速是限制当前连接的数量

2:可以通过range来实现文件分片下载

那么,我们能不能使用多个连接,每个连接只下载x个字节,到最后进行拼装成一个文件呢?答案是可以的

下面,我们就使用php的curl_multi进行多线程下载<?php

$filePath = '127.0.0.1/2.txt';

//查看文件大小

$ch = curl_init();

//$headerData = [

//    "Range: bytes=0-1"

//];

//curl_setopt($ch, CURLOPT_HTTPHEADER, $headerData);

curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "HEAD");

curl_setopt($ch, CURLOPT_URL, $filePath);

curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); // return don't print

curl_setopt($ch, CURLOPT_TIMEOUT, 30);

curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)');

curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); // 302 redirect

curl_setopt($ch, CURLOPT_MAXREDIRS, 7);

curl_setopt($ch, CURLOPT_HEADER, true);//需要获取header头

curl_setopt($ch, CURLOPT_NOBODY, 1);    //不需要body,只需要获取header头的文件大小

$sContent = curl_exec($ch);

// 获得响应结果里的:头大小

$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);//获取header头大小

// 根据头大小去获取头信息内容

$header = substr($sContent, 0, $headerSize);//获取真实的header头

curl_close($ch);

$headerArr = explode("\r\n", $header);

foreach ($headerArr as $item) {

$value = explode(':', $item);

if ($value[0] == 'Content-Length') {

$fileSize = (int)$value[1];//文件大小

break;

}

}

//开启多线程下载

$mh = curl_multi_init();

$count = 5;//n个线程

$handle = [];//n线程数组

$data = [];//数据分段数组

$fileData = ceil($fileSize / $count);

for ($i = 0; $i

$ch = curl_init();

//判断是否读取数量大于剩余数量

if ($fileData > ($fileSize-($i * $fileData))) {

$headerData = [

"Range:bytes=" . $i * $fileData . "-" . ($fileSize)

];

}else{

$headerData = [

"Range:bytes=" . $i * $fileData . "-" .(($i+1)*$fileData)

];

}

echo PHP_EOL;

curl_setopt($ch, CURLOPT_URL, $filePath);

curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); // return don't print

curl_setopt($ch, CURLOPT_TIMEOUT, 30);

curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)');

curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); // 302 redirect

curl_setopt($ch, CURLOPT_HTTPHEADER, $headerData);

curl_setopt($ch, CURLOPT_MAXREDIRS, 7);

curl_multi_add_handle($mh, $ch); // 把 curl resource 放进 multi curl handler 里

$handle[$i] = $ch;

}

$active = null;

do {

//同时执行多线程,直到全部完成或超时

$mrc = curl_multi_exec($mh, $active);

} while ($active);

for ($i = 0; $i

$data[$i] = curl_multi_getcontent($handle[$i]);

curl_multi_remove_handle($mh, $handle[$i]);

}

curl_multi_close($mh);

$file = implode('',$data);//组合成一个文件

$arr = explode('x',$file);

var_dump($data);

var_dump(count($arr));

var_dump($arr[count($arr)-2]);

//测试文件是否正确

运行截图:

该代码将会开出5个线程,按照不同的文件段去同时下载,再最后组装成一个字符串,即实现了多线程下载

以上代码是访问nginx直接测试的,之前的代码不支持head  http头,我们需要修改一下才可以支持(但这是标准http写法)

我们需要修改下之前的代码,使其支持range的结束位置:<?php

/**

* Created by PhpStorm.

* User: tioncico

* Date: 19-2-4

* Time: 下午4:30

*/

$filePath = './2.txt';//文件

$fp = fopen($filePath, "r");

//set_time_limit(1);

//取得文件大小

$fileSize = filesize($filePath);

$buffer = 50000;

$bufferCount = 0;

header("Content-type:application/octet-stream");//设定header头为下载

header("Content-Disposition: attachment; filename=2.txt");//文件名

if (!empty($_SERVER['HTTP_RANGE'])) {

//切割字符串

$range = explode('-', substr($_SERVER['HTTP_RANGE'], 6));

fseek($fp, $range[0]);//移动文件指针到range上

header('HTTP/1.1 206 Partial Content');

header("Content-Range: bytes $range[0]-$range[1]/$fileSize");

$range[1]>0&&$fileSize=$range[1];//只获取range[1]的数量

header("content-length:" . $fileSize - $range[0]);

} else {

header("Accept-Length:" . $fileSize);//响应大小

}

ob_end_clean();//缓冲区结束

ob_implicit_flush();//强制每当有输出的时候,即刻把输出发送到浏览器

header('X-Accel-Buffering: no'); // 不缓冲数据

while (!feof($fp) && $fileSize-$range[0] - $bufferCount > 0) {//循环读取文件数据

//避免多读取

$buffer>($fileSize-$range[0]-$bufferCount)&&$buffer=$fileSize-$range[0]-$bufferCount;

$data = fread($fp, $buffer);

$bufferCount += $buffer;

echo $data;//输出文件

sleep(1);

}

fclose($fp);

修改下多线程下载代码:<?php

$filePath = '127.0.0.1';

//查看文件大小

$ch = curl_init();

$headerData = [

"Range: bytes=0-1"

];

curl_setopt($ch, CURLOPT_HTTPHEADER, $headerData);

//curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "HEAD");

curl_setopt($ch, CURLOPT_URL, $filePath);

curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); // return don't print

curl_setopt($ch, CURLOPT_TIMEOUT, 0);

curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)');

curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); // 302 redirect

curl_setopt($ch, CURLOPT_MAXREDIRS, 7);

curl_setopt($ch, CURLOPT_HEADER, true);//需要获取header头

curl_setopt($ch, CURLOPT_NOBODY, 1);    //不需要body,只需要获取header头的文件大小

$sContent = curl_exec($ch);

// 获得响应结果里的:头大小

$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);//获取header头大小

// 根据头大小去获取头信息内容

$header = substr($sContent, 0, $headerSize);//获取真实的header头

curl_close($ch);

$headerArr = explode("\r\n", $header);

foreach ($headerArr as $item) {

$value = explode(':', $item);

if ($value[0] == 'Content-Range') {//通过分段,获取到文件大小

$fileSize = explode('/',$value[1])[1];//文件大小

break;

}

}

var_dump($fileSize);

//开启多线程下载

$mh = curl_multi_init();

$count = 5;//n个线程

$handle = [];//n线程数组

$data = [];//数据分段数组

$fileData = ceil($fileSize / $count);

for ($i = 0; $i

$ch = curl_init();

//判断是否读取数量大于剩余数量

if ($fileData > ($fileSize-($i * $fileData))) {

$headerData = [

"Range:bytes=" . $i * $fileData . "-" . ($fileSize)

];

}else{

$headerData = [

"Range:bytes=" . $i * $fileData . "-" .(($i+1)*$fileData)

];

}

echo PHP_EOL;

curl_setopt($ch, CURLOPT_URL, $filePath);

curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); // return don't print

curl_setopt($ch, CURLOPT_TIMEOUT, 0);

curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)');

curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); // 302 redirect

curl_setopt($ch, CURLOPT_HTTPHEADER, $headerData);

curl_setopt($ch, CURLOPT_MAXREDIRS, 7);

curl_multi_add_handle($mh, $ch); // 把 curl resource 放进 multi curl handler 里

$handle[$i] = $ch;

}

$active = null;

do {

//同时执行多线程,直到全部完成或超时

$mrc = curl_multi_exec($mh, $active);

} while ($active);

for ($i = 0; $i

$data[$i] = curl_multi_getcontent($handle[$i]);

curl_multi_remove_handle($mh, $handle[$i]);

}

curl_multi_close($mh);

$file = implode('',$data);//组合成一个文件

$arr = explode('x',$file);

var_dump($data);

var_dump(count($arr));

var_dump($arr[count($arr)-2]);

//测试文件是否正确

成功下载,测试耗时结果为:5个线程4秒左右完成,1个线程花费13秒完成

本文为仙士可原创文章,转载无需和我联系,但请注明来自仙士可博客www.php20.cn

php文件断点续传,php文件下载限速,文件断点续传,多线程下载文件原理解析相关推荐

  1. java 多线程下载文件并实时计算下载百分比(断点续传)

    多线程下载文件 多线程同时下载文件即:在同一时间内通过多个线程对同一个请求地址发起多个请求,将需要下载的数据分割成多个部分,同时下载,每个线程只负责下载其中的一部分,最后将每一个线程下载的部分组装起来 ...

  2. C# 多线程下载文件功能实现,优化文件下载不全问题

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.I ...

  3. 多线程下载文件实践之旅

    目录 1.使用场景 2.多线程下载原理 3.请求如何分段下载 3.1.需要请求的数据如何分段. 3.2.分段下载的数据如何组装成完整的数据文件. 4.关键代码实现 3.成果展现 4.总结 5.参考文章 ...

  4. Java实现多线程下载文件

    这是本人在实际开发当中遇到的多线程下载文件并记录下来 public class DownloadUtil {private String pathFile;private String strFile ...

  5. Java多线程下载文件

    Java多线程下载文件 优化:合理利用服务器资源,将资源利用最大化,加快下载速度 一般有两种方式: 线程池里面有N个线程,多线程下载单个文件,将网络路径的文件流切割成多快,每个线程下载一小部分,然后写 ...

  6. Http多线程下载文件的处理机制

    本文出自博客Vander丶CSDN博客,如需转载请标明出处,尊重原创谢谢 博客地址:http://blog.csdn.net/l540675759/article/details/62111148 导 ...

  7. 在QT中采用多线程下载文件

    在QT中采用多线程下载文件 这里的线程是指下载的通道(和操作系统中的线程不一样),一个线程就是一个文件的下载通道,多线程也就是同时开起好几个下载通道.当服务器提供下载服务 时,使用下载者是共享带宽的, ...

  8. 文件多线程下载的原理与代码展示

    首先,我们要下载一个文件,可以通过多线程的方式快速下载!!! 多线程下载文件的步骤: 1.首先要知道请求下载的服务器支持断点下载,即支持request头信息中的Range的设置 2.然后通过对请求头设 ...

  9. python多线程下载文件

    看到一篇多线程下载的文章,这里把自己的理解写一篇多线程下载的文章. 我们访问http://192.168.10.7/a.jpg时是get请求,response的head包含Content-Length ...

最新文章

  1. 对象类什么是面向对象(1)
  2. 2021年春季学期-信号与系统-第八次作业参考答案-第十小题
  3. win10+计算机安全配置,做好个人电脑安全隐私设置Windows10系统磁盘数据加密操作...
  4. Java生鲜电商平台-生鲜供应链(采购管理)
  5. Memcached 在linux上安装笔记
  6. apache 设置缓存
  7. 大哥特斯拉:造车“三傻”,咱们抱团?
  8. 几个容易混淆的对齐概念
  9. Qt学习之路(54): 自定义拖放数据对象
  10. Python基础代码大全,都在这里了,初学者必看
  11. 安卓pdf阅读器_2020年双十一有哪些电纸书、电子书阅读器值得买?Kindle、掌阅、文石、科大讯飞哪个好?...
  12. Linux根目录解析
  13. 计算机主机拆解图,电脑的主机结构是怎样的 电脑主机结构图【图文】
  14. AD采样前级电路的分析
  15. License之外,社区的规则与潜规则
  16. 印度黑客号称世界第一,结果第二天被中国黑客干掉了
  17. 哈工大计算机系统Lab4.Tiny Shell
  18. Jackson JsonParser 和 JsonGenerator
  19. 【discuz x3】home_follow.php(广播页面)中$alist = getfollowfeed($vuid, $view, true, $start, $perpage);结果
  20. 教你轻松学会用PYTHON给对象画圣诞树

热门文章

  1. cbecame计算机辅助教育,计算机辅助教育习资料.doc
  2. postman代码生成
  3. Redisson基本用法
  4. python全栈开发,Day1(python介绍,变量,if,while)
  5. DeepMind研究:测试神经网络的抽象推理
  6. Oceanbase查询改写:谓词移动
  7. ELF符号表分析(转载)
  8. Python详细知识体系总结(2021年2月8日)学Python的一定要看
  9. Vue入门之组件化开发
  10. iOS beta固件升级的坑