斷點續傳瞭解一下啊?

好想吃涼皮發表於2018-04-09
是不是被圖片騙進來了?哈哈不要著急走,其實也不是跟圖沒關係,我們這次就是說斷點續傳的那些事。
我們寫程式碼的有時候工作中會做一些上傳/下載圖片或者檔案的操作。萬一線路中斷,不具備斷點續傳的 HTTP/FTP 伺服器或下載軟體就只能從頭重傳,比較好的 HTTP/FTP 伺服器或下載軟體具有斷點續傳能力,允許使用者從上傳/下載斷線的地方繼續傳送,這樣大大減少了使用者的煩惱。
複製程式碼

獲取部分內容的範圍請求

其實啊,為了實現中斷恢復下載的需求,需要能下載指定下載的實體範圍:

  • 請求頭中的Range來指定 資源的byte範圍
  • 響應會返回狀態碼206響應報文
  • 對於多重範圍的範圍請求,響應會在首部欄位Content-Type中標明multipart/byteranges 我們來模擬一下: 先開啟我們的命令列工具,寫上如下程式碼 (後面是我在百度上找的一個圖片)
 curl -v --header "Range:bytes=0-10" http://pic26.nipic.com/20130117/10558908_124756651000_2.jpg
複製程式碼

執行一下得到的這個結果:

斷點續傳瞭解一下啊?

從http那段伺服器返回給我們的資訊可以看出如下資訊(206代表伺服器答應我們的range請求方式)

  • Content-Type: image/jpeg 型別是圖片
  • Accept-Ranges: bytes 單位是bytes
  • Content-Range: bytes 0-10/292871 請求段為0-10,總共有292871bytes
  • Content-Length: 11 請求長度為11 知道這些之後我們可以簡單模擬一下客戶端和伺服器端怎麼做的:

伺服器端程式碼

首先客戶端要發一個頭Range:bytes=0-10,然後服務端返回一個頭 // Accept-Ranges:bytes // Content-Range:0-10/總大小

// 獲取範圍請求
let http = require('http');
let fs = require('fs');
let path = require('path');
let { promisify } = require('util');//這個模組是包裝某個東西成為promise的。
let stat = promisify(fs.stat);

let server = http.createServer(async function (req, res) {

    let p = path.join(__dirname, 'con.txt');
    // 判斷當前檔案的大小
    let statObj = await stat(p);
    let start = 0;
    let end = statObj.size - 1; // 讀流是包前又包後的:statObj.size 是檔案的大小。
    let total = end
    let range = req.headers['range'];
    if (range) {//判斷客戶端是否有帶range的請求
        // 告訴它支援範圍請求
        res.setHeader('Accept-Ranges','bytes');
        // ['匹配的字串','第一個分組']
        let result = range.match(/bytes=(\d*)-(\d*)/);
        start = result[1]?parseInt(result[1]):start;
        end = result[2]?parseInt(result[2])-1:end;
        // 獲取成功並且檔案總大小是多少
        // Content-Range:0-10/總大小
        res.setHeader('Content-Range',`${start}-${end}/${total}`)
    }
    res.setHeader('Content-Type', 'text/plain;charset=utf8');
    fs.createReadStream(p, { start, end }).pipe(res);
});
server.listen(3000);
複製程式碼

這樣一個簡單的伺服器端就寫好了。

客戶端程式碼

//首先要寫一個options 
let options = {
    hostname:'localhost',
    port:3000,
    path:'/',
    method:'GET'
}
let fs = require('fs');
let path = require('path');
let http = require('http');
let ws = fs.createWriteStream('./download.txt');
let pause = false;
let start = 0;
// 下載,每次獲取10個
process.stdin.on('data',function(chunk){
    chunk = chunk.toString();
    if(chunk.includes('p')){  //輸入p就是暫停
        pause = true
    }else{
        pause = false;
        download();
    }
});
function download(){
    options.headers = {
        Range:`bytes=${start}-${start+10}`
    }
    start+=10;
    // 發請求
    http.get(options,function(res){
        let range = res.headers['content-range'];
        let total = range.split('/')[1];
        let buffers = [];//建立一個快取區,把讀到的資料都放在裡面
        //,等到end的時候就整個取出來。
        res.on('data',function(chunk){
            buffers.push(chunk);
        });
        res.on('end',function(){
            //將獲取的資料寫入到檔案中
            ws.write(Buffer.concat(buffers));
            setTimeout(function(){
                if(pause === false&&start<total){
                    download();
                }   
            },1000)
        })
    })
}
download();
複製程式碼

程式碼非常淺顯易懂。 最後我們在同級目錄下建立一個con.txt檔案。用node執行一下客戶端檔案,就會實現功能啦。

場景校驗

有時候即使終端發起續傳請求是url對應問問價在伺服器端已經發生變化,那麼很顯然此時需要一個標識問價唯一的方式,

  • Etag(請求頭) ===> if-none-match
  • Last-Modified(請求頭) ===> if-modified-since

Etag

Etag(Entity Tags)主要為了解決 Last-Modified 無法解決的一些問題。

  • 一些檔案也許會週期性的更改,但是內容並不改變(僅改變修改時間),這時候我們並不希望客戶端認為這個檔案被修改了,而重新 GET。
  • 某些檔案修改非常頻繁,例如:在秒以下的時間內進行修改(1s 內修改了 N 次),If-Modified-Since 能檢查到的粒度是 s 級的,這種修改無法判斷(或者說 UNIX 記錄 MTIME 只能精確到秒)。
  • 某些伺服器不能精確的得到檔案的最後修改時間。

為此,HTTP/1.1 引入了 Etag。Etag 僅僅是一個和檔案相關的標記,可以是一個版本標記,例如:v1.0.0;或者說 “627-4d648041f6b80” 這麼一串看起來很神祕的編碼。但是 HTTP/1.1 標準並沒有規定 Etag 的內容是什麼或者說要怎麼實現,唯一規定的是 Etag 需要放在 “” 內,一般情況下Etag的值是時間+檔案大小

Last-Modified

If-Modified-Since,和 Last-Modified 一樣都是用於記錄頁面最後修改時間的 HTTP 頭資訊,只是 Last-Modified 是由伺服器往客戶端傳送的 HTTP 頭,而 If-Modified-Since 則是由客戶端往伺服器傳送的頭,可以看到,再次請求本地存在的 cache 頁面時,客戶端會通過 If-Modified-Since 頭將先前伺服器端發過來的 Last-Modified 最後修改時間戳傳送回去,這是為了讓伺服器端進行驗證,通過這個時間戳判斷客戶端的頁面是否是最新的,如果不是最新的,則返回新的內容,如果是最新的,則返回 304 告訴客戶端其本地 cache 的頁面是最新的,於是客戶端就可以直接從本地載入頁面了,這樣在網路上傳輸的資料就會大大減少,同時也減輕了伺服器的負擔。 如果想了解具體怎麼實現可以看看靜態服務裡面的快取。

是不是躍躍欲試??

如果對你有幫助請點個贊噻!

相關文章