首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C# 實作 HTTP 多線程下載器:從原理到完整代碼,突破單線程速度瓶頸

     編輯:關於C語言

為什麼需要多線程下載?

單線程下載受限於伺服器單連接頻寬限制、網路延遲、串行傳輸效率低。多線程下載透過分段並行請求(HTTP Range 標頭),將檔案切分為多個區塊同時下載,再合併還原,可顯著提升吞吐量,這正是「網路螞蟻」「迅雷」等下載工具的核心原理。

HTTP 協議關鍵:Range 請求與斷點續傳基礎

HTTP/1.1 定義 Range 標頭,允許客戶端請求資源的特定位元組範圍:

Range: bytes=0-1023        // 前 1024 位元組
Range: bytes=1024-2047     // 第二段
Range: bytes=1024-         // 從 1024 到結尾

伺服器支援時回應 206 Partial Content,並附上 Content-Range 與 Content-Length。若不支援則回 200 OK 全檔傳輸(此時多線程無效)。

.NET 核心類別:HttpWebRequest / HttpWebResponse

  • HttpWebRequest:建立請求、設定標頭(AddRange)、送出請求。
  • HttpWebResponse:取得回應串流(GetResponseStream)、讀取 ContentLength。
  • 兩者皆在 System.Net 命名空間,屬抽象類 WebRequest/WebResponse 的 HTTP 實作。

完整實作步驟與代碼解析

1. UI 介面佈局

包含:URL 輸入框、儲存路徑、線程數輸入、開始按鈕、狀態列表框。

2. 核心變數定義

public bool[] threadDone;       // 各線程完成標記
public string[] tempFiles;      // 暫存檔路徑
public int[] startPos;          // 起始位元組
public int[] blockSize;         // 區塊大小
public string targetUrl;        // 下載 URL
public int threadCount;         // 線程數

3. 下載線程類 HttpFile

public class HttpFile {
    public Form1 Form { get; set; }
    public int ThreadIndex { get; set; }

    public void Receive() {
        string tempFile = Form.tempFiles[ThreadIndex];
        string url = Form.targetUrl;
        int start = Form.startPos[ThreadIndex];
        int size = Form.blockSize[ThreadIndex];

        Form.Log($"線程 {ThreadIndex} 開始下載:{start} - {start + size}");

        using (FileStream fs = new FileStream(tempFile, FileMode.Create))
        using (var request = (HttpWebRequest)WebRequest.Create(url)) {
            request.AddRange(start, start + size - 1); // 關鍵:設定 Range
            using (var response = (HttpWebResponse)request.GetResponse())
            using (Stream ns = response.GetResponseStream()) {
                byte[] buffer = new byte[4096]; // 4KB 緩衝區
                int read;
                while ((read = ns.Read(buffer, 0, buffer.Length)) > 0) {
                    fs.Write(buffer, 0, read);
                }
            }
        }
        Form.Log($"線程 {ThreadIndex} 下載完成");
        Form.threadDone[ThreadIndex] = true;
    }
}

4. 主線程:計算分段、啟動線程

private void btnStart_Click(object sender, EventArgs e) {
    targetUrl = txtUrl.Text.Trim();
    threadCount = int.Parse(txtThreadCount.Text);

    // 1. 先取得檔案總大小
    long totalSize;
    using (var req = (HttpWebRequest)WebRequest.Create(targetUrl))
    using (var resp = (HttpWebResponse)req.GetResponse()) {
        totalSize = resp.ContentLength;
        req.Abort(); // 僅取標頭,釋放連線
    }

    // 2. 平均分段(最後一段含餘數)
    int avg = (int)(totalSize / threadCount);
    int last = avg + (int)(totalSize % threadCount);

    threadDone = new bool[threadCount];
    tempFiles = new string[threadCount];
    startPos = new int[threadCount];
    blockSize = new int[threadCount];

    for (int i = 0; i < threadCount; i++) {
        threadDone[i] = false;
        tempFiles[i] = $"{i}.part";
        startPos[i] = i * avg;
        blockSize[i] = (i == threadCount - 1) ? last : avg;
    }

    // 3. 啟動下載線程
    for (int i = 0; i < threadCount; i++) {
        var httpFile = new HttpFile { Form = this, ThreadIndex = i };
        new Thread(httpFile.Receive).Start();
    }

    // 4. 啟動合併監控線程
    new Thread(MergeFiles).Start();
}

5. 合併線程:等待全部完成 → 依序寫入最終檔案

private void MergeFiles() {
    // 等待所有線程完成
    while (true) {
        bool allDone = true;
        for (int i = 0; i < threadCount; i++) {
            if (!threadDone[i]) { allDone = false; Thread.Sleep(100); break; }
        }
        if (allDone) break;
    }

    string savePath = txtSavePath.Text.Trim();
    using (FileStream fs = new FileStream(savePath, FileMode.Create)) {
        byte[] buffer = new byte[4096];
        for (int i = 0; i < threadCount; i++) {
            using (FileStream part = new FileStream(tempFiles[i], FileMode.Open)) {
                int read;
                while ((read = part.Read(buffer, 0, buffer.Length)) > 0) {
                    fs.Write(buffer, 0, read);
                }
            }
            File.Delete(tempFiles[i]); // 清理暫存檔
        }
    }
    MessageBox.Show("下載合併完成!");
}

關鍵優化與避坑指南

  1. 線程數不是越多越好:建議 4-8 線程,過多反增連線開銷、觸發伺服器限流。
  2. 緩衝區大小:4KB-64KB 平衡記憶體與系統呼叫開銷。
  3. 例外處理與重試:網路波動、伺服器斷線需捕獲 WebException 並實作指數退避重試。
  4. 進度顯示:可在 Receive 迴圈中回報已下載位元組,UI 執行緒透過 Invoke 更新進度條。
  5. 斷點續傳擴展:記錄各線程已下載位移,下次啟動時 AddRange(start + downloaded, end) 接續。
  6. 現代替代方案:.NET Core / 5+ 建議使用 HttpClient + Stream.CopyToAsync,支援非同步、連線池、更簡潔。

總結

本文從 HTTP Range 原理出發,完整實作了 C# 多線程下載器:取得長度 → 計算分段 → 並行 Range 請求 → 暫存檔 → 合併清理。掌握此核心流程,即可自行構建高效下載工具,並為斷點續傳、限速、多來源下載等進階功能奠定基礎。

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