最新消息:

Web 开启对断点续传,php断点续传的文件下载类,PHP上传断点续传文件的方法

php admin 4284浏览 0评论

背景:大文件的断点续传,有时网络波动啥的,需要断点从已经下载位置续传下载文件,对于没有传过的文件再次从开始下载就麻烦了,这块http协议支持的,Apache和Nginx都支持这样的方法实现了从某个部分进行断点下载。
服务器是否支持断点续传的判断:
更多 0
断点续传 linux wget 服务器 curl
通常情况下,Web服务器(如Apache)会默认开启对断点续传的支持。因此,如果直接通过Web服务器来提供文件的下载,可以不必做特别的配置,即可享受到断点续传的好处。断点续传是在发起HTTP请求的时候加入RANGE头来告诉服务器客户端已经下载了多少字节。等所有这些请求都返回之后,再把得到的内容一块一块的拼接起来得到完整的资源。

Resumable download file Web服务器(如Apache)默认开启断点续传

你可以通过以下的命令来测试一下。

Linux 测试服务器是否支持断点续传

localhost [~]# wget -S http://httpd.apache.org/images/httpd_logo_wide_new.png 2>&1 | grep ‘Accept-Ranges’
Accept-Ranges: bytes

输出结果 Accept-Ranges: bytes ,说明服务器支持按字节下载。

curl 命令发送字节范围下载

curl –range 0-99 http://images.apple.com/home/images/billboard_iphone_hero.jpg

这样可以到最开始99字节,结果如下图:

curl range bytes request curl 命令发送字节范围请求

说明从服务器端按字节范围下载是完全没有问题的。

现在我们尝试以下方式:

1、一次性下载整个图片。

localhost [~]# curl –range 0-98315 http://images.apple.com/home/images/billboard_iphone_hero.jpg > test.jpg
% Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
Dload  Upload   Total   Spent    Left  Speed
100 98316  100 98316    0     0   524k      0 –:–:– –:–:– –:—:—  527k

完成后,test.jpg完全等于billboard_iphone_hero.jpg,文件大小为98,316 字节。

实践如下:我的Nginx服务器,请求下看是否支持,如下:
1)实践下下载这块的header返回头有Accept-Ranges: bytes证明Nginx也是支持断点续传下载的:

 wget -S http://justwinit.cn/template/trielegant/images/bridge-banner-nine.jpg
--2014-11-19 22:46:51--  http://justwinit.cn/template/trielegant/images/bridge-banner-nine.jpg
正在解析主机 justwinit.cn... 119.10.6.23
正在连接 justwinit.cn|119.10.6.23|:80... 已连接。
已发出 HTTP 请求,正在等待回应...
  HTTP/1.1 200 OK
  Server: nginx
  Date: Wed, 19 Nov 2014 14:34:46 GMT
  Content-Type: image/jpeg
  Content-Length: 7052
  Last-Modified: Fri, 07 Nov 2014 05:06:12 GMT
  Connection: keep-alive
  ETag: "545c5344-1b8c"
  Expires: Fri, 19 Dec 2014 14:34:46 GMT
  Cache-Control: max-age=2592000
  Accept-Ranges: bytes
长度:7052 (6.9K) [image/jpeg]
正在保存至: “bridge-banner-nine.jpg.1”
2)通地加上grep指令有返回即是支持的:
wget -S http://justwinit.cn/template/trielegant/images/bridge-banner-nine.jpg 2>&1 | grep 'Accept-Ranges'
  Accept-Ranges: bytes

3)用curl实现下载一段并保存到本地:
[codes=php]
 curl --range 0-99 http://justwinit.cn/template/trielegant/images/bridge-banner-nine.jpg  > bridge-banner-nine.jpg
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0   100    0   100    0     0    729      0 --:--:-- --:--:-- --:--:--  1886

(1)Curl包含range的请求头是这样的:
GET /template/trielegant/images/bridge-banner-nine.jpg HTTP/1.1
Request Version: HTTP/1.1
Range: bytes=0-108
User-Agent: curl/7.19.7 (i386-redhat-linux-gnu) libcurl/7.19.7 NSS/3.14.0.0 zlib/1.2.3 libidn/1.18 libssh2/1.4.2
Host: justwinit.cn
Accept: */*

(2)其抓包Nginx的返回头是这样:
HTTP/1.1 206 Partial Content
Server: nginx
Date: Wed, 19 Nov 2014 14:45:07 GMT
Content-Type: image/jpeg
Content-Length: 109
Last-Modified: Fri, 07 Nov 2014 05:06:12 GMT
Connection: keep-alive
ETag: “545c5344-1b8c”
Expires: Fri, 19 Dec 2014 14:45:07 GMT
Cache-Control: max-age=2592000
Content-Range: bytes 0-108/7052

4)通过前面的curl及wget联合起来,先后组合起来实现一个断点下载整个图片,并看其服务器返回头(curl已经下了前面的108,后从109开始wget:
(1)先保存一部分到108:

root@192.168.0.6:~# curl --range 0-108 http://justwinit.cn/template/trielegant/images/bridge-banner-nine.jpg  > bridge-banner-nine.jpg
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
109   109  109   109    0     0    907      0 --:--:-- --:--:-- --:--:--  1912

(2)再通过wget的断点续传下载命令-c,请求剩下的部分(Content-Range: bytes 109-7051/7052):
A)加上-S看返回头, -S,  –server-response         打印服务器响应。:

root@192.168.0.6:~# wget -S -c http://justwinit.cn/template/trielegant/images/bridge-banner-nine.jpg
--2014-11-19 22:53:16--  http://justwinit.cn/template/trielegant/images/bridge-banner-nine.jpg
正在解析主机 justwinit.cn... 119.10.6.23
正在连接 justwinit.cn|119.10.6.23|:80... 已连接。
已发出 HTTP 请求,正在等待回应...
  HTTP/1.1 206 Partial Content
  Server: nginx
  Date: Wed, 19 Nov 2014 14:41:12 GMT
  Content-Type: image/jpeg
  Content-Length: 6943
  Last-Modified: Fri, 07 Nov 2014 05:06:12 GMT
  Connection: keep-alive
  ETag: "545c5344-1b8c"
  Expires: Fri, 19 Dec 2014 14:41:12 GMT
  Cache-Control: max-age=2592000
  Content-Range: bytes 109-7051/7052
长度:7052 (6.9K),6943 (6.8K) 字节剩余 [image/jpeg]
正在保存至: “bridge-banner-nine.jpg”

100%[+=============================================================================================================>] 7,052       --.-K/s   in 0.1s

2014-11-19 22:53:16 (68.2 KB/s) - 已保存 “bridge-banner-nine.jpg” [7052/7052])

B)发起头如下,也就是说经curl保存一部分后,wget通过-c参数时,后面它会去读取目前文件大小,后写在http头里去找服务端要,请求头如下:

GET /template/trielegant/images/bridge-banner-nine.jpg HTTP/1.0
Request Version: HTTP/1.0
Range: bytes=109-
User-Agent: Wget/1.12 (linux-gnu)
Accept: */*
Host: justwinit.cn
Connection: Keep-Alive

注意:字节是从0开始,结束字节为总字节长度 减 1。

 

php 支持断点续传,主要依靠HTTP协议中 header HTTP_RANGE实现。

HTTP断点续传原理
Http头 Range、Content-Range()
HTTP头中一般断点下载时才用到Range和Content-Range实体头,
Range用户请求头中,指定第一个字节的位置和最后一个字节的位置,如(Range:200-300)
Content-Range用于响应头

请求下载整个文件:
GET /test.rar HTTP/1.1
Connection: close
Host: 116.1.219.219
Range: bytes=0-801 //一般请求下载整个文件是bytes=0- 或不用这个头

一般正常回应
HTTP/1.1 200 OK
Content-Length: 801
Content-Type: application/octet-stream
Content-Range: bytes 0-800/801 //801:文件总大小

FileDownload.class.php

    <?php  
    /** php下载类,支持断点续传
    *   Date:   2013-06-30
    *   Author: fdipzone
    *   Ver:    1.0
    *
    *   Func:
    *   download: 下载文件
    *   setSpeed: 设置下载速度
    *   getRange: 获取header中Range
    */  
      
    class FileDownload{ // class start  
      
        private $_speed = 512;   // 下载速度  
      
      
        /** 下载
        * @param String  $file   要下载的文件路径
        * @param String  $name   文件名称,为空则与下载的文件名称一样
        * @param boolean $reload 是否开启断点续传
        */  
        public function download($file, $name='', $reload=false){  
            if(file_exists($file)){  
                if($name==''){  
                    $name = basename($file);  
                }  
      
                $fp = fopen($file, 'rb');  
                $file_size = filesize($file);  
                $ranges = $this->getRange($file_size);  
      
                header('cache-control:public');  
                header('content-type:application/octet-stream');  
                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));  
                      
                    // fp指针跳到断点位置  
                    fseek($fp, sprintf('%u', $ranges['start']));  
                }else{  
                    header('HTTP/1.1 200 OK');  
                    header('content-length:'.$file_size);  
                }  
      
                while(!feof($fp)){  
                    echo fread($fp, round($this->_speed*1024,0));  
                    ob_flush();  
                    //sleep(1); // 用于测试,减慢下载速度  
                }  
      
                ($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){  
            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;  
        }  
      
    } // class end  
      
    ?>  

demo
[codes=php]

    <?php  
      
    require('FileDownload.class.php');  
    $file = 'book.zip';  
    $name = time().'.zip';  
    $obj = new FileDownload();  
    $flag = $obj->download($file, $name);  
    //$flag = $obj->download($file, $name, true); // 断点续传  
      
    if(!$flag){  
        echo 'file not exists';  
    }  
      
    ?>  

断点续传测试方法:
使用linux wget命令去测试下载, wget -c -O file http://xxx

1.先关闭断点续传
$flag = $obj->download($file, $name);
[plain] view plaincopy

    fdipzone@ubuntu:~/Downloads$ wget -O test.rar http://demo.fdipzone.com/demo.php  
    --2013-06-30 16:52:44--  http://demo.fdipzone.com/demo.php  
    正在解析主机 demo.fdipzone.com... 127.0.0.1  
    正在连接 demo.fdipzone.com|127.0.0.1|:80... 已连接。  
    已发出 HTTP 请求,正在等待回应... 200 OK  
    长度: 10445120 (10.0M) [application/octet-stream]  
    正在保存至: “test.rar”  
      
    30% [============================>                                                                     ] 3,146,580    513K/s  估时 14s  
    ^C  
    fdipzone@ubuntu:~/Downloads$ wget -c -O test.rar http://demo.fdipzone.com/demo.php  
    --2013-06-30 16:52:57--  http://demo.fdipzone.com/demo.php  
    正在解析主机 demo.fdipzone.com... 127.0.0.1  
    正在连接 demo.fdipzone.com|127.0.0.1|:80... 已连接。  
    已发出 HTTP 请求,正在等待回应... 200 OK  
    长度: 10445120 (10.0M) [application/octet-stream]  
    正在保存至: “test.rar”  
      
    30% [============================>                                                                     ] 3,146,580    515K/s  估时 14s  
    ^C  
      
    可以看到,wget -c不能断点续传  


2.开启断点续传
$flag = $obj->download($file, $name, true);
[plain] view plaincopy

    fdipzone@ubuntu:~/Downloads$ wget -O test.rar http://demo.fdipzone.com/demo.php  
    --2013-06-30 16:53:19--  http://demo.fdipzone.com/demo.php  
    正在解析主机 demo.fdipzone.com... 127.0.0.1  
    正在连接 demo.fdipzone.com|127.0.0.1|:80... 已连接。  
    已发出 HTTP 请求,正在等待回应... 200 OK  
    长度: 10445120 (10.0M) [application/octet-stream]  
    正在保存至: “test.rar”  
      
    20% [==================>                                                                               ] 2,097,720    516K/s  估时 16s  
    ^C  
    fdipzone@ubuntu:~/Downloads$ wget -c -O test.rar http://demo.fdipzone.com/demo.php  
    --2013-06-30 16:53:31--  http://demo.fdipzone.com/demo.php  
    正在解析主机 demo.fdipzone.com... 127.0.0.1  
    正在连接 demo.fdipzone.com|127.0.0.1|:80... 已连接。  
    已发出 HTTP 请求,正在等待回应... 206 Partial Content  
    长度: 10445121 (10.0M),7822971 (7.5M) 字节剩余 [application/octet-stream]  
    正在保存至: “test.rar”  
      
    100%[++++++++++++++++++++++++=========================================================================>] 10,445,121   543K/s   花时 14s    
      
    2013-06-30 16:53:45 (543 KB/s) - 已保存 “test.rar” [10445121/10445121])  
      
    可以看到会从断点的位置(%20)开始下载。  

源码下载地址:<a href="http://download.csdn.net/detail/fdipzone/5676439" target="_blank">点击下载 </a>
摘自:http://blog.csdn.net/fdipzone/article/details/9208221


PHP上传实现断点续传文件的方法:
其实说简单点就是通过这个变量$_SERVER['HTTP_RANGE']取得用户请求的文件的range,然后程序去控制文件的输出。比如第一次请求一个文件的从0到999字节,第二次请求1000到1999字节,以此类推,每次请求1000字节的内容,然后程序通过fseek函数去取得对应的文件位置,然后输出。
[codes=php]
    $fname = './05e58c19552bb26b158f6621a6650899';
    $fp = fopen($fname,'rb');
    $fsize = filesize($fname);
    if (isset($_SERVER['HTTP_RANGE']) && ($_SERVER['HTTP_RANGE'] != "") && preg_match("/^bytes=([0-9]+)-$/i", $_SERVER['HTTP_RANGE'], $match) && ($match[1] < $fsize)) {
    $start = $match[1];
    } else {
    $start = 0;
    }
    @header("Cache-control: public");
    @header("Pragma: public");
    if ($start > 0) {
    fseek($fp, $start);
    Header("HTTP/1.1 206 Partial Content");
    Header("Content-Length: " . ($fsize - $start));
    Header("Content-Ranges: bytes" . $start . "-" . ($fsize - 1) . "/" . $fsize);
    } else {
    header("Content-Length: $fsize");
    Header("Accept-Ranges: bytes");
    }
    @header("Content-Type: application/octet-stream");
    @header("Content-Disposition: attachment;filename=1.rm");
    fpassthru($fp);

大家也可以看下Discuz!论坛软件的attachment.php文件是如何实现断点续传的。请看代码:也是通过$_SERVER[‘HTTP_RANGE’]取得用户请求的文件的range,具体的大家可以查看其源码分析下。这里我就当抛砖引玉了。

 $range = 0;
if($readmod == 4) {
dheader('Accept-Ranges: bytes');
if(!emptyempty($_SERVER['HTTP_RANGE'])) {
list($range) = explode('-',(str_replace('bytes=', '', $_SERVER['HTTP_RANGE'])));
$rangesize = ($filesize - $range) > 0 ? ($filesize - $range) : 0;
dheader('Content-Length: '.$rangesize);
dheader('HTTP/1.1 206 Partial Content');
dheader('Content-Range: bytes='.$range.'-'.($filesize-1).'/'.($filesize));
}
}

转载请注明:爱开源 » Web 开启对断点续传,php断点续传的文件下载类,PHP上传断点续传文件的方法

您必须 登录 才能发表评论!