一、系统概述
断点续传是大文件下载的核心需求,尤其在网络不稳定、下载中断后需恢复进度的场景(如GB级文件、弱网环境)。本工具基于 C# 语言和 HTTP协议Range请求,实现支持断点续传、进度跟踪、错误恢复的文件下载功能,核心特点:
- 低内存占用:流式读取/写入,避免全文件加载至内存;
- 高可靠性:支持网络中断后从断点继续,自动校验文件完整性;
- 易用性:提供同步/异步接口,可集成至GUI(WinForms/WPF)或命令行工具;
- 多协议支持:兼容HTTP/HTTPS,适配主流服务器(Apache/Nginx/IIS)。
二、核心设计思路
2.1 断点续传原理
- 获取文件总大小 :首次请求时,通过
HEAD方法或GET响应头Content-Length获取文件总大小; - 记录已下载位置 :本地创建临时文件(如
filename.ext.part),记录已下载字节数(通过文件长度判断); - 分段请求数据 :后续请求在
Range头中指定bytes=已下载长度-,服务器返回206 Partial Content及部分数据; - 追加写入文件:将新数据追加至临时文件,更新进度,直至下载完成(重命名为目标文件)。
2.2 关键技术点
- HTTP Range请求 :通过
Range: bytes=start-end头实现分段下载; - 文件流随机访问 :用
FileStream的Seek方法定位写入位置; - 进度跟踪 :实时计算
已下载大小/总大小,支持进度条显示; - 错误恢复:捕获网络异常,保存当前进度,下次启动时自动续传。
三、实现步骤与代码
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 文件流随机访问
- 用
FileStream的Seek(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工具或后台服务,适用于大文件下载、离线资源同步等场景。