RError.com

RError.com Logo RError.com Logo

RError.com Navigation

  • 主页

Mobile menu

Close
  • 主页
  • 系统&网络
    • 热门问题
    • 最新问题
    • 标签
  • Ubuntu
    • 热门问题
    • 最新问题
    • 标签
  • 帮助
主页 / 问题 / 1110487
Accepted
EvgeniyZ
EvgeniyZ
Asked:2020-04-15 07:01:13 +0000 UTC2020-04-15 07:01:13 +0000 UTC 2020-04-15 07:01:13 +0000 UTC

HttpClient文件下载实现

  • 772

我正在开发一个应用程序,该应用程序应将文件从网址下载到计算机,然后对其进行处理。
我完成了任务,一切正常,然后客户转向我说:

我的网速很慢,经常断线,不能直接给链接,否则程序会重新下载文件。

实际上我正在尝试解决这个问题,或者更确切地说,我需要做以下两件事:

  1. 连接断开后恢复文件下载。- 正如他们所说,在缓冲的帮助下部分解决了短期休息似乎已经消失了。
  2. 下载现有的“存根”,该存根在上一个不成功的下载过程之后仍然存在。

现在下载文件的过程如下所示:

private async Task ProcessContentStream(long? totalDownloadSize, Stream contentStream)
{
    var totalBytesRead = 0L;
    var readCount = 0L;
    var buffer = new byte[4096];
    var isMoreToRead = true;

    using (var fileStream = new FileStream(_destinationFilePath, FileMode.Create, FileAccess.Write, FileShare.None, 4096, true))
    {
        do
        {
            var bytesRead = await contentStream.ReadAsync(buffer, 0, buffer.Length);
            if (bytesRead == 0)
            {
                isMoreToRead = false;
                TriggerProgressChanged(totalDownloadSize, totalBytesRead);
                continue;
            }

            await fileStream.WriteAsync(buffer, 0, bytesRead);

            totalBytesRead += bytesRead;
            readCount += 1;

            if (readCount % 100 == 0)
                TriggerProgressChanged(totalDownloadSize, totalBytesRead);
        }
        while (isMoreToRead);
    }
}

也就是说,我们从服务器中取出一块(缓冲区)并将其写入,FileStream直到服务器为我们提供整个文件。

我试过了:

  • 在启动时,将Stream's 的位置移动到fileStream.Length(当前文件大小),但HttpClient给出通常的Stream,CanSeek = false即,我无法从某个点开始从服务器接收数据,只能从一开始。
  • 我试图跳过写入文件(await fileStream.WriteAsync(buffer, 0, bytesRead);),直到从服务器接收到的数据数量等于当前文件大小,也就是说,我做了这样的事情(草图):

    if (totalBytesRead >= fileStream.Length)
        await fileStream.WriteAsync(buffer, 0, bytesRead);
    

    但是感觉就像是因为我,await contentStream.ReadAsync(buffer, 0, buffer.Length);我仍然从服务器下载数据,但我只是不把它写下来,结果我得到了一个损坏的文件,在下载停止的地方正好有一个间隙。在这里,很可能发生这种情况是因为当应用程序关闭和重新加载时缓冲区没有完全写入,我需要更早地开始写入文件 1“缓冲区”,但我不明白如何执行此操作。

简而言之,在应用程序中断/关闭后,我陷入了僵局,不知道如何恢复磁盘上的现有文件。如果你教我从服务器的某个位置获取字节而不影响开头(Seekin Stream),那就太好了,那么你可以在休息后请求这些字节并继续下载,或者还有其他选择吗?

c#
  • 1 1 个回答
  • 10 Views

1 个回答

  • Voted
  1. Best Answer
    EvgeniyZ
    2020-04-16T01:41:30Z2020-04-16T01:41:30Z

    总的来说,是的,感谢所有帮助并提出Range Header的人。

    • 我创建了一个方法,该方法通过“回滚”返回当前文件大小到缓冲区大小:

      private long GetFileSize()
      {
          long? fileSize = null;
          if (_isExist)
          {
              fileSize = new FileInfo(_destinationFilePath)?.Length;
      
              var tail = fileSize % _bufferSize;
              if (tail != 0) fileSize -= _bufferSize;
          }
      
          return fileSize ?? 0L;
      }
      

      它将返回 0 或размер файла - размер буфера.

    • 然后我将请求重写为以下内容:

      var request = new HttpRequestMessage { RequestUri = new Uri(_downloadUrl) };
      request.Headers.Range = new RangeHeaderValue(_fileSize, null);
      using var response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
      
      • RangeHeaderValue(_fileSize, null);- 将先前接收到的文件的大小发送到服务器并取走全部余额。如果 0 - 服务器提供一切。
      • _httpClient.SendAsync(...)- 向服务器发送带有Range标头的 GET 请求。
    • 我FileStream设置FileMode.OpenOrCreate它以使文件不被覆盖。

    • 在从服务器读取数据之前,我将位置设置为FileStream我之前收到的文件的大小:

      fileStream.Seek(_fileSize, SeekOrigin.Begin);
      

    实际上,继续下载文件所需的一切。代码本身现在是这样的(草稿):

    public class DownloadManager : IDisposable
    {
        private readonly HttpClient _httpClient;
    
        private readonly string _downloadUrl;
        private readonly string _destinationFilePath;
        private long _fileSize;
        private long? _totalFileSize;
        private bool _isExist;
        private const int _bufferSize = 8192;
    
        private int _connections;
    
        public delegate void ProgressChangedHandler(long? totalFileSize, long totalBytesDownloaded, double? progressPercentage);
        public event ProgressChangedHandler ProgressChanged;
    
        public DownloadManager(string downloadUrl, string destinationFilePath)
            => (_downloadUrl, _destinationFilePath, _httpClient)
            = (downloadUrl, destinationFilePath, new HttpClient());
    
        public async Task StartDownload()
        {
            bool isConnect = false;
            do
            {
                try
                {
                    using var sizeRequest = await _httpClient.GetAsync(_downloadUrl, HttpCompletionOption.ResponseHeadersRead);
                    _totalFileSize = sizeRequest.Content.Headers.ContentLength;
                    isConnect = true;
                    _connections = 0;
                }
                catch (Exception e)
                {
                    Console.WriteLine(e.Message);
                    _connections++;
                    await Task.Delay(TimeSpan.FromSeconds(5));
                }
    
            } while (!isConnect && _connections <= 15);
    
            if (!isConnect) return;
    
            _isExist = File.Exists(_destinationFilePath);
            _fileSize = GetFileSize();
    
            var request = new HttpRequestMessage { RequestUri = new Uri(_downloadUrl) };
            request.Headers.Range = new RangeHeaderValue(_fileSize, null);
            using var response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
            await DownloadFileFromHttpResponseMessage(response);
        }
    
        private async Task DownloadFileFromHttpResponseMessage(HttpResponseMessage response)
        {
            Console.WriteLine($"[{response.StatusCode}]: {response.RequestMessage}");
    
            if (response.IsSuccessStatusCode)
            {
                using var contentStream = await response.Content.ReadAsStreamAsync();
                await ProcessContentStream(_totalFileSize, contentStream);
            }
            else
            {
                if (_fileSize != _totalFileSize)
                    response.EnsureSuccessStatusCode();
            }
        }
    
        private async Task ProcessContentStream(long? totalDownloadSize, Stream contentStream)
        {
            var buffer = new byte[_bufferSize];
            var totalBytesRead = _fileSize;
            var isMoreToRead = true;
            var readCount = 0L;
    
            using (var fileStream = new FileStream(_destinationFilePath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None, _bufferSize, true))
            {
                fileStream.Seek(_fileSize, SeekOrigin.Begin);
    
                do
                {
                    int bytesRead = 0;
    
                    try
                    {
                        bytesRead = await contentStream.ReadAsync(buffer, 0, buffer.Length);
                    }
                    catch (Exception e)
                    {
                        Console.WriteLine(e.Message);
    
                        await fileStream.FlushAsync();
                        fileStream.Close();
                        fileStream.Dispose();
                        await StartDownload();
                        return;
                    }
    
    
                    if (bytesRead == 0)
                    {
                        isMoreToRead = false;
                        TriggerProgressChanged(totalDownloadSize, totalBytesRead);
                        continue;
                    }
    
                    await fileStream.WriteAsync(buffer, 0, bytesRead);
    
                    totalBytesRead += bytesRead;
                    readCount += 1;
    
                    if (readCount % 100 == 0)
                        TriggerProgressChanged(totalDownloadSize, totalBytesRead);
                }
                while (isMoreToRead);
            }
    
            Console.WriteLine(_destinationFilePath.GetMD5());
        }
    
        private void TriggerProgressChanged(long? totalDownloadSize, long totalBytesRead)
        {
            if (ProgressChanged == null)
                return;
    
            double? progressPercentage = null;
            if (totalDownloadSize.HasValue)
                progressPercentage = Math.Round((double)totalBytesRead / totalDownloadSize.Value * 100, 2);
    
            ProgressChanged(totalDownloadSize, totalBytesRead, progressPercentage);
        }
    
        private long GetFileSize()
        {
            long? fileSize = null;
            if (_isExist)
            {
                fileSize = new FileInfo(_destinationFilePath)?.Length;
    
                var tail = fileSize % _bufferSize;
                if (tail != 0) fileSize -= _bufferSize;
            }
    
            return fileSize ?? 0L;
        }
    
        public void Dispose() => _httpClient?.Dispose();
    }
    

    按类型欺凌:

    • 每 3-5% 开/关一次窗户。
    • 互联网关闭。
    • 冻结过程很长时间。

    代码成功通过,文件总是被下载并且总是有正确的 Md5 和大小。
    它仍然需要清理,把所有东西都整理好,添加一些检查,事件,然后你就可以使用它了)

    • 4

相关问题

  • 使用嵌套类导出 xml 文件

  • 分层数据模板 [WPF]

  • 如何在 WPF 中为 ListView 手动创建列?

  • 在 2D 空间中,Collider 2D 挂在玩家身上,它对敌人的重量相同,我需要它这样当它们碰撞时,它们不会飞向不同的方向。统一

  • 如何在 c# 中使用 python 神经网络来创建语音合成?

  • 如何知道类中的方法是否属于接口?

Sidebar

Stats

  • 问题 10021
  • Answers 30001
  • 最佳答案 8000
  • 用户 6900
  • 常问
  • 回答
  • Marko Smith

    如何从列表中打印最大元素(str 类型)的长度?

    • 2 个回答
  • Marko Smith

    如何在 PyQT5 中清除 QFrame 的内容

    • 1 个回答
  • Marko Smith

    如何将具有特定字符的字符串拆分为两个不同的列表?

    • 2 个回答
  • Marko Smith

    导航栏活动元素

    • 1 个回答
  • Marko Smith

    是否可以将文本放入数组中?[关闭]

    • 1 个回答
  • Marko Smith

    如何一次用多个分隔符拆分字符串?

    • 1 个回答
  • Marko Smith

    如何通过 ClassPath 创建 InputStream?

    • 2 个回答
  • Marko Smith

    在一个查询中连接多个表

    • 1 个回答
  • Marko Smith

    对列表列表中的所有值求和

    • 3 个回答
  • Marko Smith

    如何对齐 string.Format 中的列?

    • 1 个回答
  • Martin Hope
    Alexandr_TT 2020年新年大赛! 2020-12-20 18:20:21 +0000 UTC
  • Martin Hope
    Alexandr_TT 圣诞树动画 2020-12-23 00:38:08 +0000 UTC
  • Martin Hope
    Air 究竟是什么标识了网站访问者? 2020-11-03 15:49:20 +0000 UTC
  • Martin Hope
    Qwertiy 号码显示 9223372036854775807 2020-07-11 18:16:49 +0000 UTC
  • Martin Hope
    user216109 如何为黑客设下陷阱,或充分击退攻击? 2020-05-10 02:22:52 +0000 UTC
  • Martin Hope
    Qwertiy 并变成3个无穷大 2020-11-06 07:15:57 +0000 UTC
  • Martin Hope
    koks_rs 什么是样板代码? 2020-10-27 15:43:19 +0000 UTC
  • Martin Hope
    Sirop4ik 向 git 提交发布的正确方法是什么? 2020-10-05 00:02:00 +0000 UTC
  • Martin Hope
    faoxis 为什么在这么多示例中函数都称为 foo? 2020-08-15 04:42:49 +0000 UTC
  • Martin Hope
    Pavel Mayorov 如何从事件或回调函数中返回值?或者至少等他们完成。 2020-08-11 16:49:28 +0000 UTC

热门标签

javascript python java php c# c++ html android jquery mysql

Explore

  • 主页
  • 问题
    • 热门问题
    • 最新问题
  • 标签
  • 帮助

Footer

RError.com

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

帮助

© 2023 RError.com All Rights Reserve   沪ICP备12040472号-5