上次我們談到了HTTP報文裏的div,知道了HTTP可以傳輸很多種類的數據,不僅是文本,也能傳輸圖片,音頻和視頻。

早期互聯網上傳輸的基本上都是隻有幾k大小的文本和小圖片,現在的情況則大有不同。網頁裏包含的信息實在太多了,隨隨便便一個主頁HTML就有可能上百K,高質量的圖片都以M論,更不要說那些電影,電視劇了,幾G,幾十G都有可能。

相比之下,100M的光纖固網或者4G移動網路在這些大文件的壓力下都變成了小水管,無論是上傳還是下載,都會把網路傳輸鏈路擠的滿滿當當。

所以如何在有限的帶寬下高效快捷的傳輸這些大文件就成了一個重要的課題,這就好比是已經打開了冰箱門,該怎麼把大象塞進去在關上門呢?

下面我們就一起看看HTTP協議裏有哪些手段能解決這個問題。

數據壓縮

通常瀏覽器在發送請求時都會帶著Accept-Encoding頭欄位,裡面是瀏覽器支持的壓縮格式,例如gzip,deflate等,這樣伺服器就可以從中選擇一種壓縮演算法,放進Content-Encoding響應頭裡,在把原數據壓縮後發給瀏覽器。

如果壓縮率能有50%,也就是說100k的數據能夠壓縮成50k的大小,那麼就相當於在帶寬不變的情況下網速提升了一倍,加速的效果是非常明顯的。

不過這個解決方法也有個缺點,gzip等壓縮演算法通常只對文本文件有較好的壓縮率,而圖片,音頻視頻等多媒體數據本身已經是高度壓縮的,在用gzip處理也不會變小,所以它就失效了。

不過數據壓縮在處理文本的時候效果還是很好的,所以各大網站的伺服器都會使用這個手段作為保底,例如,在nginx裏就會使用gzip on指令,啟用對text/html的壓縮。

分塊傳輸

在數據壓縮之外,還能有什麼辦法來解決大文件的問題呢?

壓縮是把大文件整體變小,我們可以反過來思考,如果大文件整體不能變小,那就把它拆開,分解成多個小塊,把這些小塊分批發給瀏覽器,瀏覽器收到後在組裝復原。

這樣瀏覽器和伺服器都不用在內存裏保存文件的全部,每次只收發一小部分,網路也不會被大文件長時間佔用,內存,帶寬等資源也就節省下來了。

這種化整為零的思路在HTTP協議裏就是chunked分塊傳輸編碼,在響應報文裏用頭欄位Transfer-Encoding:chunked來表示,意思是報文裏的div部分不是一次性發過來的,而是分成了許多的塊(chunk)逐個發送。

分塊傳輸也可以用於流式數據,例如有資料庫動態生成的表單頁面,這種情況下div數據的長度是未知的,無法在頭欄位Content-Length給出確切的長度,所以也只能用chunked方式分塊發送。

Transfer-Encoding:chunked和Content-Length這兩個欄位是互斥的,也就是說響應報文裏這兩個欄位不能同時出現,一個響應報文裏的傳輸要麼是長度已知,要麼是長度未知。

下面是分塊傳輸的編碼規則:

1.每個分塊包含兩個部分,長度頭和數據塊

2.長度頭是以CRLF結尾的一行明文,用16進位數字表示長度

3.數據塊緊跟在長度頭後,最後也用CRLF結尾,但數據不包含CRLF

4.最後用一個長度為0的塊表示結束

範圍請求

有了分塊傳輸編碼,伺服器就可以輕鬆收發大文件了,但對於上G的超大文件,還有一些問題需要考慮。

比如,你在看當下正熱播的電視劇,想跳過片頭,直接看正片,或者有段劇情無聊,想拖動進度條快進幾分鐘,這實際上是想獲取一個大文件其中的片段數據,而分塊傳輸並沒有這個能力。

HTTP協議為了滿足這樣的需求,提出了範圍請求的概念,允許客戶端在請求頭裡使用專用欄位來表示只獲取文件的一部分,相當於是客戶端的化整為零。

範圍請求不是web伺服器必備的功能,可以實現也可以不實現,所以伺服器必須在響應頭裡使用欄位Accept-Ranges:bytes明確告知客戶端:我是支持範圍請求的。

請求頭Range是HTTP範圍請求的專用欄位,格式是"bytes=x-y",其中的x和y是以位元組為單位的數據範圍。

要注意x,y表示的是偏移量,範圍必須從0計數,例如前10個位元組表示為0-9,第二個10位元組表示為10-19,而0-10實際上是前11個位元組。

Range的格式也很靈活,起點x和終點y可以不要,能夠很方便的表示正數或者倒數的範圍。假設文件是100個位元組,那麼:

"0-"表示從文檔起點到文檔終點,相當於"0-99",即整個文件

「10-」是從第10個位元組開始到文檔末尾,相當於"10-99"

"-1"是文檔的最後一個位元組,相當於"99-99"

"-10"是從文檔末尾倒數10個位元組,相當於"90-99"

伺服器收到Range欄位後,需要做四件事

第一,它必須檢查範圍是否合法,比如文件只有100個位元組,但是請求"200-300",這就是範圍越界了,伺服器就會返回狀態碼416,意思是你的範圍請求有誤,我無法處理,請在檢查一下

第二,如果範圍正確,伺服器就可以根據Range頭計算偏移量,讀取文件的片段了,返回狀態碼206 partial content,和200的意思差不多,但表示div只是原數據的一部分

第三,伺服器要添加一個響應頭欄位Content-Range,告訴片段的實際偏移量和資源的總大小,格式是bytes x-y/length,與Range頭區別在沒有「=」,範圍後多了總長度

最後剩下的就是發送數據了,直接把片段用TCP發送給客戶端,一個範圍請求就算是處理完了。

多段數據

剛才說的範圍請求一次只獲取一個片段,其實它還支持在Range頭裡使用多個"x-y",一次性獲取多個片段數據。

這種情況需要使用一種特殊的MIME類型:multipart/byteranges,表示報文的div是由多段位元組序列組成的,並且還要用一個參數boundary=xxx給出段之間的分隔標記。

每一個分段必須以"--boundary"開始,之後要用Content-Type和Content-Range標記這段數據的類型和所在範圍,然後就像普通的響應頭一樣以回車換行結束,在加上分段數據,最後用一個"--boundary--"表示所有的分段結束。

推薦閱讀:

相關文章