我們經常使用下載工具,如bit精靈、迅雷、FlashGet,這些軟件都支持斷點續傳。
斷點續傳即下載任務暫停後可以繼續,而無需重新下載,即下載時需要通知服務器的起始位置。如果 允許多線程進行分片下載,必須提供起始-截止位置。說到底就是可以選擇下載某個片段,整個文件的字 節流,可以截取流的片段,也能實現流的累積,最終完成文件下載。
一、原理
在 HTTP/1.1裡新增的一個頭屬性:Range,也是現在眾多號稱多線程下載工具(如 FlashGet、迅雷 等)實現多線程下載的核心所在。老版本的HTTP協議不支持,所以一些老的服務器還不支持斷點續傳。
Range(請求參數)
用於請求頭中,指定第一個字節的位置和最後一個字節的位置,一般格式:
Range:(unit=first byte pos)-[last byte pos]
例如:Range:100-199,取文件流的100至199之間的字節。
Range:100,取位置為100後的所有字節。如果range 為正值,服務器應該開始發送從指定的 range 參 數到 HTTP 實體中數據的末尾之間的數據。
Range:-99,取開始的100個字節。如果range 為負值,服務器應該開始發送從 HTTP 實體中數據的開 頭到指定的 range 參數之間的數據。
Content-Range (響應參數)
用於響應頭,指定整個實體中的一部分的插入位置,他也指示了整個實體的長度。在服務器向客戶返 回一個部分響應,它必須描述響應覆蓋的范圍和整個實體長度。
一般格式:
Content-Range: bytes (unit first byte pos) - [last byte pos]/[entity legth]
例如:Content-Range: bytes 1024000-1126399/7421120
HTTP協議:http://www.w3.org/Protocols/rfc2616/rfc2616.html
二、C#中實現
在C#中使用AddRange方法向請求添加指定范圍的字節范圍標頭
System.Net.HttpWebRequest
所有的方法:

常用的方法為例:
void AddRange(longfrom, long to); 指定范圍起始、終止位置,來請求該片段的數據。
// 摘要:
// 向請求添加指定范圍的字節范圍標頭。
//
// 參數:
// from:
// 開始發送數據的位置。
//
// to:
// 停止發送數據的位置。
public void AddRange(long from, long to);
我們通過該方法,基於HTTP協議實現了斷點續傳,支持暫停、繼續下載功能,為了更清晰顯示效果, 提供了進度條顯示。效果圖:

Request
請求的Range參數,我們可以清晰看到起具體值,這就是請求的片段。在這裡可以清晰的看到HTTP協 議版本、請求方法、請求地址等信息

Response
服務器根據請求的Range參數,只返回該片段的數據。我們可以清晰看到Content-Range的具體值。
注意:返回的StatusCode變為了PartialContent,說明是部分數據。
代碼 含義 200 OK 請求成功返回 206 Partial Content 部分數據

查看本欄目
下載的核心代碼:
/// <summary>
/// 下載
/// </summary>
public void Download()
{
//從0計數,需要減一
long from = this.currentSize;
if (from < 0)
{
from = 0;
}
long to = this.currentSize + this.step - 1;
if (to >= this.totalSize && this.totalSize > 0)
{
to = this.totalSize - 1;
}
this.Download(from, to);
}
/// <summary>
/// 下載
/// </summary>
/// <param name="url"></param>
/// <param name="range"></param>
public void Download(long from,long to)
{
if (this.totalSize == 0)
{
GetTotalSize();
}
if (from >= this.totalSize || this.currentSize >= this.totalSize)
{
this.isFinished = true;
return;
}
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(url);
//request.Method = "GET";
request.AddRange("bytes", from, to);
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
string result = string.Empty;
if (response != null)
{
byte[] buffer = this.Buffer;
using (Stream stream = response.GetResponseStream())
{
int readTotalSize = 0;
int size = stream.Read(buffer, 0, buffer.Length);
while (size > 0)
{
//只將讀出的字節寫入文件
fs.Write(buffer, 0, size);
readTotalSize += size;
size = stream.Read(buffer, 0, buffer.Length);
}
//更新當前進度
this.currentSize += readTotalSize;
//如果返回的response頭中Content-Range值為空,說明服務器不支持Range屬性,不支持斷點續傳,返回的是所有數據
if (response.Headers["Content-Range"] == null)
{
this.isFinished = true;
}
}
}
}
項目源碼:
http://files.cnblogs.com/yank/DownloadSample.rar
三、多線程下載
上述例子提供了簡單的斷點續傳功能,如果想再進一步實現多線程下載。原理很簡單,我們只需根據 所要下載的文件大小,進行分塊,沒塊啟動一個線程進行下載,線程只負責下載自己負責的片段,一定 要嚴格設置Range的值。具體實現這裡不再介紹,如有興趣,下來可以繼續研究。
提醒一下:多線程下載的字節流如何保存為文件,是否必須按照先後順序?