在当今的软件开发中,通过网络自动下载文件是一个常见且重要的功能。无论是自动更新客户端程序、同步云端数据,还是批量获取网络资源,文件自动下载功能都扮演着关键角色。本文将深入探讨如何使用 C# 实现基于 HTTP 协议的网络通信和文件下载,重点分析 HTTP 请求与 IO 流的结合应用。
一、项目环境与准备工作
首先,我们创建一个 C# 控制台应用程序。这里使用 .NET 6 及以上版本,因为它们提供了更简洁的异步编程模型和更好的性能。
using System;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
namespace FileDownloader
{
class Program
{
static async Task Main(string[] args)
{
// 下载示例
string url = "https://example.com/samplefile.zip";
string savePath = "downloaded_file.zip";
try
{
await DownloadFileAsync(url, savePath);
Console.WriteLine("文件下载完成!");
}
catch (Exception ex)
{
Console.WriteLine($"下载失败: {ex.Message}");
}
}
}
}
二、核心下载方法实现
下面是实现文件下载的核心方法,我们使用
HttpClient类进行网络通信,结合 FileStream处理文件写入。public static class FileDownloader
{
// 使用静态 HttpClient 以提高性能(.NET Core 2.1+ 推荐方式)
private static readonly HttpClient _httpClient = new HttpClient();
/// <summary>
/// 异步下载文件
/// </summary>
/// <param name="url">文件URL地址</param>
/// <param name="savePath">本地保存路径</param>
/// <param name="bufferSize">缓冲区大小(字节)</param>
public static async Task DownloadFileAsync(string url, string savePath, int bufferSize = 81920)
{
if (string.IsNullOrEmpty(url))
throw new ArgumentException("URL不能为空", nameof(url));
if (string.IsNullOrEmpty(savePath))
throw new ArgumentException("保存路径不能为空", nameof(savePath));
// 创建保存目录(如果不存在)
string directory = Path.GetDirectoryName(savePath);
if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
// 发送HTTP GET请求
using HttpResponseMessage response = await _httpClient.GetAsync(
url, HttpCompletionOption.ResponseHeadersRead);
// 检查响应状态
response.EnsureSuccessStatusCode();
// 获取内容长度(如果可用)
long? contentLength = response.Content.Headers.ContentLength;
// 创建文件流
using Stream contentStream = await response.Content.ReadAsStreamAsync();
using FileStream fileStream = new FileStream(
savePath,
FileMode.Create,
FileAccess.Write,
FileShare.None,
bufferSize,
useAsync: true);
// 缓冲区
byte[] buffer = new byte[bufferSize];
int bytesRead;
long totalBytesRead = 0;
// 读取并写入文件
while ((bytesRead = await contentStream.ReadAsync(buffer, 0, buffer.Length)) > 0)
{
await fileStream.WriteAsync(buffer, 0, bytesRead);
totalBytesRead += bytesRead;
// 显示进度(如果有内容长度信息)
if (contentLength.HasValue)
{
double progress = (double)totalBytesRead / contentLength.Value * 100;
Console.WriteLine($"下载进度: {progress:F2}% ({totalBytesRead}/{contentLength})");
}
}
}
}
三、进阶功能:支持进度报告和取消操作
为了提供更好的用户体验,我们可以添加进度报告和取消功能:
public static class AdvancedFileDownloader
{
private static readonly HttpClient _httpClient = new HttpClient();
/// <summary>
/// 带进度报告和取消支持的下载方法
/// </summary>
public static async Task DownloadFileWithProgressAsync(
string url,
string savePath,
IProgress<double> progress = null,
CancellationToken cancellationToken = default)
{
// 参数验证
if (string.IsNullOrEmpty(url))
throw new ArgumentException("URL不能为空", nameof(url));
if (string.IsNullOrEmpty(savePath))
throw new ArgumentException("保存路径不能为空", nameof(savePath));
// 创建目录
string directory = Path.GetDirectoryName(savePath);
if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
// 发送请求
using HttpResponseMessage response = await _httpClient.GetAsync(
url,
HttpCompletionOption.ResponseHeadersRead,
cancellationToken);
response.EnsureSuccessStatusCode();
// 获取文件信息
long? totalBytes = response.Content.Headers.ContentLength;
// 创建文件流
using Stream contentStream = await response.Content.ReadAsStreamAsync();
using FileStream fileStream = new FileStream(
savePath,
FileMode.Create,
FileAccess.Write,
FileShare.None,
81920,
useAsync: true);
// 下载缓冲区
byte[] buffer = new byte[81920];
int bytesRead;
long totalBytesRead = 0;
// 读取并写入
while ((bytesRead = await contentStream.ReadAsync(buffer, 0, buffer.Length, cancellationToken)) > 0)
{
// 检查取消请求
cancellationToken.ThrowIfCancellationRequested();
// 写入文件
await fileStream.WriteAsync(buffer, 0, bytesRead, cancellationToken);
totalBytesRead += bytesRead;
// 报告进度
if (totalBytes.HasValue && progress != null)
{
double percentage = (double)totalBytesRead / totalBytes.Value * 100;
progress.Report(percentage);
}
}
}
}
四、断点续传实现
对于大文件下载,断点续传是一个重要功能:
public static class ResumableDownloader
{
private static readonly HttpClient _httpClient = new HttpClient();
/// <summary>
/// 支持断点续传的下载方法
/// </summary>
public static async Task<bool> DownloadWithResumeAsync(
string url,
string savePath,
IProgress<double> progress = null)
{
long existingLength = 0;
// 检查已下载的部分
if (File.Exists(savePath))
{
FileInfo fileInfo = new FileInfo(savePath);
existingLength = fileInfo.Length;
// 设置断点续传请求头
_httpClient.DefaultRequestHeaders.Range = new System.Net.Http.Headers.RangeHeaderValue(
existingLength, null);
}
// 发送请求
using HttpResponseMessage response = await _httpClient.GetAsync(
url, HttpCompletionOption.ResponseHeadersRead);
// 检查服务器是否支持断点续传
if (existingLength > 0 && response.StatusCode != System.Net.HttpStatusCode.PartialContent)
{
// 服务器不支持断点续传,重新开始下载
File.Delete(savePath);
existingLength = 0;
_httpClient.DefaultRequestHeaders.Range = null;
// 重新发送请求
response.Dispose();
return await DownloadWithResumeAsync(url, savePath, progress);
}
response.EnsureSuccessStatusCode();
long? totalBytes = existingLength + response.Content.Headers.ContentLength;
// 以追加模式打开文件
using Stream contentStream = await response.Content.ReadAsStreamAsync();
using FileStream fileStream = new FileStream(
savePath,
existingLength > 0 ? FileMode.Append : FileMode.Create,
FileAccess.Write,
FileShare.None,
81920,
useAsync: true);
byte[] buffer = new byte[81920];
int bytesRead;
long totalBytesRead = existingLength;
while ((bytesRead = await contentStream.ReadAsync(buffer, 0, buffer.Length)) > 0)
{
await fileStream.WriteAsync(buffer, 0, bytesRead);
totalBytesRead += bytesRead;
if (totalBytes.HasValue && progress != null)
{
double percentage = (double)totalBytesRead / totalBytes.Value * 100;
progress.Report(percentage);
}
}
return true;
}
}
五、完整示例程序
下面是一个完整的控制台应用程序示例:
using System;
using System.Threading;
using System.Threading.Tasks;
namespace FileDownloaderDemo
{
class Program
{
static async Task Main(string[] args)
{
Console.WriteLine("=== C# 文件下载演示程序 ===");
// 下载配置
string[] urls =
{
"https://example.com/file1.zip",
"https://example.com/file2.pdf",
"https://example.com/file3.jpg"
};
string saveDirectory = "Downloads";
// 创建进度显示
var progress = new Progress<double>();
progress.ProgressChanged += (sender, percentage) =>
{
Console.SetCursorPosition(0, Console.CursorTop);
Console.Write($"下载进度: {percentage:F2}%");
};
// 创建取消令牌
using var cts = new CancellationTokenSource();
// 设置超时取消
cts.CancelAfter(TimeSpan.FromMinutes(5));
// 批量下载
for (int i = 0; i < urls.Length; i++)
{
try
{
string url = urls[i];
string fileName = $"file_{i + 1}{Path.GetExtension(url)}";
string savePath = Path.Combine(saveDirectory, fileName);
Console.WriteLine($"\n开始下载: {fileName}");
Console.WriteLine($"来源: {url}");
await AdvancedFileDownloader.DownloadFileWithProgressAsync(
url,
savePath,
progress,
cts.Token);
Console.WriteLine($"\n✓ 下载完成: {fileName}");
}
catch (OperationCanceledException)
{
Console.WriteLine("\n下载已被取消");
break;
}
catch (Exception ex)
{
Console.WriteLine($"\n✗ 下载失败: {ex.Message}");
}
}
Console.WriteLine("\n所有下载任务已完成!");
Console.WriteLine("按任意键退出...");
Console.ReadKey();
}
}
}
六、关键知识点解析
1. HTTP 协议基础
-
GET 请求用于获取资源
-
状态码检查(EnsureSuccessStatusCode)
-
内容类型和长度头信息
2. IO 流处理
-
Stream 抽象类的使用
-
异步读写操作(ReadAsync/WriteAsync)
-
缓冲区大小优化
3. 异步编程
-
async/await 关键字
-
取消令牌(CancellationToken)
-
进度报告(IProgress<T>)
4. 错误处理
-
网络异常处理
-
文件系统权限检查
-
资源释放(using 语句)
七、优化建议
-
连接池管理:重用 HttpClient 实例以提高性能
-
超时设置:配置合理的连接和读取超时
-
重试机制:实现指数退避重试策略
-
速度限制:添加下载速度控制功能
-
校验和验证:下载完成后验证文件完整性
八、总结
本文详细介绍了如何使用 C# 实现基于 HTTP 协议的文件自动下载功能。通过结合 HttpClient 和 IO 流,我们可以创建高效、可靠的文件下载工具。关键点包括异步编程模型的使用、进度报告的实现、错误处理机制以及断点续传等高级功能。
在实际应用中,可以根据具体需求调整缓冲区大小、添加日志记录、实现更复杂的重试逻辑等。这些技术不仅适用于文件下载,也为其他网络通信编程提供了基础模式。
希望本文能为您的 C# 网络编程实践提供有价值的参考。在实现类似功能时,始终要考虑到网络环境的不稳定性、资源管理的正确性以及用户体验的流畅性。