把大象裝進冰箱:HTTP傳輸大文件的方法


上次我們談到了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--"表示所有的分段結束。
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM