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

2023-11-18

文件下载是一个软件开发中的常见需求。本文从最简单的下载方式开始步步递进,讲述了文件下载过程中的常见问题并给出了解决方案。并展示了如何使用多线程提升 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, // 要下载的文件分片数量,默认值为1
    MaximumBytesPerSecond = 1024 * 1024, // 下载速度限制为1MB/s,默认值为零或无限制
    MaxTryAgainOnFailover = int.MaxValue, // 失败的最大次数
    OnTheFlyDownload = false, // 是否在内存中进行缓存? 默认值是true
    ParallelDownload = true, // 下载文件是否为并行的。默认值为false
    TempDirectory = "C:\\temp", // 设置用于缓冲大块文件的临时路径,默认路径为Path.GetTempPath()。
    Timeout = 1000, // 每个 stream reader  的超时(毫秒),默认值是1000
    RequestConfiguration = // 定制请求头文件
    {
        Accept = "*/*",
        AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate,
        CookieContainer =  new CookieContainer(), // Add your cookies
        Headers = new WebHeaderCollection(), // Add your custom headers
        KeepAlive = false,
        ProtocolVersion = HttpVersion.Version11, // Default value is HTTP 1.1
        UseDefaultCredentials = 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);
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

随机推荐

  • Modbus 485继电器开、关、读 串口指令

    以下均为地址01时的命令 开继电器1 01 06 00 00 00 01 48 0A 关继电器1 01 06 00 00 00 00 89 CA 开继电器2 01 06 00 01 00 01 19 CA 关继电器2 01 06 00 01
  • Java NIO 的前生今世 之二 NIO Channel 小结

    Java NIO Channel 通常来说 所有的 NIO 的 I O 操作都是从 Channel 开始的 一个 channel 类似于一个 stream java Stream 和 NIO Channel 对比 我们可以在同一个 Chan
  • 高淇Struts2.0教程之视频笔记(4)

    4 struts2配置文件 struts xml 详解 4 1指定web应用默认字符集
  • java几位_JAVA 获得数字第几位的几种方法总结(转)

    JAVA获得某个数字的最后第几位是什么的方法 比如 1234567890 则获得最后第三位 则返回8 public class Test 字典 里面保存的是1 10 100 1000 直到long的最大允许位数 private static
  • Latex插入图片并引用

    插入图片主要为viso文件和excel图表 两种图片如果直接转pdf的话会有大片空白区域 不利于排版 解决办法 excel图表粘贴到viso中 后面两中图片处理方法相同 点击另存 保存格式为可移植网络图形 即png格式 保存路径为latex
  • 2023华为od机试统一考试B卷【阿里巴巴找黄金宝箱(III)】

    题目描述 贫如洗的樵夫阿里巴巴在去砍柴的路上 无意中发现了强盗集团的藏宝地 藏宝地有编号从0 N的箱子 每个箱子上面贴有一个数字 阿里巴巴念出一个咒语数字 查看宝箱是否存在两个不同箱子 这两个箱子上贴的数字相同 同时这两个箱子的编号之差的绝
  • 2022蓝桥杯学习——1.递归和递推

    递归 关于递归 所有的递归都可以转换成一棵递归搜索树 我们需要考虑的是枚举的顺序 例题 1 递归实现指数型枚举 题目描述 从 1 n 这 n 个整数中随机选取任意多个 输出所有可能的选择方案 输入格式 输入一个整数 n 输出格式 每行输出一
  • vue实现鼠标放上去就有提示_Vue实现鼠标经过文字显示悬浮框效果的示例代码

    需求 在所做的Vue项目中 需要在鼠标移动文字框的时候显示一些详细信息 最终实现的效果如下 鼠标经过button的时候 可以在光标附近显示出一个悬浮框 显示框里面显示时间和值的信息 鼠标移出button元素的时候 这个显示框会消失 分析 涉
  • C++编写优先队列打印任务

    打印机的打印队列中 每一个打印任务都有一个优先级 为1 9的一个整数 9的优先级最高 1的优先级最低 打印按如下方法进行 1 取出打印队列中队首的打印任务J 2 如果打印队列中存在优先级高于J的打印任务 则将J移动到打印队列的队尾 否则 打
  • 微软鼠标测试软件,第一款win8鼠标:微软Sculpt全球首测

    1Sculpt触控鼠标 带来全新感受 中关村在线键鼠频道原创 微软硬件在外设产品研发上 一直致力于以领先的科技带给用户超凡的体验 从早期的IE3 0 到越野蓝影 再到Arc Touch Touch Mouse等等 微软硬件在的每一次技术革命
  • 企业怎么选择固定资产管理系统

    资产管理 无论在企业还是在事业单位 都是管理人员重要的工作 随着计算机技术的普及 资产管理系统 已经有了相对清晰的管理流程及其配套的管理软件 资产管理系统是面向资产密集型企业的企业信息化解决方案的总称 它以提高资产管理效率 降低企业管理成本
  • VS2019 preview 卡在正在加载解决方案

    VS2019 解决方案 或者项目 卡 正在加载 的解决办法 1 关闭VS 2 去C Users
  • 微信小程序css篇----定位(position)

    昨天2017的微信公开课pro如期进行了 小程序将于2017年1月9日对个人开放 公司项目的demo版做了个大概 过程中花的时间最多的还是页面布局 所以后面将花一段时间将css的属性在小程序里过一篇 虽然小程序里面对于css支持 但没有说明
  • 使用labview 的http协议实现post和get,带解析

    1 创建一个新项目 右键点击我的电脑 新建web服务 然后就弹出web资源和启动VI 2 web资源新建一个VI HTTPMethed 1 vi 用来相应post data的数据 右键可以显示方法url HTTPMethed 1内容如下 3
  • shell脚本实现文件移动、复制等操作

    如题 在此做一记录 方便查阅 bin bash 将一个目录下的一些文件移动到另一个目录下 raw dir home liuyi evt test 可修改绝对路径 mkdir home liuyi evt bp 创建新的文件目录 for el
  • 四、spring源码循环依赖的处理之doCreateBean方法的执行流程(文字描述)

    还写了一篇 内容和这个差不多的 emm 那篇更简洁 算是半伪码形式 https blog csdn net qq 36951116 article details 100078947 其实createBean方法没做什么事 主要就是 1 调
  • Android---SpringBoot实现前后端数据交互

    Android SpringBoot实现前后端数据交互 星光不问赶路人 时间不负有心人 这篇是针对android传数据到后台springboot 使用Xutils框架 使用Xutils框架 关于xutils的使用这是老师的博客大家可以看看
  • 【1024狂欢】力扣经典链表OJ题合集

    现在的力扣题的源代码我会全部一并上传至我的码云仓库里面 点我看仓库 写在前面 首先祝各位程序猿1024狂欢节快乐鸭 这是属于我们的节日 为了致敬1024 今天的力扣系列不再是一题了 而是多个题的组合 也是与我们最近更新的内容梦幻联动 祝大家
  • 节后充电!卷王必看的前端进阶指南来喽~

    本文推荐最近在考虑新机会的小伙伴阅读 前言 上 周和部门BP聊天 她说最近在boss上放出一个初级前端岗位 平均每天都能收到500多份简历 前端市场越来越卷 跳槽前做好技术进阶突击 才能稳拿offer 这里有一份我爆肝两个月整理出的 202
  • 使用 C# 下载文件的十八般武艺

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