1、简介

这一篇文章主要介绍的是http协议下载时的断点续传,详细到各个步骤。主要步骤有:DNS查找、TCP三次握手、http请求发送、TCP协议数据传输、暂停后的状态、继续下载、TCP三次握手、http请求发送、数据传输、。。。、下载成功发送http响应信息、TCP四次握手断开连接。

2、原理知识


 2.1、问答问答

  问:什么是断点续传?断点续传的原理是什么?

  答:断点续传就是信号中断后(掉线或关机等),下次能够从上次的地方接着传送(一般指下载或上传),不支持断点续传就意味着下次下载或上传必须从零开始。http协议中的断点续传是基于Http头Range以及Content-Range。HTTP头中一般断点下载时才用到Range和Content-Range实体头,Range用户请求头中,指定第一个字节的位置和最后一个字节的位置,如( Range:200-300或者Range:200- );Content-Range用于响应头。通俗的来讲就是文件大小为10,这次下载了3,被中断了,下次继续下载时则将指针移到3位置,从3开始下载,最终将整个文件下载下来。

2.2、简单http下载文件

 请求下载整个文件: 
  GET /test.rar HTTP/1.1 
  Connection: close 
  Host: 192.168.95.11
  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:文件总大小

2.3、重要的几个头

  响应头:

Content-type:Content-type 告诉浏览器文件的MIME 类型,这是非常重要的一个响应头了,MIME种类繁多。很可能会在程序中漏掉一些MIME类型,表示全部为 content-type:application/octet-stream(字节流)

Content-Disposition:是 MIME 协议的扩展,MIME 协议指示 MIME 用户代理如何显示附加的文件。当 Internet Explorer 接收到头时,它会激活文件下载对话框,它的文件名框自动填充了头中指定的文件名。 嗯,就是这个头哟,激活弹出提示下载框,一般这样写content-disposition:attachment; filename=name


Content-Length:" Content-Length: 321" 就是告诉浏览器这个文件的大小是321字节,其实我发现好像不设置这个头,浏览器也能自己识别


Pragma Cache-control:把这2个头都设置成public 告诉浏览器缓存,我一般设置 cache-control:public


Content-Range:字段说明服务器返回了文件的某个范围及文件的总长度。这时Content-Length字段就不是整个文件的大小了,而是对应文件这个范围的字节数,这一点一定要注意。一般格式,Content-Range: bytes 500-999/1000

响应头:

Range:可以请求实体的一个或者多个子范围。

  例如:
  表示头500个字节:bytes=0-499
  表示第二个500字节:bytes=500-999
  表示最后500个字节:bytes=-500
  表示500字节以后的范围:bytes=500-  【下载断点续传(一般range格式为500-)】
  第一个和最后一个字节:bytes=0-0,-1
  同时指定几个范围:bytes=500-600,601-999
  但是服务器可以忽略此请求头,如果无条件GET包含Range请求头,响应会以状态码206(PartialContent)返回而不是以200(OK)。【206表示服务器已经完成get的部分请求,即表示断点续传】

3、PHP断点续传类


1 <?PHP
  2 #文件下载(支持断点续传)
  3 class FileDownload
  4 {
  5     #下载速度
  6     private $_speed = 512;
  7 
  8     /**
  9     * @desc 下载文件
 10     *  
 11     * @param $file string 下载的文件路径
 12     * @param $name string 保存文件时的文件名,不写则最终下载文件默认为原文件名
 13     * @param $reload bool 是否使用断点续传方式下载
 14     */
 15     public function download($file, $name='', $reload=false)
 16     {
 17         if(file_exists($file))  #判断文件是否存在
 18         {
 19             if($name == '')     #判断命名参数是否存在
 20             {
 21                 $name = basename($file);    #采用原文件名进行存储
 22             }
 23             $fHandle = fopen($file, 'rb');   #只读方式打开;为移植性考虑,使用b标记打开文件(不同系统有不同换行符)
 24             $fileSize = filesize($file);    #文件大小
 25             $ranges = $this->getRange($fileSize);  #断点续传时,先查看下载的区间范围
 26             header('cache-control:public');         #可以被任何缓存所缓存
 27             header('content-type:application/octet-stream');  #告诉浏览器响应的对象的类型(字节流、浏览器默认使用下载方式处理)
 28             header('content-disposition:attachment; filename='.$name); #不打开此文件,刺激浏览器弹出下载窗口
 29             #判断是否使用续传方式进行下载
 30             #且请求头ranges不能为null(为null表示第一次请求下载)
 31             if($reload && $ranges!=null)
 32             {
 33                 header('HTTP/1.1 206 Partial Content');     #发送自定义报文 206续传状态码
 34                 header('Accept-Ranges:bytes');              #表明服务器支持Range请求,所支持的单位是字节
 35                 # 剩余长度 
 36                 header(sprintf('content-length:%u',$ranges['end']-$ranges['start'])); 
 37                 # range信息 
 38                 header(sprintf('content-range:bytes %s-%s/%s', $ranges['start'], $ranges['end'], $fileSize));  
 39                 # fHandle指针跳到断点位置 
 40                 fseek($fHandle, sprintf('%u', $ranges['start'])); 
 41             }
 42             else
 43             {
 44                 header('HTTP/1.1 200 OK'); 
 45                 header('content-length:'.$fileSize);
 46             }
 47             while(!feof($fHandle))
 48             { 
 49                 echo fread($fHandle, round($this->_speed*1024,0)); 
 50                 ob_flush();    #把数据从PHP的缓冲中释放出来
 51                 //sleep(2); // 用于测试,减慢下载速度 
 52             } 
 53             ($fHandle!=null) && fclose($fHandle);
 54         }
 55         else
 56         {
 57             #没文件
 58             header("HTTP/1.1 404 Not Found");
 59             return false;
 60         }
 61     }
 62 
 63     /**
 64     * @desc 获取请求头部range信息
 65     *
 66     * @param $fileSize int 该文件的大小
 67     *
 68     * @return array|null 返回range信息或者null
 69     */
 70     public function getRange($fileSize)
 71     {
 72         if(isset($_SERVER['HTTP_RANGE']) && !empty($_SERVER['HTTP_RANGE']))
 73         {
 74             #请求头部range信息  Range: bytes=41078-\r\n
 75             $range = $_SERVER['HTTP_RANGE']; 
 76             $range = preg_replace('/[\s|,].*/', '', $range); 
 77             $range = explode('-', substr($range, 6));       #只需将41078-进行分割变成数组
 78             #断点续传头部range信息都是为 4444- 这种形式 ,因此切割后形成的数组就只有两个元素
 79             $range = array_combine(array('start','end'), $range); 
 80             if(empty($range['start']))
 81             { 
 82                 $range['start'] = 0; 
 83             } 
 84             if(empty($range['end']))
 85             { 
 86                 $range['end'] = $fileSize; 
 87             } 
 88             return $range; 
 89         }
 90         return null;    #第一次请求没有range信息
 91     }
 92 
 93     /**
 94     * @desc 设置文件下载速度
 95     *
 96     * @param $speed int 下载速度
 97     */
 98     public function setSpeed($speed)
 99     { 
100         if(is_numeric($speed) && $speed>16 && $speed<4096)
101         { 
102             $this->_speed = $speed; 
103         } 
104     } 
105 
106 }
107 
108 ?>

4、抓包分析