Pixockets: كيف كتبنا مكتبة الشبكة الخاصة بنا لخادم الألعاب



مرحبا! متصل ستانيسلاف يابلونسكي ، المطور الرئيسي لخادم Pixonic.

عندما جئت لأول مرة إلى Pixonic ، كانت خوادم الألعاب لدينا تطبيقات تستند إلى Photon Realtime SDK : إطار متعدد الوظائف ولكنه ثقيل جدًا. يبدو أن هذا الحل هو تبسيط العمل مع الخادم. لذلك كان - حتى نقطة معينة.

ربطنا Photon Realtime بنفسه من خلال الاضطرار إلى استخدامه لتبادل البيانات بين اللاعبين والخادم - وربطه أيضًا بنظام Windows ، لأنه لا يعمل إلا عليه. هذا فرض قيود علينا سواء من وجهة نظر وقت التشغيل (وقت التشغيل): كان من المستحيل تغيير العديد من الإعدادات الهامة للجهاز الظاهري. NET ونظام التشغيل. لقد اعتدنا على العمل مع خوادم Linux ، وليس Windows. بالإضافة إلى ذلك ، فإنهم يكلفوننا أقل.

أيضا ، استخدام الفوتون ضرب الأداء على كل من الخادم والعميل ، وعند إنشاء ملف تعريف ، تم تشكيل تحميل لائق على جامع القمامة وكمية كبيرة من الملاكمة / إلغاء التعبئة.

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

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

ما جاء منها - اقرأ.

أيديولوجية المكتبة


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

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

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

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

هندسة معمارية


في مكتبتنا نستخدم مقبس ذو طبقتين:

  • الطبقة الأولى مسؤولة عن العمل مع مكالمات النظام وتوفر واجهة برمجة تطبيقات أكثر ملاءمة للمستوى التالي ؛
  • تعمل الطبقة الثانية مباشرة مع الجلسة ، وتجزئة / تجميع الحزم ، وإعادة توجيهها ، إلخ.



وتنقسم فئة العمل مع الاتصال بدورها إلى مستويين:

  • المستوى الأدنى (SockBase) مسؤول عن إرسال واستقبال البيانات عبر UDP. إنه غلاف رقيق فوق كائن نظام مأخذ التوصيل.
  • يوفر المستوى العلوي (SmartSock) وظائف إضافية عبر UDP. حزم القطع واللصق ، وإعادة توجيه البيانات التي لم تصل ، ورفض التكرارات - كل هذا هو مجال مسؤوليته.

ينقسم المستوى الأدنى إلى فئتين: BareSock و ThreadSock.

  • يعمل BareSock في نفس مؤشر الترابط حيث نشأت المكالمة ، وأرسلت واستقبلت البيانات في وضع عدم الحظر.
  • يضع ThreadSock الحزم في طوابير وبالتالي ينشئ مؤشرات ترابط منفصلة لإرسال البيانات وتلقيها. عند الوصول إليها ، هناك عملية واحدة فقط: إضافة أو إزالة البيانات من قائمة الانتظار.

غالبًا ما يُستخدم BareSock للعمل مع العميل ThreadSock - مع الخادم.

ميزات العمل


لقد كتبت أيضًا نوعين من المقابس ذات المستوى المنخفض:

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

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

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



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

بنية رزمة البيانات


يتم إرسال البيانات الموجودة بالمكتبة على النحو التالي:



في بداية الحزمة يوجد الرأس:

  • يبدأ بحجم الحزمة ، والذي يقتصر بدوره على 64 كيلو بايت.
  • ويتبع الحجم بايت مع الأعلام. يعتمد تفسير بقية العنوان على توفرها.
  • التالي هو معرّف الجلسة أو الاتصال.

مع العلامات المناسبة ، نحصل على:

  • إذا تم تعيين العلم برقم الحزمة بدوره ، يتم إرسال رقم الحزمة بعد معرف الجلسة.
  • بعده - أيضًا في حالة مجموعة العلم - عدد الحزم المؤكدة وأرقامها.

في نهاية الرأس توجد معلومات حول الجزء:

  • معرف تسلسل الأجزاء ، وهو أمر ضروري من أجل التمييز بين أجزاء الرسائل المختلفة ؛
  • رقم تسلسل الجزء ؛
  • إجمالي عدد الأجزاء في الرسالة.

تتطلب معلومات حول الجزء أيضًا تعيين العلم المقابل.

المكتبة مكتوبة. ماذا بعد؟


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

في الإصدار الأول من Pixockets ، لم يكن الأمر كذلك: لم يكن العميل بحاجة إلى استدعاء طريقة الاتصال (المضيف ، المنفذ) - فقد بدأ للتو في إرسال البيانات إلى عنوان ومنفذ معروفين. ثم دعا الخادم طريقة الاستماع (المنفذ) وبدأ في استقبال البيانات من عنوان معين. تم تهيئة بيانات الجلسة عند استلام / إرسال الحزمة.

الآن ، لإنشاء اتصال ، أصبحت "مصافحة" ضرورية - تبادل الحزم المشكلة بشكل خاص - ويجب على العميل الاتصال Connect.

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

اختبارات


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

بعد أن أثبتت تطبيقات الاختبار كفاءة مكتبتنا ، بدأنا في مقارنتها بالمكتبات الأخرى: مع برنامجنا الفوتوني Realtime القديم ومع مكتبة UDP LiteNetLib 0.7.

لقد اختبرنا نسخة مبسطة من خادم ألعاب يجمع ببساطة المدخلات من اللاعبين ويرسل النتيجة "الملصقة". أخذنا 500 لاعب في غرف 6 أشخاص ، معدل التحديث 30 مرة في الثانية.



تبين أن الحمل على جامع القمامة واستهلاك المعالج أقل في حالة Pixockets ، وكذلك النسبة المئوية للحزم المفقودة - على ما يبدو بسبب حقيقة أننا ، على عكس إصدارات UDP الأخرى ، لا نتجاهل الحزم المتأخرة.

بعد تلقينا تأكيدًا لميزة الحل الذي نقدمه في الاختبارات التركيبية ، كانت الخطوة التالية هي تشغيل المكتبة في مشروع حقيقي.

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

لبعض الوقت ، لعب العملاء في وقت واحد على كلا البروتوكولين ، وفي ذلك الوقت قمنا بجمع إحصائيات حول كيفية عملهم. في نهاية جمع الإحصاءات ، اتضح أن النتائج لا تختلف عن الاختبارات الاصطناعية: فقد انخفض الحمل على جامع القمامة والمعالج ، وفقدان الحزمة أيضًا. في نفس الوقت ، أصبح ping أقل قليلاً. لذلك ، تم بالفعل إصدار الإصدار التالي من اللعبة بالكامل على Pixockets دون استخدام Photon Realtime SDK.



خطط مستقبلية


نريد الآن تنفيذ الميزات التالية في المكتبة:

  • اتصال مبسط: الآن لا يعمل بشكل مثالي ، وبعد الاتصال Connect على العميل ، تحتاج إلى الاتصال بالقراءة حتى تتغير حالة الاتصال ؛
  • إغلاق صريح: في الوقت الحالي ، يحدث الإغلاق على الجانب الآخر فقط بواسطة المؤقت ؛
  • الأصوات المدمجة للحفاظ على الاتصال ؛
  • التحديد التلقائي لحجم الإطار الأمثل (يتم الآن استخدام ثابت فقط).

يمكنك عرض والمشاركة في تطوير Pixockets الإضافي على عنوان المستودع.

All Articles