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

时间:2019-07-16 11:13:36

标签: c#

我用C#创建了一个Windows窗体应用程序,我的游戏修改用户可以用来自动下载更新。

我所说的“启动器”使用WebClient下载更新。但是该mod的第一个发行版非常大(压缩了2.7 GB)。启动器非常适合我和大多数用户,但是对于某些用户而言,解压缩zip文件会记录错误,表明该文件已损坏且无法读取。

我已经在堆栈中搜索,由于互联网连接问题,文件可能已损坏或被截断。但是,我该如何构建解决该问题的方法?

//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();
}  

编辑,仅显示安装代码,以便您知道我正在使用backgroundworker提取zip。

3 个答案:

答案 0 :(得分:0)

这里的常用方法是将大文件下载成小块,并在完成后将它们放到客户端上。使用这种方法,您可以:1.并行运行少量下载,2.可以在网络出现问题时不必再次下载整个文件,而只需下载不完整的块即可。

答案 1 :(得分:0)

许多年前,我遇到了类似的问题,并创建了WebClient的子类,该子类使用DownloadProgressChanged事件和计时器来中止挂起的下载,并比基础Internet传输层更平滑地取消下载。该代码还支持回调以通知调用代码进度。我发现足以平稳地处理偶尔出现的下载1GB大小文件的问题。

将下载分成多个部分的想法也很有价值。您可以利用7-Zip之类的库来创建块并将它们重新组合在一起(许多压缩库都具有该功能;我个人最熟悉7-Zip)。

这是我编写的代码。随时以对您有帮助的任何方式使用和/或修改。

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

答案 2 :(得分:0)

我使用BackgroundWorker的组合并一小段下载(按照mtkachenko的解决方案)进行了修复。它还检查下载文件的长度是否与服务器上的长度相同。使用它,它可以在中断连接的地方继续下载。


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

如您所见,我报告了进度,因此我也有一个正常工作的进度条。


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