斷點續傳(下載篇)

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、抓包分析

詳見原文地址:

原文地址:http://www.cnblogs.com/phpstudy2015-6/p/6821478.html 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章