大文件下載斷網、崩潰後能從中斷處繼續,而非重頭開始,這是下載器的基本素質。本文深度解析 HTTP Range 請求原理、.NET 核心類(HttpWebRequest、FileStream)用法,並提供生產級可運行的斷點續傳代碼,含異常處理、進度回調、多線程擴展接口。
Range: bytes=起始位置-Accept-Ranges: bytes,回應 206 Partial Content 及 Content-RangeFileStream.Seek 定位到文件末尾,追加寫入響應流| 類 | 關鍵成員 | 用途 |
|---|---|---|
HttpWebRequest | AddRange(start) | 設置 Range 請求頭 |
HttpWebResponse | GetResponseStream() | 獲取響應數據流 |
FileStream | Seek(offset, SeekOrigin) | 移動文件指針到斷點 |
Stream | Read/Write(byte[], offset, count) | 塊讀寫,控制緩衝區大小 |
using System;
using System.IO;
using System.Net;
using System.Threading;
public class ResumableDownloader
{
public event Action ProgressChanged; // (已下載, 總大小)
public event Action Completed;
public event Action Failed;
public void Download(string url, string filePath, CancellationToken token = default)
{
long startPos = 0;
FileMode mode = FileMode.Create;
// 1. 檢查本地文件是否存在,決定續傳位置
if (File.Exists(filePath))
{
var fi = new FileInfo(filePath);
startPos = fi.Length;
mode = FileMode.Append;
}
try
{
var request = (HttpWebRequest)WebRequest.Create(url);
request.Method = "GET";
request.Timeout = 30000;
request.ReadWriteTimeout = 60000;
// 2. 設置 Range:從 startPos 續傳
if (startPos > 0)
request.AddRange(startPos);
using (var response = (HttpWebResponse)request.GetResponse())
using (var respStream = response.GetResponseStream())
using (var fileStream = new FileStream(filePath, mode, FileAccess.Write, FileShare.None, 8192))
{
long totalSize = startPos + response.ContentLength; // 總大小 = 已有 + 本次響應
byte[] buffer = new byte[8192];
int bytesRead;
long downloaded = startPos;
while ((bytesRead = respStream.Read(buffer, 0, buffer.Length)) > 0)
{
token.ThrowIfCancellationRequested();
fileStream.Write(buffer, 0, bytesRead);
downloaded += bytesRead;
ProgressChanged?.Invoke(downloaded, totalSize);
}
fileStream.Flush();
}
Completed?.Invoke();
}
catch (OperationCanceledException)
{
// 用戶取消,保留已下載部分
}
catch (Exception ex)
{
Failed?.Invoke(ex);
}
}
}
class Program
{
static void Main()
{
string url = "https://speed.hetzner.de/100MB.bin"; // 測試用大文件
string path = @"C:\Downloads\100MB.bin";
var dl = new ResumableDownloader();
dl.ProgressChanged += (done, total) =>
Console.Write($"\r進度: {done * 100.0 / total:F1}% ({done}/{total} bytes)");
dl.Completed += () => Console.WriteLine("\n下載完成!");
dl.Failed += ex => Console.WriteLine($"\n失敗: {ex.Message}");
dl.Download(url, path);
Console.ReadLine();
}
}
Range: bytes=start-end,合併時注意順序。Thread.Sleep 或 TokenBucket 算法控制寫入頻率。MD5/SHA256 或 ETag。HttpClient + HttpCompletionOption.ResponseHeadersRead,支持 Range、自動解壓、HTTP/2。| 現象 | 原因 | 解決 |
|---|---|---|
| 服務端回 200 而非 206 | 不支持 Range 或範圍無效 | 檢查 Accept-Ranges、重試全量下載 |
| 續傳後文件損壞 | 文件已被修改(ETag 變化) | 下載前比對 Last-Modified/ETag |
| 大文件 > 2GB 失敗 | int 溢出 | 全程用 long、AddRange(long) 重載 |
| 網絡抖動頻繁異常 | 無重試機制 | 引入 Polly 重試策略 |
斷點續傳本質是「HTTP Range + 文件隨機寫入」。掌握 HttpWebRequest.AddRange、FileStream.Seek、流式讀寫三大核心,即可構建健壯下載器。現代專案建議遷移至 HttpClient,享受更優的連接池、HTTP/2、取消令牌等原生支持。