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

C# 實作 HTTP 多線程檔案下載與斷點續傳

     編輯:關於C語言

前言

多線程下載工具(如網路螞蟻、IDM)之所以快,核心在於多線程分段下載與斷點續傳。本文將以 C# 為例,示範如何使用 HttpWebRequest/HttpWebResponse 實作支援多線程分段下載的檔案傳輸程式。

HTTP 協議與 Range 標頭

HTTP/1.1 定義 Range 標頭,允許客戶端請求資源的特定位元組範圍。伺服器回應 206 Partial Content 並傳回指定區段資料。此為多線程下載的基石。

// 請求第 100~1000 位元組
request.AddRange(100, 1000);

核心類別設計

1. 下載執行緒類別 HttpFile

public class HttpFile
{
    private readonly Form1 _form;
    private readonly int _threadIndex;

    public HttpFile(Form1 form, int threadIndex)
    {
        _form = form;
        _threadIndex = threadIndex;
    }

    public void Receive()
    {
        string url = _form.Url;
        string tempFile = _form.TempFiles[_threadIndex];
        long start = _form.StartPositions[_threadIndex];
        long length = _form.Lengths[_threadIndex];

        _form.Log($"Thread {_threadIndex} start: {start} - {start + length}");

        using (var fs = new FileStream(tempFile, FileMode.Create, FileAccess.Write, FileShare.None))
        using (var request = (HttpWebRequest)WebRequest.Create(url))
        {
            request.AddRange(start, start + length - 1);
            request.Method = "GET";
            request.Timeout = 30000;

            using (var response = (HttpWebResponse)request.GetResponse())
            using (var stream = response.GetResponseStream())
            {
                byte[] buffer = new byte[8192];
                int read;
                while ((read = stream.Read(buffer, 0, buffer.Length)) > 0)
                {
                    fs.Write(buffer, 0, read);
                }
            }
        }

        _form.MarkThreadCompleted(_threadIndex);
        _form.Log($"Thread {_threadIndex} completed.");
    }
}

2. 主表單協調邏輯

public partial class Form1 : Form
{
    public string Url { get; set; }
    public string[] TempFiles { get; set; }
    public long[] StartPositions { get; set; }
    public long[] Lengths { get; set; }
    public bool[] ThreadCompleted { get; set; }
    public int ThreadCount { get; set; }

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

        // 1. 取得檔案總大小
        long totalSize;
        using (var request = (HttpWebRequest)WebRequest.Create(Url))
        using (var response = (HttpWebResponse)request.GetResponse())
        {
            totalSize = response.ContentLength;
        }

        // 2. 計算分段
        long partSize = totalSize / ThreadCount;
        TempFiles = new string[ThreadCount];
        StartPositions = new long[ThreadCount];
        Lengths = new long[ThreadCount];
        ThreadCompleted = new bool[ThreadCount];

        for (int i = 0; i < ThreadCount; i++)
        {
            TempFiles[i] = $"{i}.part";
            StartPositions[i] = i * partSize;
            Lengths[i] = (i == ThreadCount - 1) ? totalSize - StartPositions[i] : partSize;
        }

        // 3. 啟動下載執行緒
        var threads = new Thread[ThreadCount];
        for (int i = 0; i < ThreadCount; i++)
        {
            var httpFile = new HttpFile(this, i);
            threads[i] = new Thread(httpFile.Receive);
            threads[i].Start();
        }

        // 4. 啟動合併執行緒
        new Thread(MergeFiles).Start();
    }

    private void MergeFiles()
    {
        // 等待所有下載執行緒完成
        while (ThreadCompleted.Any(c => !c))
            Thread.Sleep(100);

        using (var fs = new FileStream(txtSavePath.Text, FileMode.Create, FileAccess.Write))
        {
            foreach (string part in TempFiles)
            {
                using (var partStream = new FileStream(part, FileMode.Open, FileAccess.Read))
                {
                    partStream.CopyTo(fs);
                }
                File.Delete(part); // 清理暫存檔
            }
        }

        Invoke(new Action(() =>
        {
            txtEndTime.Text = DateTime.Now.ToString();
            MessageBox.Show("下載完成!");
        }));
    }

    public void MarkThreadCompleted(int index) => ThreadCompleted[index] = true;
    public void Log(string msg) => Invoke(new Action(() => listBoxLog.Items.Add(msg)));
}

關鍵技術點

  • Range 請求:request.AddRange(start, end) 指定下載區間。
  • 執行緒同步:使用 bool[] 旗標陣列配合輪詢等待,或改用 CountdownEvent、Task.WhenAll 更優雅。
  • 暫存檔合併:依序將分段暫存檔複製至最終檔案。
  • 錯誤處理:實務上需加入重試機制、網路中斷續傳邏輯(記錄已下載位元組)。

現代化改寫建議

.NET Core / .NET 5+ 建議使用 HttpClient 搭配 System.IO.Pipelines 或 Task 非同步模式,程式碼更簡潔且效能更佳:

public async Task DownloadRangeAsync(HttpClient client, string url, long start, long length, Stream destination)
{
    using var request = new HttpRequestMessage(HttpMethod.Get, url);
    request.Headers.Range = new RangeHeaderValue(start, start + length - 1);
    using var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
    using var stream = await response.Content.ReadAsStreamAsync();
    await stream.CopyToAsync(destination);
}

結語

本文示範了傳統 HttpWebRequest 實作多線程下載的完整流程,涵蓋分段計算、執行緒協調、暫存檔合併等關鍵步驟。雖然現代開發推薦使用 HttpClient 非同步模式,但理解底層 HTTP Range 機制與執行緒同步原理,對於維護舊專案或設計高效能下載引擎仍具參考價值。

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