Menghitung kecepatan unduhan di aplikasi Anda

Latar belakang


Saya punya proyek hewan peliharaan kecil dan nyaman, yang memungkinkan Anda mengunduh file dari Internet. File dikelompokkan bersama dan pengguna tidak ditampilkan setiap file, tetapi beberapa pengelompokan. Dan seluruh proses pengunduhan (dan tampilan proses ini) sangat bergantung pada data. Data diperoleh dengan cepat, mis. pengguna mulai mengunduh dan tidak ada informasi seberapa banyak Anda harus mengunduh pada kenyataannya.


Implementasi yang naif dari setidaknya beberapa jenis informasi dibuat sederhana - kemajuan unduhan ditampilkan sebagai rasio jumlah yang diunduh dengan jumlah total. Tidak ada banyak informasi untuk pengguna - hanya setrip yang merayap, tetapi ini lebih baik daripada tidak sama sekali, dan itu jauh lebih baik daripada mekanisme pemuatan yang sedang populer saat ini tanpa menunjukkan kemajuan.


Dan kemudian seorang pengguna muncul dengan masalah logis - dalam kelompok besar tidak jelas mengapa kemajuan hampir tidak merayap - apakah saya perlu mengunduh banyak file atau kecepatan rendah? Seperti yang saya sebutkan di atas - jumlah file tidak diketahui sebelumnya. Karena itu, saya memutuskan untuk menambah penghitung kecepatan.


Analisis


Merupakan praktik yang baik untuk melihat mereka yang telah menyelesaikan masalah serupa agar tidak menemukan kembali roda. Perangkat lunak yang berbeda menutup tugas-tugas yang berbeda ini, tetapi tampilannya terlihat hampir sama:


uTorrentDownloadmaster
uTorrentDownloadmaster

Poin kunci yang telah saya identifikasi untuk diri saya adalah tampilan kecepatan pertama diperlukan pada saat ini. Bukan kecepatan apa yang rata-rata, bukan kecepatan apa secara keseluruhan adalah rata-rata sejak saat dimulai, yaitu seperti apa angka ini pada saat ini. Sebenarnya, ini penting ketika saya sampai ke kode - saya akan menjelaskannya secara terpisah.


Jadi, kita membutuhkan digit sederhana seperti 10 MB/s atau sesuatu seperti itu. Bagaimana kita menghitungnya?


Teori dan Praktek


Implementasi unduhan yang ada menggunakan HttpWebRequest dan saya memutuskan untuk tidak mengulangi unduhan itu sendiri - jangan menyentuh mekanisme kerja.


Jadi, implementasi awal tanpa perhitungan:


  var request = WebRequest.Create(uri); var response = await request.GetResponseAsync(); using (var ms = new MemoryStream()) { await response.GetResponseStream().CopyToAsync(ms); return ms.ToArray(); } 

Di tingkat API seperti itu, Anda hanya dapat menanggapi unduhan lengkap file, untuk grup kecil (atau bahkan untuk satu file), Anda tidak dapat benar-benar menghitung kecepatannya. Kami mengikuti kode sumber CopyToAsync , salin-tempel logika sederhana dari sana:


  byte[] buffer = new byte[bufferSize]; int bytesRead; while ((bytesRead = await ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0) { await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false); } 

Sekarang kita dapat menanggapi setiap buffer yang diberikan kepada kita melalui jaringan.


Jadi, pertama-tama, apa yang kita lakukan alih-alih kotak CopyToAsync:


  public static async Task<byte[]> GetBytesAsync(this Stream from) { using (var memory = new MemoryStream()) { byte[] buffer = new byte[81920]; int bytesRead; while ((bytesRead = await from.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false)) != 0) { await memory.WriteAsync(buffer, 0, bytesRead).ConfigureAwait(false); NetworkSpeed.AddInfo(bytesRead); } return memory.ToArray(); } } 

Satu-satunya hal yang benar-benar ditambahkan adalah NetworkSpeed.AddInfo . Dan satu-satunya hal yang kami kirimkan adalah jumlah byte yang diunduh.


Kode itu sendiri untuk mengunduh terlihat seperti ini:


  var request = WebRequest.Create(uri); var response = await request.GetResponseAsync(); var array = await response.GetResponseStream().GetBytesAsync(); 

Opsi untuk WebClient
  var client = new WebClient(); var lastRecorded = 0L; client.DownloadProgressChanged += (sender, eventArgs) => { NetworkSpeed.AddInfo(eventArgs.BytesReceived - lastRecorded); lastRecorded = eventArgs.BytesReceived; }; var array = await client.DownloadDataTaskAsync(uri); 

Opsi untuk HttpClient
  var httpClient = new HttpClient(); var content = await httpClient.GetStreamAsync(uri); var array = await content.GetBytesAsync(); 

Nah, setengah dari masalah telah diselesaikan - kami tahu berapa banyak yang kami unduh. Kami beralih ke kecepatan.


Menurut wikipedia :


Kecepatan transfer data - jumlah data yang dikirimkan per unit waktu.

Pendekatan naif pertama


Kami memiliki volume. Waktu dapat diambil secara harfiah dari startup dan dapatkan perbedaannya dengan DateTime.Now . Sekarang. Ambil dan bagikan?
Untuk utilitas konsol seperti curl, ini mungkin dan masuk akal.
Tetapi jika aplikasi Anda sedikit lebih rumit, maka tombol pause secara dramatis akan menyulitkan hidup Anda.


Sedikit tentang jeda
Mungkin saya sangat naif, atau mungkin pertanyaannya tidak begitu sederhana - tetapi jeda membuat saya berpikir terus-menerus. Jeda saat mengunduh dapat berperilaku setidaknya dalam tiga cara:


  • interupsi unggah file, mulai lagi setelah
  • jangan mengunduh file lebih lanjut, berharap server akan melanjutkan setelah
  • unduh file yang sudah dimulai, jangan unduh yang baru, unduh yang baru setelah

Karena dua yang pertama menyebabkan hilangnya informasi yang sudah diunduh, saya menggunakan yang ketiga.
Sedikit lebih tinggi, saya perhatikan bahwa kecepatan dibutuhkan tepat pada suatu titik waktu. Jadi, jeda mempersulit masalah ini:


  • Anda tidak dapat menghitung dengan benar apa kecepatan rata-rata itu, hanya mengambil volume untuk sementara waktu
  • Jeda mungkin memiliki alasan eksternal yang akan mengubah kecepatan dan saluran (menyambung kembali ke jaringan penyedia, beralih ke VPN, mengakhiri uTorrent yang mengambil seluruh saluran), yang akan mengarah pada perubahan kecepatan nyata.
    Bahkan, jeda membagi indikator menjadi sebelum dan sesudahnya. Ini tidak terlalu mempengaruhi kode di bawah ini, hanya satu menit informasi yang menyenangkan untuk dipikirkan.

Pendekatan naif kedua


Tambahkan penghitung waktu. Penghitung waktu setiap periode waktu akan mengambil semua informasi terbaru tentang volume yang diunduh dan menghitung ulang indikator kecepatan. Dan jika Anda mengatur timer per detik, maka semua informasi yang diterima untuk detik ini tentang volume yang diunduh akan sama dengan kecepatan untuk detik ini:


Seluruh implementasi kelas NetworkSpeed
  public class NetworkSpeed { public static double TotalSpeed { get { return totalSpeed; } } private static double totalSpeed = 0; private const uint TimerInterval = 1000; private static Timer speedTimer = new Timer(state => { var now = 0L; while (ReceivedStorage.TryDequeue(out var added)) now += added; totalSpeed = now; }, null, 0, TimerInterval); private static readonly ConcurrentQueue<long> ReceivedStorage = new ConcurrentQueue<long>(); public static void Clear() { while (ReceivedStorage.TryDequeue(out _)) { } totalSpeed = 0; } public static void AddInfo(long received) { ReceivedStorage.Enqueue(received); } } 

Dibandingkan dengan opsi pertama, implementasi semacam itu mulai merespons jeda - kecepatan turun ke 0 di detik berikutnya setelah data di luar tiba.
Tapi, ada juga kekurangannya. Kami bekerja dengan penyangga 80kb, yang berarti unduhan yang dimulai dalam satu detik hanya akan ditampilkan di yang berikutnya. Dan dengan aliran besar unduhan paralel, kesalahan pengukuran seperti itu akan menampilkan apa saja - Saya memiliki spread hingga 30% dari angka sebenarnya. Saya mungkin tidak memperhatikan, tetapi melebihi 100 Mbit tampak terlalu mencurigakan .


Pendekatan ketiga


Opsi kedua sudah cukup dekat dengan kebenaran, ditambah kesalahannya diamati lebih banyak pada awal pengunduhan, dan tidak sepanjang siklus hidup.
Oleh karena itu, solusi sederhana adalah dengan mengambil bukan indikator angka per detik, tetapi rata-rata selama tiga detik terakhir. Tiga di sini adalah konstanta sihir yang cocok dengan mata. Di satu sisi, saya ingin tampilan yang menyenangkan dari pertumbuhan dan penurunan kecepatan, di sisi lain - sehingga kecepatan lebih dekat dengan kebenaran.


Implementasinya agak rumit, tetapi secara umum, tidak seperti ini:


Seluruh implementasi kelas NetworkSpeed
  public class NetworkSpeed { public static double TotalSpeed { get { return totalSpeed; } } private static double totalSpeed = 0; private const uint Seconds = 3; private const uint TimerInterval = 1000; private static Timer speedTimer = new Timer(state => { var now = 0L; while (ReceivedStorage.TryDequeue(out var added)) now += added; LastSpeeds.Enqueue(now); totalSpeed = LastSpeeds.Average(); OnUpdated(totalSpeed); }, null, 0, TimerInterval); private static readonly LimitedConcurrentQueue<double> LastSpeeds = new LimitedConcurrentQueue<double>(Seconds); private static readonly ConcurrentQueue<long> ReceivedStorage = new ConcurrentQueue<long>(); public static void Clear() { while (ReceivedStorage.TryDequeue(out _)) { } while (LastSpeeds.TryDequeue(out _)) { } totalSpeed = 0; } public static void AddInfo(long received) { ReceivedStorage.Enqueue(received); } public static event Action<double> Updated; private class LimitedConcurrentQueue<T> : ConcurrentQueue<T> { public uint Limit { get; } public new void Enqueue(T item) { while (Count >= Limit) TryDequeue(out _); base.Enqueue(item); } public LimitedConcurrentQueue(uint limit) { Limit = limit; } } private static void OnUpdated(double obj) { Updated?.Invoke(obj); } } 

Beberapa poin:


  • pada saat implementasi, saya tidak menemukan antrian jadi dengan batas jumlah elemen dan membawanya di Internet, dalam kode di atas itu adalah LimitedConcurrentQueue .
  • alih-alih menerapkan INotifyPropertyChanged karena beberapa alasan, Action , penggunaannya praktis sama, saya tidak ingat alasannya. Logikanya sederhana - indikatornya berubah, pengguna perlu diberitahu tentang hal ini. Implementasinya dapat berupa apa saja, bahkan IObservable , kepada siapa lebih nyaman.

Dan sedikit mudah dibaca


API memberikan kecepatan dalam byte, untuk keterbacaan yang sederhana (diambil di Internet) berguna


konverter
  public static string HumanizeByteSize(this long byteCount) { string[] suf = { "B", "KB", "MB", "GB", "TB", "PB", "EB" }; //Longs run out around EB if (byteCount == 0) return "0" + suf[0]; long bytes = Math.Abs(byteCount); int place = Convert.ToInt32(Math.Floor(Math.Log(bytes, 1024))); double num = Math.Round(bytes / Math.Pow(1024, place), 1); return Math.Sign(byteCount) * num + suf[place]; } public static string HumanizeByteSize(this double byteCount) { if (double.IsNaN(byteCount) || double.IsInfinity(byteCount) || byteCount == 0) return string.Empty; return HumanizeByteSize((long)byteCount); } 

Biarkan saya mengingatkan Anda bahwa kecepatan dalam byte, yaitu per 100mbit saluran harus mengeluarkan tidak lebih dari 12.5MB.


Bagaimana akhirnya terlihat seperti:


Unduh gambar ubuntu
Kecepatan saat ini 904.5KB / s
Kecepatan saat ini 1.8MB / s
Kecepatan saat ini 2.9MB / s
Kecepatan saat ini 3.2MB / s
Kecepatan saat ini 2.9MB / s
Kecepatan saat ini 2.8MB / s
Kecepatan saat ini 3MB / s
Kecepatan saat ini 3.1MB / s
Kecepatan saat ini 3.2MB / s
Kecepatan saat ini 3.3MB / s
Kecepatan saat ini 3,5MB / s
Kecepatan saat ini 3.6MB / s
Kecepatan saat ini 3.6MB / s
Kecepatan saat ini 3.6MB / s
...

Nah, beberapa gambar sekaligus
Kecepatan saat ini 1,2MB / s
Kecepatan saat ini 3,8MB / s
Kecepatan saat ini 7.3MB / s
Kecepatan saat ini 10MB / s
Kecepatan saat ini 10.3MB / s
Kecepatan saat ini 10MB / s
Kecepatan saat ini 9,7MB / s
Kecepatan saat ini 9,8MB / s
Kecepatan saat ini 10.1MB / s
Kecepatan saat ini 9,8MB / s
Kecepatan saat ini 9,1MB / s
Kecepatan saat ini 8.6MB / s
Kecepatan saat ini 8,4MB / s
...

Kesimpulan


Sangat menarik untuk berurusan dengan tugas yang tampaknya biasa menghitung kecepatan. Dan meskipun kodenya bekerja dan memberikan beberapa angka, saya ingin mendengarkan kritik - apa yang saya lewatkan, bagaimana saya bisa melakukan yang lebih baik, mungkin ada beberapa solusi yang sudah jadi.


Saya ingin mengucapkan terima kasih kepada Stack Overflow dalam bahasa Rusia dan khususnya VladD-exrabbit - meskipun ada setengah jawaban dalam pertanyaan yang bagus, setiap petunjuk dan bantuan selalu membuat Anda maju.


Saya ingin mengingatkan Anda bahwa ini adalah proyek hewan peliharaan - karena itulah kelasnya statis dan sama sekali, sehingga akurasinya tidak benar-benar. Saya melihat banyak hal kecil yang bisa dilakukan lebih baik, tapi ... selalu ada hal lain yang harus dilakukan, jadi untuk sekarang saya pikir itu kecepatannya dan saya pikir ini bukan pilihan yang buruk.

Source: https://habr.com/ru/post/id465669/


All Articles