繁体   English   中英

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

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

我用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。

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

多年前,我遇到了类似的问题,并创建了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();
        }
    }
}

我结合了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;
}

暂无
暂无

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

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