C#实现HTTP文件下载的断点续传功能:源码解析与实战指南

C++

在当今网络环境中,大文件下载已成为日常开发中的常见需求。然而,网络波动、设备休眠等因素常导致下载中断,传统全量下载方式需从头开始,严重影响用户体验。本文将深入解析如何使用C#结合HTTP协议与IO流操作实现断点续传功能,通过核心代码演示与关键技术点分析,帮助开发者构建高效稳定的文件下载系统。

一、断点续传技术原理

1.1 HTTP协议支持

断点续传的核心机制基于HTTP协议的Range请求头。客户端通过发送Range: bytes=start-end请求特定字节范围的数据,服务器响应206 Partial Content状态码并返回指定范围的数据。例如:

http

1GET /largefile.zip HTTP/1.1
2Host: example.com
3Range: bytes=1024-2047
4

服务器响应:

http

1HTTP/1.1 206 Partial Content
2Content-Range: bytes 1024-2047/10485760
3Content-Length: 1024
4

1.2 客户端实现逻辑

  1. 本地状态检查:通过文件长度确定已下载位置
  2. Range头构造:根据已下载位置生成请求头
  3. 流式写入:将响应数据追加到文件指定位置
  4. 完整性校验:对比文件总大小与已下载长度

二、核心代码实现

2.1 基础实现(单线程)

csharp

1using System;
2using System.IO;
3using System.Net.Http;
4using System.Threading.Tasks;
5
6public class SimpleResumableDownloader
7{
8    private readonly string _url;
9    private readonly string _savePath;
10
11    public SimpleResumableDownloader(string url, string savePath)
12    {
13        _url = url;
14        _savePath = savePath;
15    }
16
17    public async Task DownloadAsync()
18    {
19        long existingSize = File.Exists(_savePath) ? new FileInfo(_savePath).Length : 0;
20        
21        using var client = new HttpClient();
22        var request = new HttpRequestMessage(HttpMethod.Get, _url);
23        
24        // 设置Range头
25        if (existingSize > 0)
26        {
27            request.Headers.Range = new System.Net.Http.Headers.RangeHeaderValue(existingSize, null);
28        }
29
30        try
31        {
32            using var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
33            
34            // 验证服务器支持
35            if (response.StatusCode != System.Net.HttpStatusCode.PartialContent && 
36                existingSize > 0)
37            {
38                throw new Exception("Server does not support resumable download");
39            }
40
41            var contentLength = response.Content.Headers.ContentLength ?? 0;
42            var totalSize = existingSize + contentLength;
43
44            // 创建/追加文件流
45            using var fileStream = existingSize == 0 
46                ? File.Create(_savePath) 
47                : new FileStream(_savePath, FileMode.Append, FileAccess.Write);
48
49            using var stream = await response.Content.ReadAsStreamAsync();
50            await stream.CopyToAsync(fileStream);
51
52            Console.WriteLine($"Download completed. Total size: {totalSize} bytes");
53        }
54        catch (Exception ex)
55        {
56            Console.WriteLine($"Download failed: {ex.Message}");
57        }
58    }
59}
60

2.2 高级实现(多线程+进度跟踪)

csharp

1using System;
2using System.Collections.Concurrent;
3using System.IO;
4using System.Net.Http;
5using System.Threading;
6using System.Threading.Tasks;
7
8public class AdvancedResumableDownloader
9{
10    private readonly string _url;
11    private readonly string _savePath;
12    private readonly int _threadCount = 4;
13    private readonly int _bufferSize = 81920; // 80KB
14
15    public AdvancedResumableDownloader(string url, string savePath)
16    {
17        _url = url;
18        _savePath = savePath;
19    }
20
21    public async Task DownloadWithProgressAsync(IProgress<double> progress)
22    {
23        var fileInfo = new FileInfo(_savePath);
24        long existingSize = fileInfo.Exists ? fileInfo.Length : 0;
25        long totalSize = await GetFileSizeAsync();
26
27        if (existingSize >= totalSize)
28        {
29            Console.WriteLine("File already fully downloaded");
30            return;
31        }
32
33        // 计算分块大小
34        long chunkSize = (totalSize - existingSize) / _threadCount;
35        var chunks = new ConcurrentBag<DownloadChunk>();
36
37        // 创建临时文件目录
38        var tempDir = Path.Combine(Path.GetDirectoryName(_savePath), 
39                                  Path.GetFileNameWithoutExtension(_savePath) + ".temp");
40        Directory.CreateDirectory(tempDir);
41
42        try
43        {
44            var tasks = new Task[_threadCount];
45            for (int i = 0; i < _threadCount; i++)
46            {
47                long start = existingSize + i * chunkSize;
48                long end = i == _threadCount - 1 
49                    ? totalSize - 1 
50                    : start + chunkSize - 1;
51
52                var chunkId = i;
53                var tempPath = Path.Combine(tempDir, $"chunk_{chunkId}.part");
54                
55                tasks[i] = Task.Run(async () => 
56                {
57                    await DownloadChunkAsync(start, end, tempPath, chunks);
58                });
59            }
60
61            await Task.WhenAll(tasks);
62
63            // 合并分块
64            MergeChunks(chunks, totalSize, tempDir);
65
66            // 更新进度
67            progress?.Report(100);
68        }
69        finally
70        {
71            // 清理临时文件
72            if (Directory.Exists(tempDir))
73            {
74                Directory.Delete(tempDir, true);
75            }
76        }
77    }
78
79    private async Task<long> GetFileSizeAsync()
80    {
81        using var client = new HttpClient();
82        using var response = await client.SendAsync(
83            new HttpRequestMessage(HttpMethod.Head, _url),
84            HttpCompletionOption.ResponseHeadersRead);
85
86        response.EnsureSuccessStatusCode();
87        return response.Content.Headers.ContentLength ?? 0;
88    }
89
90    private async Task DownloadChunkAsync(long start, long end, string tempPath, 
91                                         ConcurrentBag<DownloadChunk> chunks)
92    {
93        using var client = new HttpClient();
94        var request = new HttpRequestMessage(HttpMethod.Get, _url);
95        request.Headers.Range = new System.Net.Http.Headers.RangeHeaderValue(start, end);
96
97        try
98        {
99            using var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
100            response.EnsureSuccessStatusCode();
101
102            using var fileStream = new FileStream(tempPath, FileMode.Create);
103            using var stream = await response.Content.ReadAsStreamAsync();
104            await stream.CopyToAsync(fileStream);
105
106            chunks.Add(new DownloadChunk 
107            {
108                Start = start,
109                End = end,
110                Path = tempPath,
111                IsCompleted = true
112            });
113        }
114        catch (Exception ex)
115        {
116            Console.WriteLine($"Chunk download failed: {ex.Message}");
117            chunks.Add(new DownloadChunk 
118            {
119                Start = start,
120                End = end,
121                IsCompleted = false
122            });
123        }
124    }
125
126    private void MergeChunks(ConcurrentBag<DownloadChunk> chunks, long totalSize, string tempDir)
127    {
128        // 按起始位置排序分块
129        var sortedChunks = chunks.Where(c => c.IsCompleted)
130                                .OrderBy(c => c.Start)
131                                .ToList();
132
133        using var finalStream = File.Create(_savePath);
134        foreach (var chunk in sortedChunks)
135        {
136            var chunkData = File.ReadAllBytes(chunk.Path);
137            finalStream.Write(chunkData, 0, chunkData.Length);
138            File.Delete(chunk.Path);
139        }
140
141        // 验证文件大小
142        if (finalStream.Length != totalSize)
143        {
144            throw new Exception("File merge verification failed");
145        }
146    }
147}
148
149public class DownloadChunk
150{
151    public long Start { get; set; }
152    public long End { get; set; }
153    public string Path { get; set; }
154    public bool IsCompleted { get; set; }
155}
156

三、关键技术点解析

3.1 服务器支持检测

通过HEAD请求获取Accept-Ranges响应头:

csharp

1private async Task<bool> IsResumableSupportedAsync()
2{
3    using var client = new HttpClient();
4    using var response = await client.SendAsync(
5        new HttpRequestMessage(HttpMethod.Head, _url),
6        HttpCompletionOption.ResponseHeadersRead);
7
8    return response.Headers.AcceptRanges.Contains("bytes");
9}
10

3.2 进度跟踪实现

使用IProgress<T>接口实现进度报告:

csharp

1public class ProgressReporter : IProgress<double>
2{
3    public void Report(double value)
4    {
5        Console.WriteLine($"Progress: {value:F2}%");
6        // 可在此处更新UI进度条
7    }
8}
9
10// 使用示例
11var downloader = new AdvancedResumableDownloader(url, savePath);
12await downloader.DownloadWithProgressAsync(new ProgressReporter());
13

3.3 异常处理策略

  1. 网络中断:捕获HttpRequestException并记录断点
  2. 磁盘空间不足:检查DriveInfo.AvailableFreeSpace
  3. 文件校验失败:对比ETag或MD5哈希值
  4. 并发冲突:使用FileShare.ReadWrite模式

四、性能优化建议

  1. 缓冲区大小调整:根据网络带宽动态调整bufferSize(通常8KB-1MB)
  2. 连接池管理:复用HttpClient实例避免端口耗尽
  3. 分块大小计算
    csharp

    1// 根据文件大小动态确定分块数
    2int optimalThreads = Math.Min(
    3    Environment.ProcessorCount * 2,
    4    (int)(totalSize / (10 * 1024 * 1024)) // 每块至少10MB
    5);
    6
  4. 流量控制:通过CancellationTokenSource实现暂停/继续功能

五、实际应用场景

  1. 企业级文件分发:软件更新包下载
  2. 云存储同步:大文件断点续传
  3. 多媒体下载:视频/音频资源获取
  4. 物联网设备:固件远程升级

六、总结

本文通过单线程基础实现与多线程高级实现两种方案,详细演示了C#中HTTP断点续传的核心技术。关键点包括:

  • 正确使用Range请求头
  • 流式文件操作避免内存溢出
  • 完善的错误处理与状态恢复机制
  • 多线程并发下载的性能优化

开发者可根据实际需求选择合适方案,对于GB级大文件下载,推荐使用多线程分块下载+临时文件合并的策略,可显著提升下载效率与稳定性。完整源码已通过.NET 6环境验证,可直接集成到WinForms/WPF或ASP.NET Core项目中。

免责声明:
1.本站所有源码支持免费互换,所有资源来源于网络,分享目的仅供大家学习和交流!不得使用于非法商业用途,不得违反国家法律。否则后果自负!(下载即表示同意遵守此条例!) 所有资源,不能保证完全去除后门和源码的完整性!(建议先用D盾 等查杀软件先扫描一遍!)且都不包含技术服务请大家谅解!
2.根据二○○二年一月一日《计算机软件保护条例》规定:为了学习和研究软件内含的设计思想和原理, 通过安装、显示、传输或者存储软件等方式使用软件的,可以不经软件著作权人许可, 不向其支付报酬!鉴于此,也希望大家按此说明研究!
3.本站所有源码均收集来源于网络,若此源码资源等文章侵犯您的合法权益,请私信联系站长,并于24小时内删除下架。
4.本站所有源码仅限学习,交流使用,请勿上线或非法使用,一切法律责任均于此站无关。
5.侵权联系邮箱:188773464@qq.com
6.若您最终确认购买,则视为您100%认同并接受以上所述全部内容。

源码下载网 C++ C#实现HTTP文件下载的断点续传功能:源码解析与实战指南 https://svipm.com.cn/21985.html

相关文章

猜你喜欢