C# 断点续传下载文件工具设计与实现

一、系统概述

断点续传是大文件下载的核心需求,尤其在网络不稳定、下载中断后需恢复进度的场景(如GB级文件、弱网环境)。本工具基于 C# 语言和 HTTP协议Range请求,实现支持断点续传、进度跟踪、错误恢复的文件下载功能,核心特点:

  • 低内存占用:流式读取/写入,避免全文件加载至内存;
  • 高可靠性:支持网络中断后从断点继续,自动校验文件完整性;
  • 易用性:提供同步/异步接口,可集成至GUI(WinForms/WPF)或命令行工具;
  • 多协议支持:兼容HTTP/HTTPS,适配主流服务器(Apache/Nginx/IIS)。

二、核心设计思路

2.1 断点续传原理

  1. 获取文件总大小 :首次请求时,通过HEAD方法或GET响应头Content-Length获取文件总大小;
  2. 记录已下载位置 :本地创建临时文件(如filename.ext.part),记录已下载字节数(通过文件长度判断);
  3. 分段请求数据 :后续请求在Range头中指定bytes=已下载长度-,服务器返回206 Partial Content及部分数据;
  4. 追加写入文件:将新数据追加至临时文件,更新进度,直至下载完成(重命名为目标文件)。

2.2 关键技术点

  • HTTP Range请求 :通过Range: bytes=start-end头实现分段下载;
  • 文件流随机访问 :用FileStreamSeek方法定位写入位置;
  • 进度跟踪 :实时计算已下载大小/总大小,支持进度条显示;
  • 错误恢复:捕获网络异常,保存当前进度,下次启动时自动续传。

三、实现步骤与代码

3.1 开发环境

  • 语言:C# 9.0+
  • 框架:.NET 6.0(跨平台支持)
  • 核心类库System.Net.Http(HTTP请求)、System.IO(文件流)、System.Threading.Tasks(异步编程)

3.2 核心类设计

3.2.1 下载配置与状态模型
csharp 复制代码
/// <summary>
/// 下载配置
/// </summary>
public class DownloadConfig
{
    public string Url { get; set; }          // 下载URL
    public string SavePath { get; set; }     // 保存路径(含文件名)
    public int BufferSize { get; set; } = 4 * 1024;  // 缓冲区大小(默认4KB)
    public bool Overwrite { get; set; } = false;     // 是否覆盖已存在文件
}

/// <summary>
/// 下载状态
/// </summary>
public class DownloadStatus
{
    public long TotalSize { get; set; }      // 文件总大小(字节)
    public long DownloadedSize { get; set; } // 已下载大小(字节)
    public double Progress => TotalSize > 0 ? (double)DownloadedSize / TotalSize * 100 : 0; // 进度百分比
    public string Status { get; set; } = "等待中"; // 状态(等待中/下载中/暂停/完成/错误)
    public string ErrorMessage { get; set; } // 错误信息
}
3.2.2 断点续传核心类
csharp 复制代码
using System.Net.Http;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

public class ResumableDownloader
{
    private readonly DownloadConfig _config;
    private readonly HttpClient _httpClient;
    private CancellationTokenSource _cts;  // 取消令牌(用于暂停/停止)

    public event Action<DownloadStatus> OnProgressChanged;  // 进度更新事件
    public DownloadStatus Status { get; private set; } = new();

    public ResumableDownloader(DownloadConfig config)
    {
        _config = config;
        _httpClient = new HttpClient(new HttpClientHandler { AllowAutoRedirect = true });
        _httpClient.DefaultRequestHeaders.Add("User-Agent", "ResumableDownloader/1.0");
    }
}

3.3 核心功能实现

3.3.1 获取文件总大小

通过HEAD请求或GET请求头Content-Length获取文件总大小,判断服务器是否支持断点续传(响应头Accept-Ranges: bytes)。

csharp 复制代码
/// <summary>
/// 获取文件总大小和支持的断点续传状态
/// </summary>
private async Task<(long totalSize, bool supportsResume)> GetFileInfoAsync()
{
    using var request = new HttpRequestMessage(HttpMethod.Head, _config.Url);
    using var response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
    
    if (!response.IsSuccessStatusCode)
        throw new Exception($"获取文件信息失败:{response.StatusCode}");
    
    long totalSize = response.Content.Headers.ContentLength ?? 0;
    bool supportsResume = response.Headers.AcceptRanges.Contains("bytes");
    return (totalSize, supportsResume);
}
3.3.2 断点续传下载核心逻辑
csharp 复制代码
/// <summary>
/// 开始/继续下载
/// </summary>
public async Task StartDownloadAsync()
{
    _cts = new CancellationTokenSource();
    Status.Status = "下载中";
    OnProgressChanged?.Invoke(Status);

    try
    {
        // 1. 获取文件总大小和支持的断点续传状态
        var (totalSize, supportsResume) = await GetFileInfoAsync();
        Status.TotalSize = totalSize;

        // 2. 检查本地临时文件(已下载部分)
        string tempFilePath = _config.SavePath + ".part";
        long downloadedSize = 0;
        if (File.Exists(tempFilePath) && !_config.Overwrite)
        {
            var fileInfo = new FileInfo(tempFilePath);
            downloadedSize = fileInfo.Length;
            if (downloadedSize >= totalSize)
            {
                // 已完成下载,直接重命名
                File.Move(tempFilePath, _config.SavePath, overwrite: true);
                Status.Status = "完成";
                Status.DownloadedSize = totalSize;
                OnProgressChanged?.Invoke(Status);
                return;
            }
        }

        // 3. 创建文件流(追加模式)
        using var fileStream = new FileStream(
            tempFilePath, 
            FileMode.OpenOrCreate, 
            FileAccess.Write, 
            FileShare.None
        );
        fileStream.Seek(downloadedSize, SeekOrigin.Begin);  // 定位到已下载末尾

        // 4. 构造Range请求头(仅当支持续传且有已下载数据时)
        if (supportsResume && downloadedSize > 0)
        {
            _httpClient.DefaultRequestHeaders.Range = new System.Net.Http.Headers.RangeHeaderValue(downloadedSize, null);
        }

        // 5. 发送GET请求,下载剩余数据
        using var response = await _httpClient.GetAsync(_config.Url, HttpCompletionOption.ResponseHeadersRead, _cts.Token);
        response.EnsureSuccessStatusCode();

        // 6. 读取响应流并写入文件
        using var contentStream = await response.Content.ReadAsStreamAsync(_cts.Token);
        byte[] buffer = new byte[_config.BufferSize];
        int bytesRead;
        Status.DownloadedSize = downloadedSize;

        while ((bytesRead = await contentStream.ReadAsync(buffer, 0, buffer.Length, _cts.Token)) > 0)
        {
            await fileStream.WriteAsync(buffer, 0, bytesRead, _cts.Token);
            Status.DownloadedSize += bytesRead;
            OnProgressChanged?.Invoke(Status);  // 更新进度
        }

        // 7. 下载完成,重命名临时文件为目标文件
        fileStream.Close();
        File.Move(tempFilePath, _config.SavePath, overwrite: true);
        Status.Status = "完成";
        Status.DownloadedSize = totalSize;
        OnProgressChanged?.Invoke(Status);
    }
    catch (OperationCanceledException)
    {
        Status.Status = "暂停";
        OnProgressChanged?.Invoke(Status);
    }
    catch (Exception ex)
    {
        Status.Status = "错误";
        Status.ErrorMessage = ex.Message;
        OnProgressChanged?.Invoke(Status);
    }
}
3.3.3 暂停与取消下载
csharp 复制代码
/// <summary>
/// 暂停下载(可恢复)
/// </summary>
public void PauseDownload()
{
    _cts?.Cancel();
}

/// <summary>
/// 停止下载并删除临时文件
/// </summary>
public void StopDownload()
{
    _cts?.Cancel();
    string tempFilePath = _config.SavePath + ".part";
    if (File.Exists(tempFilePath))
        File.Delete(tempFilePath);
    Status.Status = "停止";
}

3.4 GUI界面集成示例(WinForms)

csharp 复制代码
// 界面控件:ProgressBar(progressBar1)、Label(labelStatus)、Button(btnStart/Pause/Stop)
private readonly ResumableDownloader _downloader;

private async void btnStart_Click(object sender, EventArgs e)
{
    var config = new DownloadConfig
    {
        Url = "https://example.com/largefile.zip",
        SavePath = @"D:\Downloads\largefile.zip",
        BufferSize = 8 * 1024  // 8KB缓冲区
    };
    _downloader = new ResumableDownloader(config);
    _downloader.OnProgressChanged += status =>
    {
        Invoke(() =>  // 跨线程更新UI
        {
            progressBar1.Value = (int)status.Progress;
            labelStatus.Text = $"{status.Status} ({status.DownloadedSize}/{status.TotalSize} bytes)";
        });
    };
    await _downloader.StartDownloadAsync();
}

private void btnPause_Click(object sender, EventArgs e)
{
    _downloader?.PauseDownload();
}

private void btnStop_Click(object sender, EventArgs e)
{
    _downloader?.StopDownload();
}

参考代码 C#-断点续传下载文件 www.youwenfan.com/contentcst/123127.html

四、关键技术点详解

4.1 HTTP Range请求细节

  • 请求头格式Range: bytes=start-end(如Range: bytes=1024-表示从1024字节开始到结束);
  • 服务器响应
    • 支持续传:返回206 Partial Content,响应头含Content-Range: bytes start-end/total
    • 不支持续传:返回200 OK,此时需从头下载(可选择覆盖或追加)。

4.2 文件流随机访问

  • FileStreamSeek(long offset, SeekOrigin origin)定位写入位置(origin=SeekOrigin.Begin);
  • 临时文件(.part)保存已下载数据,下载完成后重命名为目标文件(避免未完成文件被误读)。

4.3 进度跟踪与异常处理

  • 进度计算Progress = (DownloadedSize / TotalSize) * 100,实时通过事件通知UI;
  • 异常捕获 :捕获OperationCanceledException(暂停)、HttpRequestException(网络错误)、IOException(文件读写错误),保存当前进度以便恢复。

五、扩展功能

5.1 多线程分段下载

将文件分成多个段(如4段),每段由一个线程下载,最后合并。优势 :提升下载速度;注意 :需服务器支持多Range请求(Accept-Ranges: bytes)。

5.2 下载速度限制

通过SemaphoreSlim控制每秒最大读取字节数,避免占用过多带宽:

csharp 复制代码
private readonly SemaphoreSlim _rateLimiter = new SemaphoreSlim(1024 * 1024); // 限制1MB/s

// 读取数据时控制速率
await _rateLimiter.WaitAsync(_cts.Token);
int bytesRead = await contentStream.ReadAsync(buffer, 0, buffer.Length, _cts.Token);
_rateLimiter.Release(bytesRead);

5.3 文件校验(MD5/SHA256)

下载完成后计算文件哈希值,与服务器提供的校验值比对,确保完整性:

csharp 复制代码
using (var sha256 = SHA256.Create())
using (var stream = File.OpenRead(_config.SavePath))
{
    byte[] hash = sha256.ComputeHash(stream);
    string computedHash = BitConverter.ToString(hash).Replace("-", "").ToLower();
    // 比对computedHash与服务器返回的hash
}

六、总结

本工具通过 HTTP Range请求 和 流式文件操作 实现了可靠的断点续传下载,核心优势在于低内存占用(流式处理)、高容错性(中断后可恢复)和易集成性(异步接口+事件驱动)。代码结构模块化,可直接嵌入GUI工具或后台服务,适用于大文件下载、离线资源同步等场景。

相关推荐
想唱rap2 小时前
线程之条件变量和生产消费模型
java·服务器·开发语言·数据库·mysql·ubuntu
Boop_wu2 小时前
[Java 算法] 栈
java·开发语言·算法
来自远方的老作者2 小时前
第7章 运算符-7.5 比较运算符
开发语言·数据结构·python·算法·代码规范·比较运算符
南境十里·墨染春水2 小时前
C++笔记 Lambda表达式
开发语言·c++·笔记
We་ct2 小时前
LeetCode 201. 数字范围按位与:位运算高效解题指南
开发语言·前端·javascript·算法·leetcode·typescript
程序员榴莲2 小时前
Java(十二)抽象类
java·开发语言
木子欢儿2 小时前
在 Fedora 上配置 Go 语言(Golang)开发环境
开发语言·后端·golang
超级大只老咪2 小时前
线性递推通用模板
java·开发语言·算法
Dream of maid2 小时前
Python基础 6 (面向对象)
开发语言·python