HTTP/1.1 支援 Range 標頭(RFC 7233),允許客戶端請求檔案特定位元組區間:Range: bytes=0-1023。多執行緒下載即將檔案分割為 N 個區間,並行請求,最後合併。核心類別:HttpWebRequest/HttpWebResponse(或現代 HttpClient)。
long GetFileSize(string url) {
var req = (HttpWebRequest)WebRequest.Create(url);
req.Method = "HEAD";
using (var resp = (HttpWebResponse)req.GetResponse())
return resp.ContentLength;
}
List<(long Start, long End)> CalcRanges(long total, int parts) {
var ranges = new List<(long, long)>();
long partSize = total / parts;
for (int i = 0; i < parts; i++) {
long start = i * partSize;
long end = (i == parts - 1) ? total - 1 : start + partSize - 1;
ranges.Add((start, end));
}
return ranges;
}
void DownloadSegment(string url, long start, long end, string tempFile, int segmentIndex) {
var req = (HttpWebRequest)WebRequest.Create(url);
req.AddRange(start, end); // 關鍵:設定 Range 標頭
req.Timeout = 30000;
req.ReadWriteTimeout = 60000;
using (var resp = (HttpWebResponse)req.GetResponse())
using (var stream = resp.GetResponseStream())
using (var fs = new FileStream(tempFile, FileMode.Create, FileAccess.Write, FileShare.None)) {
byte[] buffer = new byte[8192];
int read;
while ((read = stream.Read(buffer, 0, buffer.Length)) > 0) {
fs.Write(buffer, 0, read);
// 可在這裡回報進度:Interlocked.Add(ref _totalBytes, read)
}
}
// 完成標記:Interlocked.Increment(ref _completedCount)
}
public void Download(string url, string destFile, int threadCount = 8) {
long totalSize = GetFileSize(url);
if (totalSize <= 0) throw new Exception("Invalid file size");
var ranges = CalcRanges(totalSize, threadCount);
var tempFiles = new string[threadCount];
int completed = 0;
// 啟動執行緒池工作項
for (int i = 0; i < threadCount; i++) {
int idx = i;
string temp = Path.GetTempFileName();
tempFiles[idx] = temp;
ThreadPool.QueueUserWorkItem(_ => {
try { DownloadSegment(url, ranges[idx].Start, ranges[idx].End, temp, idx); }
catch (Exception ex) { /* 記錄錯誤、標記失敗、可重試 */ }
finally { Interlocked.Increment(ref completed); }
});
}
// 等待完成(簡易輪詢;生產環境用 CountdownEvent/Task.WhenAll)
while (completed < threadCount) Thread.Sleep(200);
// 合併檔案
using (var fs = new FileStream(destFile, FileMode.Create, FileAccess.Write)) {
foreach (var tf in tempFiles) {
using (var ts = File.OpenRead(tf)) ts.CopyTo(fs);
File.Delete(tf);
}
}
}
.part 檔案長度,續傳時 req.AddRange(start + alreadyDownloaded, end)。{url, totalSize, ranges[], downloadedBytes[]} 到 .meta 檔,程式重啟後讀取恢復。Content-MD5 標頭或獨立 API)。
public async Task DownloadAsync(string url, string destFile, int concurrency = 8, IProgress progress = null) {
using var client = new HttpClient();
var head = await client.SendAsync(new HttpRequestMessage(HttpMethod.Head, url));
long total = head.Content.Headers.ContentLength ?? throw new Exception("Missing Content-Length");
long part = total / concurrency;
var tasks = new List();
for (int i = 0; i < concurrency; i++) {
long start = i * part;
long end = (i == concurrency - 1) ? total - 1 : start + part - 1;
string temp = Path.GetTempFileName();
tasks.Add(DownloadSegmentAsync(client, url, start, end, temp, progress));
}
await Task.WhenAll(tasks);
using (var fs = new FileStream(destFile, FileMode.Create))
foreach (var t in tasks.Select(t => t.Result)) // 假設回傳 temp path
using (var ts = File.OpenRead(t)) { ts.CopyTo(fs); File.Delete(t); }
}
private async Task DownloadSegmentAsync(HttpClient client, string url, long start, long end, string tempFile, IProgress progress) {
var req = new HttpRequestMessage(HttpMethod.Get, url);
req.Headers.Range = new System.Net.Http.Headers.RangeHeaderValue(start, end);
using var resp = await client.SendAsync(req, HttpCompletionOption.ResponseHeadersRead);
using var stream = await resp.Content.ReadAsStreamAsync();
using var fs = new FileStream(tempFile, FileMode.Create);
byte[] buffer = new byte[8192];
int read; long downloaded = 0;
while ((read = await stream.ReadAsync(buffer)) > 0) {
await fs.WriteAsync(buffer.AsMemory(0, read));
downloaded += read;
progress?.Report((double)downloaded / (end - start + 1));
}
return tempFile;
}
ServicePointManager.DefaultConnectionLimit = 100(HttpWebRequest)或 HttpClient 自動管理。Accept-Ranges: bytes;不支援則退回單執行緒。HTTP 多執行緒下載核心在於 Range 標頭分段請求與檔案合併。從 HttpWebRequest 到 HttpClient,非同步模式大幅簡化協調邏輯。結合斷點續傳、校驗、進度回報,即可構建生產級下載引擎。