简体   繁体   English

如何使用WebClient DownloadFileAsync解决某些用户的下载问题?

[英]How to fix download issues for some users using WebClient DownloadFileAsync?

I created a Windows Form Application in C# that users of my game modification can use to download updates automatically. 我用C#创建了一个Windows窗体应用程序,我的游戏修改用户可以用来自动下载更新。

The 'Launcher', as I call it, uses WebClient to download the updates. 我称之为“启动器”,它使用WebClient下载更新。 But the first release of the mod is very big (2,7 GB zipped). 但是该mod的第一个发行版非常大(压缩了2.7 GB)。 The launcher works perfect for me and most users, but for some users the extraction of the zip file logs an error where the file is corrupted and not readable. 对于我和大多数用户而言,启动器是完美的选择,但对于某些用户而言,提取zip文件会记录错误,表明该文件已损坏且无法读取。

I searched already on stack, and it is possible that the file might be corrupted or truncated due to bad internet connection. 我已经在堆栈上进行搜索,并且由于互联网连接不良,文件可能已损坏或被截断。 But how do I build in a method that fix that problem? 但是,我如何构建解决该问题的方法?

//Start downloading file
using (WebClient webClient = new WebClient())
{
    webClient.DownloadFileCompleted += new 
    AsyncCompletedEventHandler(Client_DownloadFileCompleted);
    webClient.DownloadProgressChanged += new 
    DownloadProgressChangedEventHandler(Client_DownloadProgressChanged);
    webClient.DownloadFileAsync(new Uri("http://www.dagovaxgames.com/api/downloads/+ patch.path), downloadPath);
}  

private void Client_DownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
{
     //install the update
     InstallUpdate();
}

private void InstallUpdate()
{
      var file = currentPatchPath;
      //get the size of the zip file
      fileInfo = new FileInfo(file);
      _fileSize = fileInfo.Length;

      installBackgroundWorker = new BackgroundWorker();
      installBackgroundWorker.DoWork += ExtractFile_DoWork;

      installBackgroundWorker.ProgressChanged += ExtractFile_ProgressChanged;
      installBackgroundWorker.RunWorkerCompleted += ExtractFile_RunWorkerCompleted;
      installBackgroundWorker.WorkerReportsProgress = true;
      installBackgroundWorker.RunWorkerAsync();
}  

EDIT, just showing install code so that you know I am using a backgroundworker to extract the zip. 编辑,仅显示安装代码,以便您知道我正在使用backgroundworker提取zip。

Common approach here is to download large file in small chunks and put them together on client after completion. 此处的常用方法是将大文件分成小块下载,并在完成后将它们放到客户端上。 Using this approach you can: 1. run few downloads in parallel and 2. in case of problem with network you don't have to download entire file again, just download incomplete chunks. 使用这种方法,您可以: 1. .并行运行少量下载,并且2.如果网络出现问题,您不必再次下载整个文件,只需下载不完整的块即可。

I faced a similar issue many years ago and created a subclass of WebClient that uses the DownloadProgressChanged event and a timer to abort downloads that get hung up and cancels the download smoother than the underlying internet transport layer does. 多年前,我遇到了类似的问题,并创建了WebClient的子类,该子类使用DownloadProgressChanged事件和计时器来中止挂起的下载,并比基础Internet传输层更平滑地取消下载。 The code also supports a callback to notify the calling code of progress. 该代码还支持回调以通知调用代码进度。 I found that sufficient to smoothly handle occasional hiccups downloading 1GB-ish files. 我发现足以顺利处理偶尔出现的下载1GB大小文件的问题。

The idea of breaking your download into multiple pieces also has merit. 将下载分为多个部分的想法也很有价值。 You could leverage a library such as 7-Zip to both create the chunks and piece them back together (many compression libraries have that feature; I'm personally most familiar with 7-Zip). 您可以利用7-Zip之类的库来创建块并将它们拼凑在一起(许多压缩库都具有该功能;我个人最熟悉7-Zip)。

Here's the code I wrote. 这是我编写的代码。 Feel free to use and/or modify in any way that's helpful to you. 随时使用对您有帮助的任何方式和/或进行修改。

public class JWebClient : WebClient, IDisposable
{
    public int Timeout { get; set; }
    public int TimeUntilFirstByte { get; set; }
    public int TimeBetweenProgressChanges { get; set; }

    public long PreviousBytesReceived { get; private set; }
    public long BytesNotNotified { get; private set; }

    public string Error { get; private set; }
    public bool HasError { get { return Error != null; } }

    private bool firstByteReceived = false;
    private bool success = true;
    private bool cancelDueToError = false;

    private EventWaitHandle asyncWait = new ManualResetEvent(false);
    private Timer abortTimer = null;
    private bool isDisposed = false;

    const long ONE_MB = 1024 * 1024;

    public delegate void PerMbHandler(long totalMb);

    public delegate void TaggedPerMbHandler(string tag, long totalMb);

    public event PerMbHandler NotifyMegabyteIncrement;

    public event TaggedPerMbHandler NotifyTaggedMegabyteIncrement;

    public JWebClient(int timeout = 60000, int timeUntilFirstByte = 30000, int timeBetweenProgressChanges = 15000)
    {
        this.Timeout = timeout;
        this.TimeUntilFirstByte = timeUntilFirstByte;
        this.TimeBetweenProgressChanges = timeBetweenProgressChanges;

        this.DownloadFileCompleted += new System.ComponentModel.AsyncCompletedEventHandler(MyWebClient_DownloadFileCompleted);
        this.DownloadProgressChanged += new DownloadProgressChangedEventHandler(MyWebClient_DownloadProgressChanged);

        abortTimer = new Timer(AbortDownload, null, TimeUntilFirstByte, System.Threading.Timeout.Infinite);
    }

    protected void OnNotifyMegabyteIncrement(long totalMb)
    {
        NotifyMegabyteIncrement?.Invoke(totalMb);
    }

    protected void OnNotifyTaggedMegabyteIncrement(string tag, long totalMb)
    {
        NotifyTaggedMegabyteIncrement?.Invoke(tag, totalMb);
    }

    void AbortDownload(object state)
    {
        cancelDueToError = true;
        this.CancelAsync();
        success = false;
        Error = firstByteReceived ? "Download aborted due to >" + TimeBetweenProgressChanges + "ms between progress change updates." : "No data was received in " + TimeUntilFirstByte + "ms";
        asyncWait.Set();
    }

    private object disposeLock = new object();

    void MyWebClient_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
    {
        if (cancelDueToError || isDisposed) return;

        long additionalBytesReceived = e.BytesReceived - PreviousBytesReceived;
        PreviousBytesReceived = e.BytesReceived;
        BytesNotNotified += additionalBytesReceived;

        if (BytesNotNotified > ONE_MB)
        {
            OnNotifyMegabyteIncrement(e.BytesReceived);
            OnNotifyTaggedMegabyteIncrement(Tag, e.BytesReceived);
            BytesNotNotified = 0;
        }
        firstByteReceived = true;
        try
        {
            lock (disposeLock)
            {
                if (!isDisposed) abortTimer.Change(TimeBetweenProgressChanges, System.Threading.Timeout.Infinite);
            }
        }
        catch (ObjectDisposedException) { } // Some strange timing issue causes this to throw now and then
    }

    public string Tag { get; private set; }

    public bool DownloadFileWithEvents(string url, string outputPath, string tag = null)
    {
        Tag = tag;

        asyncWait.Reset();
        Uri uri = new Uri(url);
        this.DownloadFileAsync(uri, outputPath);
        asyncWait.WaitOne();

        return success;
    }



    void MyWebClient_DownloadFileCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
    {
        if (e.Error != null) success = false;

        if (cancelDueToError || isDisposed) return;
        asyncWait.Set();
    }

    protected override WebRequest GetWebRequest(Uri address)
    {
        var result = base.GetWebRequest(address);
        result.Timeout = this.Timeout;
        return result;
    }

    void IDisposable.Dispose()
    {
        lock (disposeLock)
        {
            isDisposed = true;

            if (asyncWait != null) asyncWait.Dispose();
            if (abortTimer != null) abortTimer.Dispose();

            base.Dispose();
        }
    }
}

I fixed it using a combination of a BackgroundWorker and downloading in small chunks (following up mtkachenko's solution). 我结合了BackgroundWorker并一小段下载(遵循mtkachenko的解决方案)进行了修复。 It also checks if the length of the downloaded file is the same as the one on the server. 它还检查下载文件的长度是否与服务器上的长度相同。 Using that, it can continue the download where the connection was interupted. 使用它,它可以在中断连接的地方继续下载。


private void DownloadPatch(Patch patch){
    //using background worker now!
    downloadBackgroundWorker = new BackgroundWorker();
    downloadBackgroundWorker.DoWork += (sender, e) => DownloadFile_DoWork(sender, e, patch);
    downloadBackgroundWorker.ProgressChanged += DownloadFile_ProgressChanged;
    downloadBackgroundWorker.RunWorkerCompleted += DownloadFile_RunWorkerCompleted;
    downloadBackgroundWorker.WorkerReportsProgress = true;
    downloadBackgroundWorker.RunWorkerAsync();
}

private void DownloadFile_DoWork(object sender, DoWorkEventArgs e, Patch patch)
{
    string startupPath = Application.StartupPath;
    string downloadPath = Path.Combine(Application.StartupPath, patch.path);

    string path = ("http://www.dagovaxgames.com/api/downloads/" + patch.path);

    long iFileSize = 0;
    int iBufferSize = 1024;
    iBufferSize *= 1000;
    long iExistLen = 0;
    System.IO.FileStream saveFileStream;

    // Check if file exists. If true, then check amount of bytes
    if (System.IO.File.Exists(downloadPath))
    {
        System.IO.FileInfo fINfo =
           new System.IO.FileInfo(downloadPath);
        iExistLen = fINfo.Length;
    }
    if (iExistLen > 0)
        saveFileStream = new System.IO.FileStream(downloadPath,
          System.IO.FileMode.Append, System.IO.FileAccess.Write,
          System.IO.FileShare.ReadWrite);
    else
        saveFileStream = new System.IO.FileStream(downloadPath,
          System.IO.FileMode.Create, System.IO.FileAccess.Write,
          System.IO.FileShare.ReadWrite);

    System.Net.HttpWebRequest hwRq;
    System.Net.HttpWebResponse hwRes;
    hwRq = (System.Net.HttpWebRequest)System.Net.HttpWebRequest.Create(path);
    hwRq.AddRange((int)iExistLen);
    System.IO.Stream smRespStream;
    hwRes = (System.Net.HttpWebResponse)hwRq.GetResponse();
    smRespStream = hwRes.GetResponseStream();

    iFileSize = hwRes.ContentLength;

    //using webclient to receive file size
    WebClient webClient = new WebClient();
    webClient.OpenRead(path);
    long totalSizeBytes = Convert.ToInt64(webClient.ResponseHeaders["Content-Length"]);

    int iByteSize;
    byte[] downBuffer = new byte[iBufferSize];

    while ((iByteSize = smRespStream.Read(downBuffer, 0, downBuffer.Length)) > 0)
    {
        if (stopDownloadWorker == true)
        {
            autoDownloadReset.WaitOne();
        }

        saveFileStream.Write(downBuffer, 0, iByteSize);

        long downloadedBytes = new System.IO.FileInfo(downloadPath).Length;

        // Report progress, hint: sender is your worker
        int percentage = Convert.ToInt32(100.0 / totalSizeBytes * downloadedBytes);

        (sender as BackgroundWorker).ReportProgress(percentage, null);
    }
}

As you can see, I report the progress, so that I have a working progress bar as well. 如您所见,我报告了进度,因此我也有一个正常的进度栏。


private void DownloadFile_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    statusTextLabel.Text = "Downloading updates for version " + currentDownloadingPatch + " (" + e.ProgressPercentage + "%)";
    progressBar.Value = e.ProgressPercentage;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM