使用 C# 下载文件的十八般武艺

简介: 文件下载是一个软件开发中的常见需求。本文从最简单的下载方式开始步步递进,讲述了文件下载过程中的常见问题并给出了解决方案。并展示了如何使用多线程提升 HTTP 的下载速度以及调用 aria2 实现非 HTTP 协议的文件下载。

文件下载是一个软件开发中的常见需求。本文从最简单的下载方式开始步步递进,讲述了文件下载过程中的常见问题并给出了解决方案。并展示了如何使用多线程提升 HTTP 的下载速度以及调用 aria2 实现非 HTTP 协议的文件下载。

简单下载

在 .NET 程序中下载文件最简单的方式就是使用 WebClient 的 DownloadFile 方法:

varurl="https://www.coderbusy.com";
varsave=@"D:\1.html";
using (varweb=newWebClient())
{
web.DownloadFile(url,save);
}

异步下载

该方法也提供异步的实现:

varurl="https://www.coderbusy.com";
varsave=@"D:\1.html";
using (varweb=newWebClient())
{
awaitweb.DownloadFileTaskAsync(url, save);
}

下载文件的同时向服务器发送自定义请求头

如果需要对文件下载请求进行定制,可以使用 HttpClient :

varurl="https://www.coderbusy.com";
varsave=@"D:\1.html";
varhttp=newHttpClient();
varrequest=newHttpRequestMessage(HttpMethod.Get,url);
//增加 Auth 请求头request.Headers.Add("Auth","123456");
varresponse=awaithttp.SendAsync(request);
response.EnsureSuccessStatusCode();
using (varfs=File.Open(save, FileMode.Create))
{
using (varms=response.Content.ReadAsStream())
    {
awaitms.CopyToAsync(fs);
    }
}

如何解决下载文件不完整的问题

以上所有代码在应对小文件的下载时没有特别大的问题,在网络情况不佳或文件较大时容易引入错误。以下代码在开发中很常见:

varurl="https://www.coderbusy.com";
varsave=@"D:\1.html";
if (!File.Exists(save))
{
Console.WriteLine("文件不存在,开始下载...");
using (varweb=newWebClient())
    {
awaitweb.DownloadFileTaskAsync(url, save);
    }
Console.WriteLine("文件下载成功");
}
Console.WriteLine("开始处理文件");
//TODO:对文件进行处理

如果在 DownloadFileTaskAsync 方法中发生了异常(通常是网络中断或网络超时),那么下载不完整的文件将会保留在本地系统中。在该任务重试执行时,因为文件已存在(虽然它不完整)所以会直接进入处理程序,从而引入异常。

一个简单的修复方式是引入异常处理,但这种方式对应用程序意外终止造成的文件不完整无效:

varurl="https://www.coderbusy.com";
varsave=@"D:\1.html";
if (!File.Exists(save))
{
Console.WriteLine("文件不存在,开始下载...");
using (varweb=newWebClient())
    {
try        {
awaitweb.DownloadFileTaskAsync(url, save);
        }
catch        {
if (File.Exists(save))
            {
File.Delete(save);
            }
throw;
        }
    }
Console.WriteLine("文件下载成功");
}
Console.WriteLine("开始处理文件");
//TODO:对文件进行处理

笔者更喜欢的方式是引入一个临时文件。下载操作将数据下载到临时文件中,当确定下载操作执行完毕时将临时文件改名:

varurl="https://www.coderbusy.com";
varsave=@"D:\1.html";
if (!File.Exists(save))
{
Console.WriteLine("文件不存在,开始下载...");
using (varweb=newWebClient())
    {
try        {
awaitweb.DownloadFileTaskAsync(url, save);
        }
catch        {
if (File.Exists(save))
            {
File.Delete(save);
            }
throw;
        }
    }
Console.WriteLine("文件下载成功");
}
Console.WriteLine("开始处理文件");
//TODO:对文件进行处理

使用 Downloader 进行 HTTP 多线程下载

在网络带宽充足的情况下,单线程下载的效率并不理想。我们需要多线程和断点续传才可以拿到更好的下载速度。

Downloader 是一个现代化的、流畅的、异步的、可测试的和可移植的 .NET 库。这是一个包含异步进度事件的多线程下载程序。Downloader 与 .NET Standard 2.0 及以上版本兼容,可以在 Windows、Linux 和 macOS 上运行。

5e8ff9bf55ba350.gif

GitHub 开源地址: https://github.com/bezzad/Downloader

NuGet 地址:https://www.nuget.org/packages/Downloader

从 NuGet 安装 Downloader 之后,创建一个下载配置:

vardownloadOpt=newDownloadConfiguration()
{
BufferBlockSize=10240, // 通常,主机最大支持8000字节,默认值为8000。ChunkCount=8, // 要下载的文件分片数量,默认值为1MaximumBytesPerSecond=1024*1024, // 下载速度限制为1MB/s,默认值为零或无限制MaxTryAgainOnFailover=int.MaxValue, // 失败的最大次数OnTheFlyDownload=false, // 是否在内存中进行缓存? 默认值是trueParallelDownload=true, // 下载文件是否为并行的。默认值为falseTempDirectory="C:\\temp", // 设置用于缓冲大块文件的临时路径,默认路径为Path.GetTempPath()。Timeout=1000, // 每个 stream reader  的超时(毫秒),默认值是1000RequestConfiguration=// 定制请求头文件    {
Accept="*/*",
AutomaticDecompression=DecompressionMethods.GZip|DecompressionMethods.Deflate,
CookieContainer=newCookieContainer(), // Add your cookiesHeaders=newWebHeaderCollection(), // Add your custom headersKeepAlive=false,
ProtocolVersion=HttpVersion.Version11, // Default value is HTTP 1.1UseDefaultCredentials=false,
UserAgent=$"DownloaderSample/{Assembly.GetExecutingAssembly().GetName().Version.ToString(3)}"    }
};

创建一个下载服务:

vardownloader=newDownloadService(downloadOpt);

配置事件处理器(该步骤可以省略):

// Provide `FileName` and `TotalBytesToReceive` at the start of each downloads// 在每次下载开始时提供 "文件名 "和 "要接收的总字节数"。downloader.DownloadStarted+=OnDownloadStarted;
// Provide any information about chunker downloads, like progress percentage per chunk, speed, total received bytes and received bytes array to live streaming.// 提供有关分块下载的信息,如每个分块的进度百分比、速度、收到的总字节数和收到的字节数组,以实现实时流。downloader.ChunkDownloadProgressChanged+=OnChunkDownloadProgressChanged;
// Provide any information about download progress, like progress percentage of sum of chunks, total speed, average speed, total received bytes and received bytes array to live streaming.// 提供任何关于下载进度的信息,如进度百分比的块数总和、总速度、平均速度、总接收字节数和接收字节数组的实时流。downloader.DownloadProgressChanged+=OnDownloadProgressChanged;
// Download completed event that can include occurred errors or cancelled or download completed successfully.// 下载完成的事件,可以包括发生错误或被取消或下载成功。downloader.DownloadFileCompleted+=OnDownloadFileCompleted;

接着就可以下载文件了:

stringfile=@"D:\1.html";
stringurl=@"https://www.coderbusy.com";
awaitdownloader.DownloadFileTaskAsync(url, file);

下载非 HTTP 协议的文件

除了 WebClient 可以下载 FTP 协议的文件之外,上文所示的其他方法只能下载 HTTP 协议的文件。

aria2 是一个轻量级的多协议和多源命令行下载工具。它支持 HTTP/HTTPS、FTP、SFTP、BitTorrent 和 Metalink。aria2 可以通过内置的 JSON-RPC 和 XML-RPC 接口进行操作。

我们可以调用 aria2 实现文件下载功能。

GitHub 地址:https://github.com/aria2/aria2

下载地址:https://github.com/aria2/aria2/releases

将下载好的 aria2c.exe 复制到应用程序目录,如果是其他系统则可以下载对应的二进制文件。

publicstaticasyncTaskDownload(stringurl, stringfn)
{
varexe="aria2c";
vardir=Path.GetDirectoryName(fn);
varname=Path.GetFileName(fn);
voidOutput(objectsender, DataReceivedEventArgsargs)
    {
if (string.IsNullOrWhiteSpace(args.Data))
        {
return;
        }
Console.WriteLine("Aria:{0}", args.Data?.Trim());
    }
varargs=$"-x 8 -s 8 --dir={dir} --out={name} {url}";
varinfo=newProcessStartInfo(exe, args)
    {
UseShellExecute=false,
CreateNoWindow=true,
RedirectStandardOutput=true,
RedirectStandardError=true,
    };
if (File.Exists(fn))
    {
File.Delete(fn);
    }
Console.WriteLine("启动 aria2c: {0}", args);
using (varp=newProcess { StartInfo=info, EnableRaisingEvents=true })
    {
if (!p.Start())
        {
thrownewException("aria 启动失败");
        }
p.ErrorDataReceived+=Output;
p.OutputDataReceived+=Output;
p.BeginOutputReadLine();
p.BeginErrorReadLine();
awaitp.WaitForExitAsync();
p.OutputDataReceived-=Output;
p.ErrorDataReceived-=Output;
    }
varfi=newFileInfo(fn);
if (!fi.Exists||fi.Length==0)
    {
thrownewFileNotFoundException("文件下载失败", fn);
    }
}

以上代码通过命令行参数启动了一个新的 aria2c 下载进程,并对下载进度信息输出在了控制台。调用方式如下:

varurl="https://www.coderbusy.com";
varsave=@"D:\1.html";
awaitDownload(url, save);
目录
相关文章
|
3月前
|
数据采集 JavaScript C#
C#图像爬虫实战:从Walmart网站下载图片
C#图像爬虫实战:从Walmart网站下载图片
|
4月前
|
数据采集 XML JavaScript
C# 中 ScrapySharp 的多线程下载策略
C# 中 ScrapySharp 的多线程下载策略
|
2月前
|
监控 前端开发 安全
C#一分钟浅谈:文件上传与下载功能实现
【10月更文挑战第2天】在Web应用开发中,文件的上传与下载是常见需求。本文从基础入手,详细讲解如何在C#环境下实现文件上传与下载。首先介绍前端表单设计及后端接收保存方法,使用`<input type="file">`与`IFormFile`接口;接着探讨错误处理与优化策略,如安全性验证和路径管理;最后讲解文件下载的基本步骤,包括确定文件位置、设置响应头及发送文件流。此外,还提供了进阶技巧,如并发处理、大文件分块上传及进度监控,帮助开发者构建更健壮的应用系统。
120 15
|
2月前
|
存储 C#
【C#】大批量判断文件是否存在的两种方法效率对比
【C#】大批量判断文件是否存在的两种方法效率对比
42 1
|
2月前
|
XML 存储 缓存
C#使用XML文件的详解及示例
C#使用XML文件的详解及示例
96 0
|
4月前
|
监控 安全 C#
使用C#如何监控选定文件夹中文件的变动情况?
使用C#如何监控选定文件夹中文件的变动情况?
118 19
|
4月前
|
编译器 C# Windows
C#基础:手动编译一个.cs源代码文件并生成.exe可执行文件
通过上述步骤,应该能够高效准确地编译C#源代码并生成相应的可执行文件。此外,这一过程强调了对命令行编译器的理解,这在调试和自动化编译流程中是非常重要的。
300 2
|
4月前
|
文字识别 C# Python
使用C#将几个Excel文件合并去重分类
使用C#将几个Excel文件合并去重分类
32 3
|
4月前
|
C# 图形学 数据安全/隐私保护
Unity数据加密☀️ 二、使用Rider将C#代码生成DLL文件
Unity数据加密☀️ 二、使用Rider将C#代码生成DLL文件
|
4月前
|
C#
C# 写日志文件
C# 写日志文件
46 0