准确测量webclient的下载速度

时间:2013-12-27 04:21:51

标签: c#

我正在使用C#和WebClient类。

这是我正在使用的代码,受到SO上另一篇文章的启发。此代码适用于大型文件,它可以准确显示下载速度。但是,现在有下载单个文件的限制,其中许多文件很小,小到0.5-5 MB。这导致速度反击猛涨,通常达到数十万KBps。我不知道还有什么可以打击这个。我添加了第二个进度条,显示单个文件下载,这有助于改善图像,但下载速度计数器应该是固定的。是否有一个不同的类可以解决这个问题?

此代码中的WebClient在其他地方正确处理。        私人类NetSpeedCounter         {             private double [] DataPoints;

        private DateTime LastUpdate;
        private int NumCounts = 0;
        private int PrevBytes = 0;

        public double Speed { get; private set; }

        public NetSpeedCounter(WebClient webClient, int maxPoints = 10)
        {
            DataPoints = new double[maxPoints];

            Array.Clear(DataPoints, 0, DataPoints.Length);
            webClient.DownloadProgressChanged += (sender, e) =>
            {
                var msElapsed = DateTime.Now - LastUpdate;

                int curBytes = (int)(e.BytesReceived - PrevBytes);
                PrevBytes = (int)e.BytesReceived;

                double dataPoint = ((double)curBytes) / msElapsed.TotalSeconds;
                DataPoints[NumCounts++ % maxPoints] = dataPoint;

                Speed = DataPoints.Average();
            };
        }

        public void Reset()
        {
            PrevBytes = 0;
            LastUpdate = DateTime.Now;
        }
    }

我使用此代码下载文件,然后通过调用DownloadFileAsync启动该代码。这段代码只是异步地将它们一个接一个地下载到一个链中。

这是设置开始下载         队列记录Q =新队列(文件);

    progressBar.Value = 0;
    progressBar.Maximum = recordQ.Count;

    UpdateStatusText("Downloading " + recordQ.Count + " files");

    var record = recordQ.Dequeue();
    speedUpdater.Start();
    CheckAndCreate(record.AbsolutePath);

添加事件处理程序

    wc.DownloadFileCompleted += (sender, e) =>
    {
    var nr = recordQ.Dequeue();
    CheckAndCreate(nr.AbsolutePath);
    this.Invoke((MethodInvoker)delegate
    {
        UpdateStatusText("Downloading " + recordQ.Count + " files", lblStatusR.Text);
    });
    counter.Reset();
    // download the next one
    wc.DownloadFileAsync(nr.DownloadPath, nr.AbsolutePath);
 }
 counter.Start();
 wc.DownloadFileAsync(record.DownloadPath, record.AbsolutePath);

这最后一个电话是关闭所有事情的。

2 个答案:

答案 0 :(得分:2)

对于频繁记录时间跨度的某些情况,

DateTime.Now不够准确(Eric Lippert提到它们的精度为30 ms here),因为DateTime.Now将返回先前使用的DateTime。现在连续快速召唤。这可能会导致速度计数器出现差异,因为下载快速完成后会出现不准确的增加。我建议您使用StopWatch API。

修改

我已根据您的代码创建了以下测试Winforms应用程序,该代码适用于小文件。我在内联网上获得了一个合理的200 kbps的5个文件,每个文件大约2MB。只要确保你在正确的地方叫秒表课程。 要复制,请创建一个winforms应用程序,创建Id lblSpeed,lblStatus,lblFile 的3个标签,然后复制\粘贴代码并将下面的URI重命名为要测试的文件。

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
  public partial class Form1 : Form
  {
    Queue<Record> recordQ;
    WebClient wc;
    NetSpeedCounter counter;
    //We store downloaded files in C:\TestDir (hardcoded in the Record class below)
    public Form1()
    {
      InitializeComponent();
      recordQ = new Queue<Record>();
      //replace the URI string below. Nothing else to replace.
      //recordQ.Enqueue(new Record(@"URI1", "SQLtraining.exe"));
      //recordQ.Enqueue(new Record(@"URI2", "Project Mgmt.pptx"));

      //first uri to process. Second param is the file name that we store.
      Record record = new Record(@"URI0","Agile.pptx"); // replace the URI

      //Initialize a webclient and download the first record
      using (wc = new WebClient())
      {
        counter = new NetSpeedCounter(wc);
        wc.DownloadFileCompleted += (sender, e) =>
        {
          if (recordQ.Count == 0)
          {
            UpdateStatusText("Done");
            return;
          }
          var nr = recordQ.Dequeue();
          //just create directory. the code uses the same directory
          CheckAndCreate(nr.Directory);
          //need not even use invoke here. Just a plain method call will suffice.
          this.Invoke((MethodInvoker)delegate
          {
              UpdateStatusText("Left to process: " + recordQ.Count + " files");
          });
          counter.Reset();
          counter.Start();
          //continue with rest of records
          wc.DownloadFileAsync(nr.DownloadPath, nr.GetFullPath());
          this.lblFile.Text = nr.DownloadPath.OriginalString;
        };
        //just update speed in UI
        wc.DownloadProgressChanged += wc_DownloadProgressChanged;
        counter.Start();
        //display URI we are downloading
        this.lblFile.Text = record.DownloadPath.OriginalString;
        //start first download
        wc.DownloadFileAsync(record.DownloadPath, record.GetFullPath());
      }

    }

    void wc_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
    {
      this.lblSpeed.Text = counter.Speed.ToString();
    }
    public void UpdateStatusText(string msg)
    {
      this.lblStatus.Text = msg;
    }
    public void CheckAndCreate(string absPath)
    {
      if (!Directory.Exists(absPath))
        Directory.CreateDirectory(absPath);
    }   

  }
  public class NetSpeedCounter
  {
    private int NumCounts = 0;
    private int PrevBytes = 0;
    private Stopwatch stopwatch;
    public double Speed { get; private set; }
    double[] DataPoints;
    public NetSpeedCounter(WebClient webClient, int maxPoints = 10)
    {
      DataPoints = new double[maxPoints];
      stopwatch = new Stopwatch();
      Array.Clear(DataPoints, 0, DataPoints.Length);
      webClient.DownloadProgressChanged += (sender, e) =>
      {
        var msElapsed = DateTime.Now - LastUpdate;

        stopwatch.Stop();
        int curBytes = (int)(e.BytesReceived - PrevBytes);
        PrevBytes = (int)e.BytesReceived;
        //record in kbps
        double dataPoint = (double)curBytes / (stopwatch.ElapsedMilliseconds); 
        DataPoints[NumCounts++ % maxPoints] = dataPoint;
        //protect NumCount from overflow
        if (NumCounts == Int32.MaxValue)
          NumCounts = 0;

        Speed = DataPoints.Average();
        stopwatch.Start();
      };
    }

    public void Start()
    {
      stopwatch.Start();
    }

    public void Reset()
    {
        PrevBytes = 0;            
        stopwatch.Reset();
    }
  }
  public class Record
  {
    public string Directory;
    public string File;
    public Uri DownloadPath;
    public Record(string uriPath, string fileOutputName)
    {
      this.Directory = @"C:\TestDir\";
      this.DownloadPath = new Uri(uriPath);
      this.File = fileOutputName;
    }
    public string GetFullPath()
    {
      return this.Directory + this.File;
    }
  }  

}

答案 1 :(得分:0)

我想出了一种不同的做法。

我现在递增一个具有字节数的计数器,而不是为每个ProgressChanged事件添加数据点。此计数器保留在多个文件中,然后我只使用计时器获取数据点,并使用System.Diagnostic.Stopwatch

获取时间。

效果非常好,准确性要好得多,我强烈建议这样做。

这是一些代码

    class NetSpeedCounter
    {
        private Stopwatch watch;
        private long NumCounts = 0;
        private int PrevBytes = 0;
        private double[] DataPoints;

        private long CurrentBytesReceived = 0;

        public double Speed { get; private set; }

        private System.Timers.Timer ticker = new System.Timers.Timer(100);

        public NetSpeedCounter(WebClient webClient, int maxPoints = 5)
        {
            watch = new System.Diagnostics.Stopwatch();

            DataPoints = new double[maxPoints];

            webClient.DownloadProgressChanged += (sender, e) =>
            {
                int curBytes = (int)(e.BytesReceived - PrevBytes);
                if (curBytes < 0)
                    curBytes = (int)e.BytesReceived;

                CurrentBytesReceived += curBytes;

                PrevBytes = (int)e.BytesReceived;
            };

            ticker.Elapsed += (sender, e) =>
                {
                    double dataPoint = (double)CurrentBytesReceived / watch.ElapsedMilliseconds;
                    DataPoints[NumCounts++ % maxPoints] = dataPoint;
                    Speed = DataPoints.Average();
                    CurrentBytesReceived = 0;
                    watch.Restart();
                };
        }

        public void Stop()
        {
            watch.Stop();
            ticker.Stop();
        }

        public void Start()
        {
            watch.Start();
            ticker.Start();
        }

        public void Reset()
        {
            CurrentBytesReceived = 0;
            PrevBytes = 0;
            watch.Restart();
            ticker.Start();
        }
    }