首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> C# >> C# 入門

C# 實作 HTTP 多執行緒檔案下載:斷點續傳與分段並行核心技術

     編輯:C# 入門

原理基礎

HTTP/1.1 支援 Range 標頭(RFC 7233),允許客戶端請求檔案特定位元組區間:Range: bytes=0-1023。多執行緒下載即將檔案分割為 N 個區間,並行請求,最後合併。核心類別:HttpWebRequest/HttpWebResponse(或現代 HttpClient)。

關鍵步驟與代碼架構

1. 取得檔案總長度(HEAD 請求)

long GetFileSize(string url) {
    var req = (HttpWebRequest)WebRequest.Create(url);
    req.Method = "HEAD";
    using (var resp = (HttpWebResponse)req.GetResponse())
        return resp.ContentLength;
}

2. 計算分段區間

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;
}

3. 下載執行緒工作函數

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)
}

4. 協調器:啟動、等待、合併


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);
        }
    }
}

斷點續傳擴充

  1. 記錄進度:每段下載前讀取 .part 檔案長度,續傳時 req.AddRange(start + alreadyDownloaded, end)。
  2. 狀態持久化:JSON 序列化 {url, totalSize, ranges[], downloadedBytes[]} 到 .meta 檔,程式重啟後讀取恢復。
  3. 校驗完整性:下載完成後計算 MD5/SHA256 與伺服器提供的雜湊比對(Content-MD5 標頭或獨立 API)。

現代化改寫:HttpClient + System.Threading.Tasks


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 自動管理。
  • 緩衝區大小:8KB~64KB 平衡記憶體與系統呼叫開銷。
  • 磁碟 I/O:SSD 可適度增加並行數;HDD 建議 4-8 避免隨機寫入抖動。
  • 伺服器支援:確認回應 Accept-Ranges: bytes;不支援則退回單執行緒。
  • 例外重試:指數退避重試網路錯誤/5xx,記錄失敗分段單獨重下。

結語

HTTP 多執行緒下載核心在於 Range 標頭分段請求與檔案合併。從 HttpWebRequest 到 HttpClient,非同步模式大幅簡化協調邏輯。結合斷點續傳、校驗、進度回報,即可構建生產級下載引擎。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved