1. 程式人生 > >把大象裝進冰箱:HTTP傳輸大檔案的方法

把大象裝進冰箱: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--"表示所有的分段結束。