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

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

简单下载

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

var url = "https://www.coderbusy.com";
var save = @"D:\1.html";
using (var web = new WebClient())
{web.DownloadFile(url,save);
}

异步下载

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

var url = "https://www.coderbusy.com";
var save = @"D:\1.html";
using (var web = new WebClient())
{await web.DownloadFileTaskAsync(url, save);
}

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

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

var url = "https://www.coderbusy.com";
var save = @"D:\1.html";
var http = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get,url);
//增加 Auth 请求头
request.Headers.Add("Auth","123456");
var response = await http.SendAsync(request);
response.EnsureSuccessStatusCode();
using (var fs = File.Open(save, FileMode.Create))
{using (var ms = response.Content.ReadAsStream()){await ms.CopyToAsync(fs);}
}

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

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

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

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

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

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

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

var url = "https://www.coderbusy.com";
var save = @"D:\1.html";
if (!File.Exists(save))
{Console.WriteLine("文件不存在,开始下载...");//先下载到临时文件var tmp = save + ".tmp";using (var web = new WebClient()){await web.DownloadFileTaskAsync(url, tmp);}File.Move(tmp, save, true);Console.WriteLine("文件下载成功");
}
Console.WriteLine("开始处理文件");
//TODO:对文件进行处理

使用 Downloader 进行 HTTP 多线程下载

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

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

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

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

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

var downloadOpt = new DownloadConfiguration()
{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 =  new CookieContainer(), // Add your cookiesHeaders = new WebHeaderCollection(), // Add your custom headersKeepAlive = false,ProtocolVersion = HttpVersion.Version11, // Default value is HTTP 1.1UseDefaultCredentials = false,UserAgent = $"DownloaderSample/{Assembly.GetExecutingAssembly().GetName().Version.ToString(3)}"}
};

创建一个下载服务:

var downloader = new DownloadService(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;

接着就可以下载文件了:

string file = @"D:\1.html";
string url = @"https://www.coderbusy.com";
await downloader.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 复制到应用程序目录,如果是其他系统则可以下载对应的二进制文件。

public static async Task Download(string url, string fn)
{var exe = "aria2c";var dir = Path.GetDirectoryName(fn);var name = Path.GetFileName(fn);void Output(object sender, DataReceivedEventArgs args){if (string.IsNullOrWhiteSpace(args.Data)){return;}Console.WriteLine("Aria:{0}", args.Data?.Trim());}var args = $"-x 8 -s 8 --dir={dir} --out={name} {url}";var info = new ProcessStartInfo(exe, args){UseShellExecute = false,CreateNoWindow = true,RedirectStandardOutput = true,RedirectStandardError = true,};if (File.Exists(fn)){File.Delete(fn);}Console.WriteLine("启动 aria2c: {0}", args);using (var p = new Process { StartInfo = info, EnableRaisingEvents = true }){if (!p.Start()){throw new Exception("aria 启动失败");}p.ErrorDataReceived += Output;p.OutputDataReceived += Output;p.BeginOutputReadLine();p.BeginErrorReadLine();await p.WaitForExitAsync();p.OutputDataReceived -= Output;p.ErrorDataReceived -= Output;}var fi = new FileInfo(fn);if (!fi.Exists || fi.Length == 0){throw new FileNotFoundException("文件下载失败", fn);}
}

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

var url = "https://www.coderbusy.com";
var save = @"D:\1.html";
await Download(url, save);

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/299303.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

打造一流的研发中心

同学们,我们正在从事伟大的事业,用SaaS这一创新模式,来实施电子商务、方便工作生活、提升全球经济。做大事,需要立大志。我们研发中心的Vision就是:打造一流的研发中心。欢迎大家讨论:‘一流’的内涵是什么…

mysql 查询空字符串 设置默认值_MySQL默认值选型是空,还是 NULL-爱可生

如果对一个字段没有过多要求,是使用“”还是使用 NULL,一直是个让人困惑的问题。即使有前人留下的开发规范,但是能说清原因的也没有几个。NULL 是“”吗?在辨别 NULL 是不是空的这个问题上,感觉就像是在证明 1 1 是不…

.NET程序加壳的基本原理和方式浅析

.NET程序加壳的基本原理和方式浅析加壳程序是一种常用的保护应用程序的办法,确切的说是一种加密办法。取名为壳,意思是说这种对程序的保护办法就像植物种子的外壳,咱们运用一段程序将咱们的主程序包裹在其间,不能轻易被其他人看见…

如何开发一个学生成绩管理糸统(9)

这一节,我要说明的是在数据集中添加事务, 在这里说明一下事务的必要性: 大多数基于 web 的电子邮件客户端都使用一个网格列出每条消息,除了包含邮件的信息(主题、发送者等等)外,还包括一个复选框…

见识决定眼界,关注这些让你变得博学且有趣

全世界只有3.14 % 的人关注了爆炸吧知识真正决定人与人之间的差距的,其实是我们对事物的见识与内心的格局,见识的深浅决定人生的深浅,格局的大小决定了人生之路是宽是窄。今天给大家推荐几个有深度、有想法的公众号,希望能够给你带…

ELK太重?试试KFC日志采集

写在前面ELK三剑客(ElasticSearch,Logstash,Kibana)基本上可以满足日志采集、信息处理、统计分析、可视化报表等一些日志分析的工作,但是对我们来说……太重了,并且技术栈不是一路的。我们的场景是需要采集…

linux引导时输入特殊信息的含义

linux引导时输入特殊信息的含义:通常只有在缺省模式失败(屏幕一片混乱或者安装到某个阶段无法进行下去)的情况下会尝试这些模式。 对于一些支持的其他模式,参考文件/usr/share/doc/anaconda*/command-line.txt(如果安装…

ActionContext和ActionSupport的学习

2019独角兽企业重金招聘Python工程师标准>>> ActionContext和ActionSupport的学习 1.ActionContext中有众多的定义好的常量,就像是Constant类中定义的常量;诸如:session、Application、actionInvocation、Container等;…

中求和符号上下标_涨电脑知识:如何在word中编写复杂的公式,写论文必备技能...

我们在用word写资料时,比如论文、数据分析、报告等,经常需要插入一些公式,这些公式会含有一些特殊符号,比如根号、分式、求和等,这些特殊符号是不能直接用输入法来完成的,需要借助于word扩展的功能来帮助我…

6、struts.properties配置详解

在Struts2的核心包中org\apache\struts2文件夹下可以找到Struts2的一个默认配置文件default.properties。有时为了调试方便我们可以更改部分默认内容。具体的方法是在工程的WEB-INF/classes文件夹下简历一个struts.properties文件,并将要修改的信息配置到该文件中 s…

菲尔兹奖第一华人!从抓虾仔到哈佛终身教授,他年少成名,获奖无数,造福我国数学教育数十年...

全世界只有3.14 % 的人关注了爆炸吧知识在数学界有这么一个人,他有着“数学天才”、“科学大师”、数学王国的“凯撒大帝”等一众称号。就连国际数学大师、阿贝尔奖获得者辛格都这样评价他说:“即使在哈佛,他一个人就是一个数学系&#xff01…

关于string

string是一种很特殊的数据类型,它既是基元类型又是引用类型,在编译以及运行时,.Net都对它做了一些优化工作。  一.恒定的字符串要想比较全面的了解stirng类型,首先要清楚.Net中的值类型与引用类型。在C#中&#xff0…

python第七天--字符串的方法与注释

capitalize() 把字符串的第一个字符改为大写casefold() 把整个字符串的所有字符改为小写center(width) 将字符串居中,并使用空格填充至长度width的新字符串count(sub[,start[,end]]) 返回sub在字符串里边出现的次数,start和end参数表示范围&#xff…

android的热点分配ip_Android 得到连接热点的ip的方法

下面给大家介绍Android 得到连接热点的ip的方法 ,具体代码如下所示:WifiManager wifiManager (WifiManager) this.getSystemService(Context.WIFI_SERVICE);if (!wifiManager.isWifiEnabled()) {System.out.println("");wifiManager.setWifiE…

【SQL基础】T-SQL函数类型——元数据函数

一般来说,元数据函数返回的是有关指定数据库和数据对象的信息。下面介绍几种元数据函数。 1、COL_NAME(tab_id,col_id),返回表tab_id的tab_id的列名。 例:selectCOL_NAME(OBJECT_ID(employees),3); 结果为’FirstName’ 2、COLUMNPROPERTY(id…

为什么我们总是忍不住要刷微信?

全世界只有3.14 % 的人关注了爆炸吧知识真正决定人与人之间的差距的,其实是我们对事物的见识与内心的格局,见识的深浅决定人生的深浅,格局的大小决定了人生之路是宽是窄。今天给大家推荐几个有深度、有想法的公众号,希望能够给你带…

22、多进程和多线程

 Android进程简介 Android会启动一个LINUX进程和一个主线程。默认的情况下,所 有该程序的组件都将在该进程中运行。当启动应用程序时,Linux会为每 一个程序单独分配一个进程,该进程默认只拥有一个主线程。 组件可以运行在当前进程中,也可以运行在其他进程中。组件运行在 哪个…

Docker小白到实战之常用命令演示,通俗易懂

前言上一篇大概认识了Docker,主要是从概念、架构、优点及流程方面进行阐述,并进行安装和体验;接下来就开始进行实操学习,在演示过程中会针对关键的知识点进行归纳和总结,这里先从常用命令说起,来吧&#xf…

lumen 配置数据库结果自动转数组_lumen 数据库操作 Cannot use object of type stdClass as array...

你可能记得,操作数据库时$dataList 什么什么 ->get();能查询到数据 数据结果为数组加stdClass类型你使用 $dataList $dataList->toArray();进行数据转化结果你发现,没有正确转化,你再到里面去转化,但结果证明也是徒劳foreach ($allDa…

今天这个日子,大多数人都不知道…

全世界只有3.14 % 的人关注了爆炸吧知识我是蝙蝠,身体虽小五毒俱全你确定要尝吗?我是果子狸,人们一度“谈我色变”但如今又被端上餐桌!我是野兔,人们说我很可爱我的肉却变成“野味”毛发变成皮草……今天,世…