Winform檔案下載之斷點續傳

77rou發表於2016-06-15

在本系列的前兩篇文章中,分別向大家介紹了用於完成下載任務的 WebClinet 和 WinINet 的基本用法和一些實用技巧。

今天來為大家講述下載過程中最常遇到的斷點續傳問題。

首先明確一點,本文所說的斷點續傳特指 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. 當我們透過位元組操作把一個檔案拼成原始大小後,如何驗證它和原始檔是一模一樣的?

接下來,本文分別針對以上問題,給出解決方法。


一、如何檢查伺服器端是否支援 Range頭?

在伺服器響應請求時,會在響應頭中透過 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。

當然,如果伺服器端也是我們自己建立的,我們就可以實現伺服器端方面的支援。目前已有部分產品在下載過程中提供斷點續傳的能力,就是其中之一。 

Demo 下載

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/28298702/viewspace-2120173/,如需轉載,請註明出處,否則將追究法律責任。

相關文章