今天來為大家講述下載下傳過程中最常遇到的斷點續傳問題。
首先明确一點,本文所說的斷點續傳特指 HTTP 協定中的斷點續傳,文章中講述了實作斷點續傳的方法思路和關鍵代碼,想了解更多細節的同學,請下載下傳并檢視本文附帶的 demo。
http 協定中定義了一些請求/響應頭,通過組合使用這些頭資訊,即可實作分批下載下傳同一檔案的目的。例如,在一次 http 請求中隻請求檔案中的一部分資料,然後将請求到的資料儲存起來,下次隻需請求剩餘部分的資料,當全部資料都下載下傳到本地後再完成資料的合并工作。
http 協定指出,可以通過 http 請求中的 Range 頭來指定請求資料的範圍。
Range 頭的使用很簡單,按照如下的格式使用即可:
Range: bytes=500-999
上述意思為:隻請求目标檔案的第500至第999,這500個位元組。
舉例說明,有一個1000 位元組大小的檔案需要下載下傳,第一次請求時不指定 Range 頭,表示下載下傳整個檔案;但在下載下傳完第499個位元組後,下載下傳被中斷了,那麼在下一次請求剩餘檔案時,隻需要下載下傳第500個至第999個位元組的資料即可。
原理看上去很簡單,但是需要考慮以下幾個問題:
1. 是不是所有的 web 伺服器都支援 Range 頭?
2. 多次請求之間可能會間隔很長的時間,伺服器上的檔案發生了變化怎麼辦?
3. 如何儲存下載下傳的部分資料和相關資訊?
4. 當我們通過位元組操作把一個檔案拼成原始大小後,如何驗證它和源檔案是一模一樣的?
接下來,本文分别針對以上問題,給出解決方法。
在伺服器響應請求時,會在響應頭中通過 Accept-Ranges 指明是否接受請求資源的一部分資料,這裡似乎有個小問題,就是不同的伺服器可能傳回不同的值來指明是否接受下載下傳部分資源的請求。比較統一的做法是:當伺服器不支援請求部分資料時,都會傳回 Accept-Ranges: none,是以隻需判斷傳回值是否等于 none 就可以了。
代碼如下:
private static bool IsAcceptRanges ( WebResponse res )
{
if ( res.Headers["Accept-Ranges"] != null )
{
string s = res.Headers["Accept-Ranges"];
if ( s == "none" )
{
return false;
}
}
return true;
}
當我們在下載下傳檔案的過程中,由于網絡故障等原因中斷了下載下傳過程,這時如果伺服器上的檔案已經變化了,那麼無論如何都需要重新從頭開始下載下傳,隻有當伺服器上的檔案沒有發生變化的情況下,斷點續傳才有意義。
當下次需要繼續下載下傳檔案時,如何确定伺服器上的檔案還是當初下載下傳了一半的檔案?
對于這個問題,http 響應頭為我們提供了兩種選擇,使用 ETag 和 Last-Modified 都能完成下載下傳任務。
先看 ETag:
The ETag response-header field provides the current value of the entity tag for the requested variant. (引自RFC2616 14.19 ETag)
簡單點說 ETag 就是一個辨別目前請求内容的字元串,當請求的資源發生變化後,對應的 ETag 也會變化,是以最簡單的辦法是,第一次請求時把響應頭中的 ETag 儲存下來,下次請求時做相應的比較。
string newEtag = GetEtag( response );
// tempFileName指已經下載下傳到本地的部分檔案内容
// tempFileInfoName指儲存了Etag内容的臨時檔案
if ( File.Exists(tempFileName) && File.Exists(tempFileInfoName) )
string oldEtag = File.ReadAllText( tempFileInfoName );
if ( !string.IsNullOrEmpty(oldEtag) && !string.IsNullOrEmpty(newEtag) && newEtag == oldEtag )
// Etag沒有變化,可以斷點續傳
resumeDowload = true;
else
if ( !string.IsNullOrEmpty(newEtag) )
File.WriteAllText( tempFileInfoName, newEtag );
//GetEtag函數
private static string GetEtag( WebResponse res )
if ( res.Headers["ETag"] != null )
return res.Headers["ETag"];
return null;
再看 Last-Modified:
The Last-Modified entity-header field indicates the date and time at which the origin server believes the variant was last modified. (引自RFC2616 14.29 Last-Modified)
Last-Modified 就是所請求的資源在伺服器上最後一次的修改時間,使用方法和 ETag 大體相同。
不論是使用 ETag 還是 Last-Modified,都能達到檢測伺服器端檔案是否發生變化的目的。
當然也可以同時使用這兩種方法,做 double check,以便更好的實作檢測目的。
這裡主要是指使用 C# 進行資料和相關資訊的儲存操作,大體思路是如果有未下載下傳完的檔案,先将已下載下傳資料儲存在某一路徑下,然後将後下載下傳的位元組資料添加到已下載下傳檔案的末尾。
詳細的實作方法,請檢視 demo 代碼。
在斷點續傳的過程中,我們以 byte 為機關進行檔案的下載下傳和合并,如果下載下傳的整個過程中出現了異常,可能最後得到的檔案就和源檔案不一樣了,是以最好能夠對下載下傳好的檔案進行一次與源檔案一緻性的校驗,這是很重要的一步,也是最難實作的部分。之是以難以實作,是因為需要伺服器端的支援,例如要求伺服器端不但提供了可供下載下傳的檔案,同時還需要提供該檔案的 MD5 hush。
<a href="http://files.cnblogs.com/files/powertoolsteam/BreakpointResume.zip" target="_blank">Demo 下載下傳</a>