مراقب حالة OpenVPN بلغة C#: راقب الجلسة لا بطاقة الشبكة
NetworkAvailabilityChanged ليس حدث VPN. اقرأ تدفق الحالة من العميل المالك، واحمِ نفسك من الفحوصات القديمة، وانشر الجاهزية لكل اتصال.
يستمر تطبيقك في العمل بينما يموت النفق. لا تزال شبكة Wi-Fi تعمل، ولا يزال الإنترنت يستجيب، وأصبحت واجهة API الخاصة التي يعتمد عليها التطبيق غير قابلة للوصول. حدث .NET الذي يلجأ إليه الناس عادة هنا، NetworkAvailabilityChanged، لا يصف هذا الوضع على الإطلاق. فهو يبلّغ عن تغيّر في الحالة الإجمالية للجهاز، ويمكن أن تظل تلك الحالة صحيحة عبر قطع اتصال VPN وإعادة توصيله. على المراقب أن يراقب العميل الذي يملك النفق، لا ملخص مكدس الشبكة عنه.
التوفر قيمة إجمالية، لا نفقك
NetworkAvailabilityChanged يُطلق عندما يتغيّر جواب التوفر الإجمالي للجهاز، وذلك الجواب يحسب أي واجهة قائمة غير loopback أو نفق.
انقطاع VPN بينما Wi-Fi قائمة لا يغيّر شيئًا مما يقيسه، ولهذا يمكن أن يظل الحدث صامتًا طوال العطل نفسه الذي يهمك.
تدفق الحالة ملك العميل الذي يملك النفق
حدّد أي مكوّن يملك الاتصال: واجهة OpenVPN الرسومية أو خدمتها، أو OpenVPN Connect، أو عملية بدأها تطبيقك ويتحكم بها. هؤلاء ملاك مختلفون بواجهات مدعومة مختلفة، وعلى المراقب استخدام الواجهة التي تطابق النشر. وبالنسبة لعملية OpenVPN المرخّص لك بالتكامل معها، تُبلّغ واجهة الإدارة عن الحالة الحالية وسجل وإشعارات فورية. حلّل الرسائل المهيكلة الموثّقة بدلًا من البحث في عنوان واجهة رسومية مترجمة عن كلمة connected.
قناة الإدارة سطح تحكم ذو امتيازات، فلا تفتح مستمعًا جديدًا ولا تنازع الواجهة الرسومية على قناة تملكها بالفعل.
حرّاس الأجيال يوقفون النتائج القديمة
كل رصد ينتمي إلى محاولة اتصال محددة. زد عدّاد الجيل عند بدء محاولة جديدة، وعند فقدان القناة، وعندما يبلّغ العميل عن الخروج.
لنأخذ فحصًا بدأ أثناء الاتصال A. يسقط النفق، ويبدأ الاتصال B، ثم تصل بعد ذلك نتيجة نجاح HTTP المتأخرة لـ A. بدون حارس، يجعل ذلك الناتج القديم العرض أخضر بينما لا يزال B يتحقق. الإلغاء يقلل العمل المهدر، وفحص الجيل هو ما يمنع اكتمالًا عتيقًا من أن يصبح الحالة الحالية:
var generation = _generation;
var reachable = await ProbeAsync(token);
if (generation != _generation) return;
Publish(reachable);
طبّق القاعدة نفسها حول إرسال واجهة المستخدم، لأن التحديث المُرسل قد يصل بعد أن يكون الجيل قد تحرّك.
التهيئة وإعادة الاتصال دورة حياة واحدة
تعامل مع أول اتصال وإعادة الاتصال وفقدان القناة كدورة حياة واحدة بمجموعة قواعد واحدة:
- اقرأ الحالة الحالية واشترك في الإشعارات في خطوة واحدة حيث تسمح الواجهة بذلك.
- وسم كل رصد بهوية الاتصال والجيل.
- أبطل الجاهزية عند إعادة الاتصال أو الخروج أو فقدان قناة الرصد.
- افحص المورد الخاص فقط بعد أن يبلّغ المالك عن اكتمال التهيئة.
- انشر فقط النتائج التي ما زالت تنتمي إلى الجيل الحالي.
فقدان القناة يعني مجهولًا، لا منفصلًا. الإبلاغ عن المجهول أصدق من الإبلاغ عن انقطاع لا تستطيع إثباته، وهو يمنع منطق إعادة المحاولة من التصرف بناءً على تخمين.
بلّغ عن الأدلة، لا عن قيمة منطقية
أعد حالة الجلسة المسمّاة وجاهزية المورد كقيمتين منفصلتين، تحمل كل منهما عمرًا. عندئذ يمكن للمستهلك الذي عليه أن يقرر شيئًا أن يرى أن آخر فحص ناجح عمره أربعون ثانية، وهي معلومة مختلفة عن معلومة حديثة.
البحث عن عنوان IP العام ليس دليلًا على أن واجهة API خاصة استخدمت المسار المطلوب، فلا تعرضه كما لو كان كذلك.
أبقِ الإنفاذ خارج المراقب
يعمل رد النداء بعد وقوع الحدث. ولا يمكنه ضمان ألا تكون أي حزمة قد غادرت الجهاز بين الحدث والمعالج. إذا كان التصميم يتطلب الحجب، فعلى سياسة الشبكة توفيره، ومهمة المراقب شرح الأدلة لمناديه. خلط الاثنين ينتج كودًا يعتقد أنه جدار حماية لأنه يحمل قيمة منطقية. أبقِ طبقة الإنفاذ مملة وقابلة للاختبار، ودع المراقب يبقى قارئًا.
حينها يصبح سؤال واجهة المستخدم بسيطًا: اعرض ما هو معروف، مع عمره، وقل مجهول حين يكون كذلك.