عد سرعة التحميل في التطبيق الخاص بك

قبل التاريخ


لديّ مشروع حيوان أليف صغير ومريح يسمح لك بتنزيل الملفات من الإنترنت. يتم تجميع الملفات معًا ولا يُظهر المستخدم كل ملف ، ولكن يتم تجميع بعض الملفات. وكانت عملية التنزيل بأكملها (وعرض هذه العملية) تعتمد اعتمادًا كبيرًا على البيانات. تم الحصول على البيانات أثناء الطيران ، أي يبدأ المستخدم في التنزيل ولا توجد معلومات حول مقدار ما يتعين عليك تنزيله في الواقع.


أصبح التنفيذ الساذج لبعض أنواع المعلومات على الأقل أمرًا بسيطًا - يتم عرض تقدم التنزيل كنسبة من عدد التنزيلات إلى العدد الإجمالي. لا توجد معلومات كثيرة للمستخدم - مجرد شريط زاحف ، لكن هذا أفضل من لا شيء ، وهو أفضل بشكل ملحوظ من آلية التحميل الشائعة حاليًا دون الإشارة إلى التقدم.


وبعد ذلك يظهر المستخدم مع مشكلة منطقية - في مجموعة كبيرة ، ليس من الواضح لماذا يزحف التقدم بالكاد - هل أحتاج إلى تنزيل الكثير من الملفات أو السرعة المنخفضة؟ كما ذكرت أعلاه - عدد الملفات غير معروف مقدما. لذلك ، قررت إضافة عداد السرعة.


تحليل


من الممارسات الجيدة رؤية أولئك الذين قاموا بالفعل بحل مشكلة مماثلة حتى لا يعيدوا اختراع العجلة. يقوم البرنامج المختلف بإغلاق هذه المهام المختلفة ، ولكن تبدو الشاشة كما هي:


أوتورنتDownloadMaster
أوتورنتDownloadMaster

النقطة الأساسية التي حددتها بنفسي هي أن العرض الأول للسرعة مطلوب في الوقت الحالي. ليس ما هي السرعة المتوسطة ، وليس السرعة ككل كانت متوسطة من اللحظة التي بدأت فيها ، أي ما هو هذا الرقم في الوقت الحالي. في الحقيقة ، هذا مهم عندما أصل إلى الكود - سأشرح ذلك بشكل منفصل.


لذلك ، نحن بحاجة إلى رقم بسيط مثل 10 MB/s أو شيء من هذا القبيل. كيف نحسبها؟


النظرية والتطبيق


استخدم تطبيق التنزيل الموجود HttpWebRequest وقررت عدم إعادة التنزيل مرة أخرى - لا تلمس آلية العمل.


لذلك ، فإن التطبيق الأولي دون أي حساب:


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

على مستوى واجهة برمجة التطبيقات هذه ، يمكنك فقط الاستجابة لتنزيل ملف كامل ، للمجموعات الصغيرة (أو حتى لملف واحد) ، لا يمكن بالفعل حساب السرعة. نتبع شفرة المصدر CopyToAsync ، انسخ المنطق البسيط من هناك:


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

الآن يمكننا الرد على كل المخزن المؤقت المعطى لنا عبر الشبكة.


أولاً ، ما الذي نفعله بدلاً من 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(); } } 

الشيء الوحيد المضاف حقا هو NetworkSpeed.AddInfo . والشيء الوحيد الذي ننقله هو عدد البايتات التي تم تنزيلها.


يبدو الرمز نفسه للتنزيل كالتالي:


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

خيار 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); 

الخيار ل HttpClient
  var httpClient = new HttpClient(); var content = await httpClient.GetStreamAsync(uri); var array = await content.GetBytesAsync(); 

حسنًا ، لقد تم حل نصف المشكلة - فنحن نعرف مقدار تنزيلنا. ننتقل إلى السرعة.


حسب ويكيبيديا :


معدل نقل البيانات - مقدار البيانات المنقولة لكل وحدة زمنية.

النهج الساذج الأول


لدينا حجم. يمكن أن يستغرق الوقت حرفيًا من بدء التشغيل والحصول على الفرق مع DateTime.Now . خذ وشارك؟
بالنسبة للأدوات المساعدة لوحدة التحكم مثل curl ، يكون ذلك ممكنًا ومن المنطقي.
ولكن إذا كان التطبيق الخاص بك أكثر تعقيدًا ، فسوف يؤدي زر الإيقاف المؤقت إلى تعقيد حياتك بشكل كبير.


قليلا عن وقفة
ربما أكون ساذجًا جدًا ، أو ربما السؤال ليس في الحقيقة بهذه البساطة - لكن التوقف المؤقت يجعلني أفكر باستمرار. يمكن أن تتوقف فترة التوقف أثناء التنزيل بثلاث طرق على الأقل:


  • يقطع تحميل الملف ، ابدأ مرة أخرى بعد
  • فقط لا تقم بتنزيل الملف أكثر ، نأمل أن يستمر الخادم بعد
  • تنزيل الملفات التي تم تشغيلها بالفعل ، وعدم تنزيل ملفات جديدة ، أو تنزيل ملفات جديدة بعد

منذ أن أدى أول اثنين إلى فقدان المعلومات التي تم تنزيلها بالفعل ، يمكنني استخدام الثالث.
أعلى قليلاً ، لاحظت أن هناك حاجة إلى السرعة على وجه التحديد في وقت ما. لذلك ، توقف وقفة هذا الأمر:


  • لا يمكنك حساب متوسط ​​السرعة بشكل صحيح ، فقط أخذ مستوى الصوت لفترة من الوقت
  • قد يكون للإيقاف المؤقت أسباب خارجية تؤدي إلى تغيير السرعة والقناة (إعادة الاتصال بشبكة الموفر ، والتحول إلى VPN ، وإنهاء uTorrent الذي يأخذ القناة بالكامل) ، مما سيؤدي إلى تغيير في السرعة الحقيقية
    في الواقع ، يقسم التوقف المؤقت أي مؤشرات إلى قبله وبعده. هذا لا يؤثر بشكل خاص على الكود أدناه ، فقط دقيقة من المعلومات الممتعة للتفكير فيها.

النهج الساذج الثاني


إضافة توقيت. سيأخذ الموقت في كل فترة زمنية جميع أحدث المعلومات حول وحدة التخزين التي تم تنزيلها وإعادة حساب مؤشر السرعة. وإذا قمت بتعيين جهاز ضبط الوقت في الثانية الواحدة ، فستكون جميع المعلومات التي يتم تلقيها في هذه المرحلة الثانية حول وحدة التخزين التي تم تنزيلها مساوية لسرعة هذه الثانية:


تطبيق فئة 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); } } 

بالمقارنة مع الخيار الأول ، يبدأ هذا التطبيق في الاستجابة لإيقاف مؤقت - تنخفض السرعة إلى 0 في الثانية التالية بعد وصول البيانات في الخارج.
ولكن ، هناك أيضا عيوب. نحن نعمل مع مخزن مؤقت يبلغ 80 كيلو بايت ، مما يعني أن التنزيل الذي بدأ في ثانية واحدة سيتم عرضه في اليوم التالي فقط. ومع تدفق كبير من التنزيلات المتوازية ، فإن أخطاء القياس هذه ستعرض أي شيء - كان لدي انتشار يصل إلى 30٪ من الأرقام الحقيقية. ربما لم أكن قد لاحظت ذلك ، ولكن بدا لي أن سرعة الوصول إلى أكثر من 100 ميجابت مشكوك فيها .


النهج الثالث


الخيار الثاني قريب بالفعل بدرجة كافية من الحقيقة ، بالإضافة إلى أن خطأه قد لوحظ أكثر في بداية التنزيل ، وليس طوال دورة الحياة.
لذلك ، فإن الحل البسيط هو أخذ الرقم في الثانية كمؤشر ، وليس المتوسط ​​خلال الثواني الثلاث الأخيرة. ثلاثة هنا هو ثابت السحر يقابل العين. من ناحية ، أردت عرضًا لطيفًا لنمو وتراجع السرعة ، من ناحية أخرى - بحيث كانت السرعة أقرب إلى الحقيقة.


التنفيذ معقد بعض الشيء ، لكن بشكل عام ، لا شيء مثل هذا:


تطبيق فئة 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); } } 

بضع نقاط:


  • في وقت التنفيذ ، لم أجد قائمة الانتظار النهائية مع تحديد عدد العناصر وأخذها على الإنترنت ، في الرمز أعلاه هو LimitedConcurrentQueue .
  • بدلاً من تطبيق INotifyPropertyChanged لسبب ما ، Action ، الاستخدام هو نفسه عمليًا ، لا أتذكر الأسباب. المنطق بسيط - المؤشر يتغير ، يحتاج المستخدمون إلى إخطارهم بهذا. يمكن أن يكون التطبيق أي ، حتى IObservable ، لمن هو أكثر ملاءمة.

وقراءة قليلا


يعطي API السرعة في البايتات ، من أجل سهولة قراءة واحدة (مأخوذة على الإنترنت)


محول
  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); } 

اسمحوا لي أن أذكركم بأن السرعة بالبايت ، أي في كل قناة بحجم 100 ميجابايت ، يجب ألا تصدر أكثر من 12.5 ميغابايت


كيف يبدو في النهاية مثل:


قم بتنزيل صورة أوبونتو
السرعة الحالية 904.5 كيلو بايت / ثانية
السرعة الحالية 1.8MB / ثانية
السرعة الحالية 2.9MB / ثانية
السرعة الحالية 3.2MB / ثانية
السرعة الحالية 2.9MB / ثانية
السرعة الحالية 2.8MB / ثانية
السرعة الحالية 3MB / ثانية
السرعة الحالية 3.1MB / ثانية
السرعة الحالية 3.2MB / ثانية
السرعة الحالية 3.3MB / ثانية
السرعة الحالية
السرعة الحالية 3.6MB / ثانية
السرعة الحالية 3.6MB / ثانية
السرعة الحالية 3.6MB / ثانية
...

حسنا ، عدة صور في وقت واحد
السرعة الحالية
السرعة الحالية 3.8MB / ثانية
السرعة الحالية 7.3MB / ثانية
السرعة الحالية 10MB / ثانية
السرعة الحالية 10.3MB / ثانية
السرعة الحالية 10MB / ثانية
السرعة الحالية 9.7MB / ثانية
السرعة الحالية 9.8MB / ثانية
السرعة الحالية 10.1MB / ثانية
السرعة الحالية 9.8MB / ثانية
السرعة الحالية 9.1MB / ثانية
السرعة الحالية 8.6MB / ثانية
السرعة الحالية 8.4MB / ثانية
...

استنتاج


كان من المثير للاهتمام التعامل مع مهمة تبدو عادية وهي سرعة العد. وعلى الرغم من أن الكود يعمل ويعطي بعض الأرقام ، إلا أنني أريد أن أستمع إلى النقاد - ما فاتني ، وكيف يمكنني أن أفعل بشكل أفضل ، ربما هناك بعض الحلول الجاهزة.


أريد أن أقول بفضل Stack Overflow باللغة الروسية وتحديداً VladD-exrabbit - على الرغم من أن هناك نصف الإجابة في سؤال جيد ، وأية تلميحات وأي مساعدة تدفعك دائمًا إلى الأمام.


أريد أن أذكركم بأن هذا مشروع للحيوانات الأليفة - ولهذا السبب فإن الفصل ثابت وواحد على الإطلاق ، وبالتالي فإن الدقة ليست في الحقيقة. أرى الكثير من الأشياء الصغيرة التي يمكن القيام بها بشكل أفضل ، ولكن ... هناك دائمًا شيء آخر يجب القيام به ، لذلك في الوقت الحالي أعتقد أن هذه هي السرعة وأعتقد أن هذا ليس خيارًا سيئًا.

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


All Articles