前幾天一個同事跑過來找我說,我們在廣告素材視訊這塊想做斷點續傳,就是這次某個視訊快取到一半,下次不用重頭開始,可以在原來停留得位置開始繼續下載.以提供更好的使用者體驗。
同時說需要我們支援吐素材地址的業務介面告訴終端最後修改時間/檔案簽名(md5),用這個用來判斷我當前要下的檔案有沒有變化,同時告訴終端檔案的Size大小.
我一細想,這個問題壓根不需要通過改變現有介面提供更多的資料來做.下面從原理實現上簡單說下:
關鍵點:
對於斷點續傳,關鍵點是兩個:
1. 終端知道當前的檔案和上一次載入的檔案是不是內容發生了變化,如果有變化,需要重新從offset 0 的位置開始下載
2. 終端記錄好上次成功下載到的offset,告訴server端,server端支援從特定的offset 開始吐資料
檔案變化感知:
前置業務介面方案:
對於關鍵點1,對於決定大部分產品的業務場景,可以通過前置業務介面解決;這裡簡單介紹一下:
對於非下載工具類的產品,如視訊APP(奇藝,優酷),視訊播放前會請求相關業務的資訊,主要返回片子叫什麼名字,主要演員等等一些列資訊,同時會返回一個對於播放最重要的資訊——播放地址。
播放地址就是我們可以做文章的地方,如果《太子妃第一集》這個片子更新了(被廣電要求減掉某個汙的畫面),可以後端系統讓這個業務介面吐不同的播放地址/一個不同的url引數(?ver=1.1)/位置引數(#ver1.1)。這樣純天然的URL變化能純天然的讓終端認為不是同一個片子,而需要重新載入。
HTPP 標準ETAG方案:
沒有業務介面的下載工具類的如何解決呢?
下載工具類的沒有前置介面,可以使用HTTP 的ETAG來標識是否檔案已經修改。
ETAG原理:如果URL上的資源內容改變,一個新的不一樣的ETag就會被分配。用這種方法使用ETag即類似於指紋,並且他們能夠被快速地被比較,以確定兩個版本的資源是否相同。ETag的比較只對同一個URL有意義——不同URL上的資源的ETag值可能相同也可能不同,從他們的ETag的比較中無從推斷。
ETAG是HTTP的一個可選欄位,且沒有規範他的實現;實際上業內用的比較多的就是使用MD5簽名的方式來生成(linux shell md5sum)
典型用法:
server端: Nginx >1.3.3 自帶有ETAG的module , 當然同時也可以在業務程式碼裡SetHeaders加一個ETAG欄位
client端:
第一次請求時:
String etag = httpURLConnection.getHeaderField("ETag");
ETag: "b428eab9654aa7c87091e"
第二次請求(斷點續傳時):
httpURLConnection.setRequestProperty(“If-None-Match”, "b428eab9654aa7c87091e");
If-None-Match: "b428eab9654aa7c87091e"
如果ETag值匹配,這就意味著資源沒有改變,伺服器便會傳送回一個極短的響應,包含HTTP “304 未修改”的狀態。304狀態告訴客戶端,它的快取版本是最新的,並應該使用它。
然而,如果ETag的值不匹配,這就意味著資源很可能發生了變化,那麼,一個完整的響應就會被返回,包括資源的內容,就好像ETag沒有被使用。這種情況下,客戶端可以用新返回的資源和新的ETag替代先前的快取版本。
續傳支援:
對於一個C/C++程式設計師,第一時間會得出一個系統級實現方案:
1. 客戶端傳當前的offset
2. server端seek到檔案特定的offset開始讀取往http connection吐資料
不過我們深處在一個開放方案和標準不斷完善的時代,不需要自己實現一個(這也是像我這樣的C/C++研發工程師越來越沒落的原因),來看看HTTP協議是怎麼解決這個問題的:
HTTP頭Range欄位:
Range : 用於客戶端到伺服器端的請求,可通過該欄位指定下載檔案的某一段大小,及其單位。典型的格式如:
Range: bytes=0-499 下載第0-499位元組範圍的內容
Range: bytes=500-999 下載第500-999位元組範圍的內容
Range: bytes=-500 下載最後500位元組的內容
Range: bytes=500- 下載從第500位元組開始到檔案結束部分的內容
來個簡單粗暴的例子
curl --header "Range: bytes=0-20000" xxx.com/memcache.pdf -o part1
curl --header "Range: bytes=20001-223651" xxx.com/memcache.pdf -o part2
cat part1 part2 >> a.pdf
衍生閱讀: