文章出處

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

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

首先明確一點,本文所說的斷點續傳特指 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。

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

Demo 下載


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()