使用 C# 下载文件的多种方法小结

作者:Soar、毅 时间:2023-11-08 06:59:37 

文件下载是一个软件开发中的常见需求。本文从最简单的下载方式开始步步递进,讲述了文件下载过程中的常见问题并给出了解决方案。并展示了如何使用多线程提升 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 上运行。

使用 C# 下载文件的多种方法小结

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);

来源:https://www.cnblogs.com/Soar1991/p/15165595.html

标签:C#,下载文件
0
投稿

猜你喜欢

  • maven继承父工程统一版本号的实现

    2023-01-27 09:19:49
  • JAVA求两直线交点和三角形内外心的方法

    2023-07-30 02:46:35
  • springboot 整合fluent mybatis的过程,看这篇够了

    2022-09-14 01:16:53
  • Java多线程之CAS算法实现线程安全

    2022-12-09 17:53:53
  • mybatis-plus 新增/修改如何实现自动填充指定字段

    2023-11-28 22:20:53
  • Java基于IO流读取文件的方法

    2023-08-11 16:23:57
  • Android WebView实现顶部进度条

    2023-10-14 23:44:03
  • sublime text3搭建配置c语言编译环境的详细图解教程(小白级)

    2021-12-16 09:18:27
  • C#中#define后面只加一个参数的解释

    2022-09-06 07:23:55
  • python调用java的jar包方法

    2023-06-23 21:54:24
  • 详解Java信号量Semaphore的原理及使用

    2023-09-18 00:13:15
  • C#实现简易计算器功能(1)(窗体应用)

    2022-09-30 16:59:16
  • mybatis 多表关联mapper文件写法操作

    2021-12-02 23:21:25
  • OpenCV图像处理之常见的图像灰度变换

    2021-09-09 07:47:01
  • 详解记录Java Log的几种方式

    2022-11-10 16:38:19
  • java实现简单美女拼图游戏

    2023-03-09 16:45:26
  • java关于字符串的常用API

    2022-09-13 22:19:03
  • Java内存区域管理详解

    2023-11-10 23:44:42
  • 详解C#如何读写config配置文件

    2023-09-23 01:18:34
  • 拉钩网java笔试题分享

    2022-02-13 08:48:25
  • asp之家 软件编程 m.aspxhome.com