كيف يعمل بروتوكول مخطط بيانات المستخدم (UDP). بروتوكول UDP

07.10.2023

مجموعة من بروتوكولات الشبكة الخاصة بالإنترنت. باستخدام UDP، يمكن لتطبيقات الكمبيوتر إرسال رسائل (تسمى في هذه الحالة مخططات البيانات) إلى مضيفين آخرين عبر شبكة IP دون الحاجة إلى اتصال مسبق لإنشاء قنوات نقل خاصة أو مسارات بيانات. تم تطوير البروتوكول بواسطة David P. Reed في عام 1980 وتم تعريفه رسميًا في RFC 768.

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

تعد طبيعة UDP كبروتوكول عديم الحالة مفيدة أيضًا للخوادم التي تستجيب للطلبات الصغيرة من عدد كبير من العملاء، مثل DNS وتطبيقات الوسائط المتدفقة مثل IPTV وVoice over IP وبروتوكولات نفق IP والعديد من الألعاب عبر الإنترنت.

يوتيوب الموسوعي

    1 / 5

    ✪ المنافذ وإعادة التوجيه/فتح المنافذ. تعليمات وشروحات في متناول يدك!

  • ترجمات

منافذ الخدمة

لا يوفر UDP أي ضمانات لتسليم الرسائل إلى البروتوكول الأعلى ولا يخزن حالة الرسائل المرسلة. لهذا السبب، يطلق على UDP أحيانًا اسم بروتوكول مخطط البيانات غير الموثوق.

تحقق من المبلغ

يتم استخدام حقل المجموع الاختباري للتحقق من الرأس والبيانات بحثًا عن الأخطاء. إذا لم يتم إنشاء المبلغ بواسطة المرسل، فسيتم ملء الحقل بالأصفار. الحقل اختياري لـ IPv4.

حساب المجموع الاختباري

يتم تعريف طريقة حساب المجموع الاختباري في RFC 1071.

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

لحساب المجموع الاختباري، يتم تقسيم الرأس الزائف ورسالة UDP إلى كلمات مزدوجة البايت. ثم يتم حساب مجموع كل الكلمات في حساب الكود العكسي (أي الكود الذي يتم فيه الحصول على رقم سالب من رقم موجب عن طريق قلب جميع أرقام الرقم ويوجد صفران: 0x0000 (يشار إليه + 0) و0xffff (يُشار إليه بـ −0)). تتم كتابة النتيجة في الحقل المقابل في رأس UDP.

قيمة المجموع الاختباري التي تساوي 0x0000 (+0 في الرمز العكسي) محجوزة وتعني أنه لم يتم حساب المجموع الاختباري للرسالة. إذا تم حساب المجموع الاختباري وتبين أنه يساوي 0x0000، فسيتم إدخال القيمة 0xffff (-0 في الرمز العكسي) في حقل المجموع الاختباري.

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

مثال لحساب المجموع الاختباري

على سبيل المثال، لنحسب المجموع الاختباري لعدة كلمات ذات 16 بت: 0x398a، 0xf802، 0x14b2، 0xc281.

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

0x398a + 0xf802 = 0x1318c → 0x318d (النقل إلى الترتيب العالي) 0x318d + 0x14b2 = 0x0463f → 0x463f (رقم موجب) 0x463f + 0xc281 = 0x108c0 → 0x08c1

في النهاية، يتم عكس كافة بتات الرقم الناتج

0x08c1 = 0000 1000 1100 0001 → 1111 0111 0011 1110 = 0xf73e أو خلاف ذلك - 0xffff − 0x08c1 = 0xf73e . هذا هو المجموع الاختباري المطلوب.

عند حساب المجموع الاختباري، يتم استخدام رأس زائف مرة أخرى، لمحاكاة رأس IPv6 الحقيقي:

أجزاء 0 - 7 8 - 15 16 - 23 24 - 31
0 عنوان المصدر
32
64
96
128 عنوان المستلم
160
192
224
256 طول UDP
288 أصفار العنوان التالي
320 منفذ المصدر ميناء الوصول
352 طول تحقق من المبلغ
384+
بيانات

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

الموثوقية والحلول لمشاكل التحميل الزائد

نظرًا لافتقارها إلى الموثوقية، يجب أن تكون تطبيقات UDP مستعدة لبعض الخسارة والخطأ والتكرار. يمكن لبعضها (على سبيل المثال، TFTP) إضافة آليات موثوقية أولية اختياريًا على مستوى التطبيق.

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

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

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

التطبيقات

تستخدم العديد من تطبيقات الإنترنت الرئيسية UDP، بما في ذلك DNS (حيث يجب أن تكون الطلبات سريعة وتتكون من طلب واحد فقط متبوعًا بحزمة استجابة واحدة)، وبروتوكول إدارة الشبكة البسيط (SNMP)، وبروتوكول معلومات التوجيه (RIP)، والتكوين الديناميكي للمضيف (DHCP) .

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

مقارنة بين UDP وTCP

TCP هو بروتوكول موجه للاتصال، مما يعني أن "المصافحة" مطلوبة لتأسيس اتصال بين مضيفين. بمجرد إنشاء الاتصال، يمكن للمستخدمين إرسال البيانات في كلا الاتجاهين.

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

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

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

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

مرحبًا، اسمي جلين فيدلر وأرحب بكم في المقالة الأولى في كتابي الإلكتروني "برمجة الشبكات لمطوري الألعاب".

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

على الأرجح أنك سمعت بالفعل شيئًا عن المقابس، وربما تعلم أنها تأتي في نوعين رئيسيين - TCP وUDP. أول شيء عليك أن تقرره عند تطوير لعبة متعددة اللاعبين هو نوع المقابس التي ستستخدمها - TCP أو UDP أو كليهما؟

يعتمد اختيار نوع المقبس بشكل كامل على نوع اللعبة التي تقوم بتطويرها. في هذه السلسلة من المقالات، سأفترض أنك تكتب لعبة أكشن - مثل Halo، وBattlefield 1942، وQuake، وUnreal، وCounterStrike، وTeam Fortress، وما إلى ذلك.

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

يرمز TCP إلى "بروتوكول التحكم في الإرسال" ويرمز IP إلى "بروتوكول الإنترنت". إنهما معًا يدعمان تقريبًا كل ما تفعله عبر الإنترنت، بدءًا من تصفح الويب وحتى اتصالات IRC والبريد الإلكتروني - وكلها تعمل على TCP/IP.

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

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

مرة أخرى - كل شيء بسيط مثل الكتابة العادية أو القراءة من ملف. واتسون الابتدائية!

لكن سهولة الاستخدام هذه تختلف تمامًا عما يحدث بالفعل "تحت الغطاء"، على مستوى أدنى - مستوى بروتوكول IP.

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

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

ماذا لو أردنا نقل المعلومات بين أجهزة الكمبيوتر ليس بأسلوب قراءة/كتابة الملفات، ولكن عن طريق إرسال واستقبال حزم فردية مباشرة؟

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

باستخدام UDP، يمكننا إرسال حزمة إلى عنوان IP محدد (على سبيل المثال، 112.140.20.10) ومنفذ (على سبيل المثال، 52423)، وسيتم نقلها من كمبيوتر إلى كمبيوتر حتى تصل إلى وجهتها (أو تضيع على طول الطريق). طريق).

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

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

كما أن UDP لا يضمن الترتيب الذي يتم به تسليم الحزم. يمكنك إرسال خمس حزم بالترتيب - 1، 2، 3، 4، 5 - ولكنها قد تصل بترتيب مختلف تمامًا - على سبيل المثال، 3، 1، 2، 5، 4. مرة أخرى، من الناحية العملية، من المرجح أن تصل تصل بالترتيب الصحيح في أغلب الأحيان، لكن لا يمكنك الاعتماد على ذلك!

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

لذا علينا أن نقرر: هل يجب علينا استخدام مآخذ TCP أو UDP؟ دعونا نلقي نظرة على خصائصهم:

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

لكن لا.

ربما يكون استخدام TCP هو أسوأ خطأ يمكن أن ترتكبه عند تطوير لعبة متعددة اللاعبين. لفهم السبب، دعونا نلقي نظرة على ما يجعل بروتوكول TCP سهل الاستخدام للغاية!

كيف يعمل برنامج التعاون الفني
يعمل كل من TCP وUDP فوق IP، لكنهما في الواقع مختلفان تمامًا. يتصرف UDP بشكل مشابه جدًا لـ IP، بينما يقوم TCP بإبعاد المستخدم عن جميع مشكلات الحزم، مما يجعل التفاعل مشابهًا لقراءة/كتابة ملف.

اذا كيف يفعل ذلك؟

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

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

لدى TCP خيار لإصلاح هذا - "TCP_NODELAY". فهو يخبر البروتوكول بعدم انتظار تراكم البيانات في قائمة انتظار الإرسال، بل إرسالها على الفور.

لسوء الحظ، حتى مع تثبيت هذا الخيار، يواجه TCP الكثير من المشكلات عند استخدامه في الألعاب عبر الإنترنت.

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

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

ولكن ماذا يحدث إذا لم تصل إحدى الحزم؟ أو إذا وصلت الطرود خارج الترتيب، أو مع التكرارات؟

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

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

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

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

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

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

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

لكن انتظر! لماذا لا يمكنني استخدام كل من UDP وTCP معًا؟

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

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

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

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

سأخبرك في المقالات التالية بكيفية القيام بذلك - بدءًا من تنفيذ البروتوكول الخاص بك باستخدام الاتصالات المستندة إلى UDP، وحتى تنفيذ موثوقية النقل والتحكم في تدفق البيانات.

بروتوكول مخطط بيانات المستخدم - UDP

بروتوكول UDPهو أحد بروتوكولي طبقة النقل المستخدمة في مكدس بروتوكول TCP/IP. يسمح UDP لبرنامج تطبيقي بإرسال رسائله عبر شبكة بأقل قدر من الحمل المرتبط بتحويل بروتوكولات طبقة التطبيق إلى IP. إلا أن برنامج التطبيق نفسه يجب أن يعتني بالتأكد من وصول الرسالة إلى وجهتها. يبدو رأس مخطط بيانات (رسالة) UDP كما هو موضح في الشكل 2.10.

أرز. 2.10. هيكل رأس رسالة UDP

تسمى وحدة بيانات بروتوكول UDP بحزمة UDP أو مخطط بيانات المستخدم. تتكون حزمة UDP من رأس وحقل بيانات يحتوي على حزمة طبقة التطبيق. يحتوي الرأس على تنسيق بسيط ويتكون من أربعة حقول ثنائية البايت:

    منفذ مصدر UDP - رقم منفذ عملية الإرسال،

    منفذ وجهة UDP - رقم المنفذ لعملية المستلم،

    طول رسالة UDP - طول حزمة UDP بالبايت،

    المجموع الاختباري UDP - المجموع الاختباري لحزمة UDP

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

تحدد المنافذ الموجودة في الرأس بروتوكول UDP باعتباره معدد إرسال يسمح بجمع الرسائل من التطبيقات وإرسالها إلى طبقة البروتوكول. في هذه الحالة، يستخدم التطبيق منفذًا محددًا. قد تستخدم التطبيقات التي تتصل عبر الشبكة منافذ مختلفة، وهو ما ينعكس في رأس الحزمة. في المجمل، يمكن تحديد 216 منفذًا مختلفًا. يتم تخصيص أول 256 منفذًا لما يسمى بـ "الخدمات المعروفة"، والتي تتضمن، على سبيل المثال، منفذ UDP رقم 53، المخصص لخدمة DNS.

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

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

بروتوكول التحكم في النقل - TCP

إذا كانت مراقبة جودة نقل البيانات عبر الشبكة أمرًا مهمًا لأحد التطبيقات، ففي هذه الحالة يتم استخدام بروتوكول TCP. ويسمى هذا البروتوكول أيضًا بروتوكولًا موثوقًا وموجهًا نحو الاتصال وموجهًا نحو التدفق. قبل مناقشة خصائص البروتوكول هذه، دعونا نفكر في تنسيق مخطط البيانات المرسل عبر الشبكة (الشكل 2.11). وفقًا لهذا الهيكل، يحتوي TCP، مثل UDP، على منافذ. يتم تعيين أول 256 منفذًا لـ WKS، ويتم تعيين المنافذ من 256 إلى 1024 لخدمات Unix، ويمكن استخدام الباقي وفقًا لتقديرك. في الميدان رقم التسلسليتم تعريف رقم الحزمة في تسلسل الحزم التي تشكل الرسالة بأكملها، متبوعًا بحقل الإقرار رقم المعرفةومعلومات التحكم الأخرى.

أرز. 2.11. هيكل حزمة TCP

    يحتل المنفذ المصدر (SOURS PORT) 2 بايت، ويحدد عملية الإرسال؛

    يحتل منفذ الوجهة (DESTINATION PORT) 2 بايت، ويحدد عملية المستلم؛

    يشغل رقم التسلسل (رقم التسلسل) 4 بايت، ويشير إلى رقم البايت الذي يحدد إزاحة المقطع بالنسبة إلى دفق البيانات المرسلة؛

    يحتل الرقم المؤكد (رقم الإقرار) 4 بايت، ويحتوي على الحد الأقصى لعدد البايتات في المقطع المستلم، مع زيادة بمقدار واحد؛ هذه هي القيمة التي يتم استخدامها كإيصال؛

    يبلغ طول الرأس (HLEN) 4 بتات ويشير إلى طول رأس مقطع TCP، مقاسًا بكلمات 32 بت. طول الرأس غير ثابت ويمكن أن يختلف اعتمادًا على القيم المحددة في حقل الخيارات؛

    يحتل الاحتياطي (محجوز) 6 بتات، ويتم حجز الحقل لاستخدامه لاحقًا؛

    تشغل بتات التعليمات البرمجية (CODE BITS) 6 بتات وتحتوي على معلومات الخدمة حول نوع مقطع معين، ويتم تحديدها عن طريق تعيين البتات المقابلة لهذا الحقل إلى واحدة:

    URG - رسالة عاجلة.

    ACK - إيصال للجزء المستلم؛

    PSH - طلب إرسال رسالة دون انتظار ملء المخزن المؤقت؛

    RST - طلب استعادة الاتصال؛

    SYN - الرسالة المستخدمة لمزامنة عدادات البيانات المرسلة عند إنشاء اتصال؛

    FIN هي علامة على أن جانب الإرسال قد وصل إلى البايت الأخير في دفق البيانات المرسلة.

    تشغل النافذة (WINDOW) 2 بايت، وتحتوي على القيمة المعلنة لحجم النافذة بالبايت؛

    يستغرق المجموع الاختباري (CHECKSUM) 2 بايت ويتم حسابه لكل مقطع؛

    يشغل المؤشر العاجل (URGENT POINTER) 2 بايت، ويستخدم مع بت كود URG، ويشير إلى نهاية البيانات التي يجب استلامها بشكل عاجل، على الرغم من تجاوز سعة المخزن المؤقت؛

    الخيارات - هذا الحقل له طول متغير وقد يكون غائبًا تمامًا، والحد الأقصى لحجم الحقل هو 3 بايت؛ يستخدم لحل المشكلات المساعدة، على سبيل المثال، عند اختيار الحد الأقصى لحجم المقطع؛

    الحشو، وهو حشو متغير الطول، هو حقل وهمي يستخدم لجلب حجم الرأس إلى عدد صحيح من الكلمات 32 بت.

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

يتم تحديد اتجاه الاتصال من خلال حقيقة أنه قبل إرسال مقطع البيانات، تقوم وحدات TCP المصدر والوجهة بتبادل معلومات التحكم. ويسمى هذا التبادل مصافحة(حرفيا "المصافحة"). يستخدم TCP مصافحة ثلاثية المراحل:

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

    يستجيب المستلم برقم في حقل إقرار إيصال SYN الذي يطابق الرقم الذي حدده المصدر. بالإضافة إلى ذلك، قد يشير حقل "الرقم بالتسلسل" أيضًا إلى الرقم الذي طلبه المصدر؛

    يؤكد المصدر أنه قبل شريحة الوجهة ويرسل الجزء الأول من البيانات.

يتم عرض هذه العملية بيانياً في الشكل 2.12.

أرز. 2.12. إنشاء اتصال TCP

وبعد أن يتم الاتصال، يرسل المصدر البيانات إلى المتلقي وينتظر التأكيد منه باستلامها، ثم يرسل البيانات مرة أخرى، الخ، حتى تنتهي الرسالة. تنتهي الرسالة عندما يتم تعيين بت FIN في حقل الإشارات، وهو ما يعني "لا مزيد من البيانات".

يتم تحديد طبيعة التدفق للبروتوكول من خلال حقيقة أن SYN يحدد رقم البداية لحساب البايتات المرسلة، وليس الحزم. وهذا يعني أنه إذا تم ضبط SYN على 0 وتم إرسال 200 بايت، فإن الرقم المحدد في الحزمة التالية سيكون 201، وليس 2.

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

أرز. 2.13. آلية نقل البيانات TCP

في هذا المثال، تم تعيين النافذة إلى 250 بايت. وهذا يعني أن المقطع الحالي عبارة عن مقطع ذو إزاحة نسبة إلى SYN يساوي 250 بايت. ومع ذلك، بعد إرسال النافذة بأكملها، تلقت وحدة TCP المصدر إقرارًا باستقبال أول 100 بايت فقط. لذلك سيبدأ النقل من 101 بايت وليس من 251.

وهكذا، قمنا بفحص جميع الخصائص الأساسية لبروتوكول TCP. يبقى فقط تسمية أشهر التطبيقات التي يستخدمها TCP لتبادل البيانات. وهي في المقام الأول TELNET وFTP، بالإضافة إلى بروتوكول HTTP، الذي يعد قلب شبكة الويب العالمية.

دعونا نقطع المحادثة حول البروتوكولات قليلاً ونوجه انتباهنا إلى عنصر مهم في نظام TCP/IP بأكمله مثل عناوين IP.

مقدمة

UDP هو بروتوكول طبقة نقل بسيط موجه نحو مخطط البيانات: تصدر العملية مخطط بيانات UDP واحدًا في كل مرة، مما يؤدي إلى إرسال مخطط بيانات IP واحد. وهذا على النقيض من البروتوكولات الموجهة نحو التدفق مثل TCP، حيث لا يكون لكمية البيانات التي يتم إخراجها بواسطة التطبيق أي علاقة فعليًا بعدد مخططات بيانات IP المرسلة.

يوضح الشكل 11.1 تغليف مخطط بيانات UDP في مخطط بيانات IP.

الشكل 11.1 تغليف UDP.

مواصفات UDP الرسمية مذكورة في RFC 768 [Postel 1980].

UDP هو بروتوكول غير موثوق: فهو يرسل مخططات البيانات التي يكتبها التطبيق إلى طبقة IP، ولكن ليس هناك ما يضمن أنها ستصل إلى وجهتها النهائية. من وجهة نظر الموثوقية، قد تميل إلى تجنب استخدام UDP واستخدام البروتوكولات الموثوقة دائمًا مثل TCP. بعد تناول TCP، سنعود إلى هذا الموضوع ونلقي نظرة على أنواع التطبيقات التي يمكنها استخدام UDP.

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

رأس UDP

يوضح الشكل 11.2 الحقول الموجودة في رأس UDP.

الشكل 11.2 رأس UDP.

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

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

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

المجموع الاختباري لـ UDP

يغطي المجموع الاختباري لـ UDP رأس UDP وبيانات UDP. تذكر أن المجموع الاختباري الموجود في رأس IP يغطي رأس IP فقط - ولا يغطي البيانات الموجودة في مخطط بيانات IP. يحتوي كل من UDP وTCP على مجاميع اختبارية في رؤوسها، والتي تغطي كلا من الرأس والبيانات. بالنسبة لـ UDP، يكون المجموع الاختباري اختياريًا، ولكن بالنسبة لـ TCP فهو مطلوب.

يتم حساب المجموع الاختباري لـ UDP بنفس الطريقة تمامًا مثل (الفصل 3، القسم) المجموع الاختباري لرأس IP (مجموع الكلمات ذات 16 بت مع تجاوز السعة)، على الرغم من وجود اختلافات. أولاً، يمكن أن يتكون مخطط بيانات UDP من عدد فردي من البايتات، في حين يتضمن حساب المجموع الاختباري إضافة كلمات ذات 16 بت. يؤدي هذا إلى إضافة صفر بايت الحشو إلى نهاية مخطط البيانات إذا لزم الأمر لحساب المجموع الاختباري. (لا يتم نقل بايت الحشو.)

في UDP وTCP، هناك رؤوس زائفة ذات 12 بايت (في مخططات بيانات UDP ومقاطع TCP) فقط لغرض حساب المجموع الاختباري. تحتوي الرؤوس الزائفة على حقول معينة من رأس IP. ويتم كل ذلك للتأكد مرة أخرى من وصول البيانات إلى وجهتها المقصودة (لن يقبل IP مخططات البيانات غير الموجهة إلى ذلك المضيف، ولن يتمكن من إعادة توجيه مخططات بيانات UDP الموجهة إلى طبقة عليا أخرى). يوضح الشكل 11.3 الرأس الزائف ومخطط بيانات UDP.

الشكل 11.3 الحقول المستخدمة لحساب المجموع الاختباري لـ UDP.

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

إذا كان المجموع الاختباري المحسوب هو 0، فسيتم تخزينه على هيئة بتات الآحاد (65535)، وتكون هذه القيم متكافئة في حساب تكملة الآحاد. إذا كان المجموع الاختباري المرسل هو 0، فهذا يعني أن المرسل لم يحسب المجموع الاختباري.

إذا قام المرسل بحساب المجموع الاختباري وقرر المستلم أن هناك خطأ، فسيتم إتلاف مخطط بيانات UDP بصمت ولا يتم إنشاء أي رسالة خطأ. (يحدث نفس الشيء إذا اكتشفت طبقة IP خطأً في المجموع الاختباري لرأس IP.)

يتم حساب المجموع الاختباري لـ UDP بواسطة المرسل ويتم التحقق منه بواسطة المستلم. يسمح لك بتحديد أي تغييرات في رأس UDP أو البيانات التي حدثت على طول المسار بين المرسل والمستلم.

على الرغم من أن المجموع الاختباري لـ UDP يعد معلمة اختيارية، إلا أنه يجب حسابه دائمًا. في أواخر الثمانينات، بدأت بعض الشركات المصنعة لأجهزة الكمبيوتر في تعطيل حساب المجموع الاختباري لـ UDP افتراضيًا لزيادة سرعة نظام ملفات الشبكة (NFS)، الذي يستخدم UDP. قد يكون هذا مقبولاً على شبكة LAN واحدة حيث يتم حساب CRC للإطارات الموجودة في طبقة ارتباط البيانات (إطارات Ethernet أو Token Ring)، والتي يمكن استخدامها لتحديد تلف الإطار أثناء مرور مخطط البيانات عبر أجهزة التوجيه. صدق أو لا تصدق، هناك أجهزة توجيه بها أخطاء في برامجها أو أجهزتها تؤدي إلى تغيير البتات في مخططات البيانات التي تقوم بتوجيهها. لا يمكن اكتشاف هذه الأخطاء في مخططات بيانات UDP إذا تم تعطيل خيار المجموع الاختباري. تجدر الإشارة أيضًا إلى أن بعض بروتوكولات طبقة الارتباط (مثل SLIP) لا تحتوي على أي شكل من أشكال حساب المجموع الاختباري للبيانات الموجودة في الارتباط.

يتطلب RFC لمتطلبات المضيف تمكين حساب المجموع الاختباري لـ UDP بشكل افتراضي. كما أنها تتطلب التحقق من المجموع الاختباري المستلم إذا تم حسابه بواسطة المرسل (في حالة أن المجموع الاختباري المستلم ليس صفراً). تتجاهل بعض التطبيقات هذا وتتحقق من المجموع الاختباري المستلم إذا تم تمكين خيار حساب المجموع الاختباري الصادر.

إخراج الأمر tcpdump

من الصعب جدًا تحديد ما إذا كان خيار حساب المجموع الاختباري لـ UDP ممكّنًا على نظام معين. عادةً لا يملك التطبيق حق الوصول إلى حقل المجموع الاختباري لرأس UDP المستلم. لحل هذه المشكلة، أضاف المؤلف خيارًا آخر إلى برنامج tcpdump، وبعد ذلك بدأ في إخراج المجموع الاختباري لـ UDP المستلم. إذا كانت القيمة المستلمة هي 0، فهذا يعني أن المرسل لم يحسب المجموع الاختباري.

يوضح الشكل 11.4 المخرجات من وإلى ثلاثة أنظمة مختلفة على شبكتنا. قمنا بتشغيل برنامج sock()، وأرسلنا مخطط بيانات UDP واحدًا يحتوي على 9 بايت من البيانات إلى خادم صدى قياسي.

>

1 0.0 sun.1900 >gemini.echo: udp 9 (UDP cksum=6e90)
2 0.303755 (0.3038)gemini.echo > sun.1900: udp 9 (UDP cksum=0)

3 17.392480 (17.0887) sun.1904 > aix.echo: udp 9 (UDP cksum=6e3b)
4 17.614371 (0.2219) aix.echo > sun.1904: udp 9 (UDP cksum=6e3b)

5 32.092454 (14.4781) sun.1907 > Solaris.echo: udp 9 (UDP cksum=6e74)
6 32.314378 (0.2219) Solaris.echo > sun.1907: udp 9 (UDP cksum=6e74)

الشكل 11.4 الإخراج من tcpdump، والذي يمكن استخدامه لتحديد ما إذا كان خيار المجموع الاختباري لـ UDP ممكّنًا على مضيف معين.

من هنا يمكننا أن نرى أن اثنين من الأنظمة الثلاثة قد تم تمكين خيار المجموع الاختباري UDP لهم.

لاحظ أيضًا أن مخطط البيانات الصادر له نفس المجموع الاختباري مثل مخطط البيانات الوارد (السطر 3 و4 و5 و6). في الشكل 11.3، ستلاحظ أنه تم تبديل عنواني IP، وكذلك رقمي المنفذ. ظلت الحقول الأخرى في الرأس الزائف ورأس UDP كما هي منذ تكرار البيانات. وهذا يؤكد أن المجموع الاختباري لـ UDP (وفي الواقع كل المجموع الاختباري في عائلة بروتوكول TCP/IP) هو مجموع بسيط مكون من 16 بت. بمساعدتها، من المستحيل اكتشاف الخطأ، والذي يتكون من تغيير أماكن قيمتين 16 بت.

بعض الإحصائيات

يوفر [Mogul 1992] بعض المعلومات الإحصائية حول حدوث أخطاء المجموع الاختباري على خادم NFS مشغول إلى حد ما والذي تم تشغيله لمدة 40 يومًا. ويبين الشكل 11.5 البيانات الإحصائية.

عدد الأخطاء في المجاميع الاختبارية

العدد التقريبي للحزم

إيثرنت
الملكية الفكرية
UDP
برنامج التعاون الفني

الشكل 11.5 إحصائيات الحزم التالفة التي تم تحديدها باستخدام المجاميع الاختبارية.

يعرض العمود الأخير العدد التقريبي للحزم، حيث تستخدم البروتوكولات الأخرى طبقة Ethernet وIP. على سبيل المثال، لا يتم استخدام كافة إطارات Ethernet بواسطة مخططات بيانات IP؛ كما يستخدم ARP شبكة Ethernet. لا يتم استخدام كافة مخططات بيانات IP بواسطة UDP أو TCP، نظرًا لأن ICMP يستخدم IP أيضًا.

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

ولذلك، فإن الأخطاء المدرجة في المحصلة النهائية لا تتعلق دائمًا بطبقة ارتباط البيانات (Ethernet، Token Ring). عند نقل البيانات، يجب عليك دائمًا تمكين خيار المجموع الاختباري في نقاط النهاية. ومع ذلك، إذا كانت البيانات التي يتم إرسالها ذات قيمة ما، فلا يجب أن تثق تمامًا في المجاميع الاختبارية لـ UDP أو TCP، نظرًا لأنها مجاميع اختبارية بسيطة وليست مضمونة لحماية البيانات من جميع الأخطاء المحتملة.

مثال بسيط

سنستخدم برنامج sock لإنشاء بعض مخططات بيانات UDP، والتي سنتناولها باستخدام tcpdump:

>بسدي % جورب -v -u -i -n4 svr4 تجاهل
متصل على 140.252.13.35.1108 إلى 140.252.13.34.9

بسدى % جورب -v -u -i -n4 -w0 svr4 تجاهل
متصل على 140.252.13.35.1110 إلى 140.252.13.34.9

في الحالة الأولى لبدء البرنامج، يتم ضبط وضع التصحيح ( -v)، بينما يمكنك عرض أرقام المنافذ المخصصة ديناميكيًا، ويتم تحديد UDP ( -u) بدلاً من TCP افتراضيًا، ويتم ضبط وضع المصدر، الخيار ( -i)، وهذا يعني أننا سنرسل البيانات، بدلاً من القراءة من الإدخال القياسي أو الكتابة إلى الإخراج القياسي. يخبره الخيار -n4 بإخراج 4 مخططات بيانات (بدلاً من 1024 الافتراضية) إلى المضيف الوجهة svr4. تم وصف خدمة التجاهل في القسم الموجود في الفصل الأول. نستخدم حجم إخراج افتراضي يبلغ 1024 بايت لكل سجل.

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

>

1 0.0 bsdi.1108 > svr4.discard: udp 1024
2 0.002424 (0.0024) bsdi.1108 > svr4.discard: udp 1024
3 0.006210 (0.0038) bsdi.1108 > svr4.discard: udp 1024
4 0.010276 (0.0041) bsdi.1108 > svr4.discard: udp 1024

5 41.720114 (41.7098) bsdi.1110 > svr4.discard: udp 0
6 41.721072 (0.0010) bsdi.1110 > svr4.discard: udp 0
7 41.722094 (0.0010) bsdi.1110 > svr4.discard: udp 0
8 41.723070 (0.0010) bsdi.1110 > svr4.discard: udp 0

الشكل 11.6: إخراج الأمر tcpdump عند إرسال مخططات بيانات UDP في اتجاه واحد.

يُظهر الإخراج أربع مخططات بيانات بحجم 1024 بايت متبوعة بأربعة مخططات بيانات ذات طول صفري. يتبع كل مخطط بيانات المخطط السابق بفاصل زمني يبلغ عدة ميلي ثانية. (استغرق الأمر 41 ثانية لإدخال الأمر الثاني.)

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

وأخيرًا، لاحظ أن رقم منفذ مصدر UDP يتغير في كل مرة تقوم فيها بتشغيل البرنامج. كان المنفذ أولاً 1108، ثم 1110. في قسم الفصل الأول، أوضحنا أن أرقام المنافذ المخصصة ديناميكيًا والتي يستخدمها العملاء تتراوح عادةً من 1024 إلى 5000.

تجزئة الملكية الفكرية

يوضح الشكل 11.9 تنسيق خطأ ICMP الذي يتعذر الوصول إليه في هذه الحالة. وهو يختلف عن التنسيق الموضح لأن البتات 16-31 في الكلمة الثانية ذات 32 بت يمكن أن تحتوي على القفزة التالية MTU بدلاً من تعيينها على 0.

الشكل 11.9 خطأ يتعذر الوصول إليه في ICMP عندما تكون التجزئة مطلوبة، ولكن تم تعيين بت "عدم التجزئة".

إذا كان جهاز التوجيه لا يدعم تنسيق خطأ ICMP الجديد هذا، فسيتم تعيين MTU للقفزة التالية على 0.

يحدد RFC الجديد لمتطلبات جهاز التوجيه [Almquist 1993] أنه يجب على جهاز التوجيه إنشاء هذا النموذج الجديد عندما يصدر رسالة ICMP غير قابلة للوصول.

حدثت المشكلة التي سنناقشها عندما تلقينا خطأ ICMP أثناء محاولة تحديد MTU لارتباط الطلب الهاتفي SLIP بين جهاز توجيه netb والشمس المضيفة. نحن نعرف وحدة الإرسال الكبرى (MTU) لهذا الرابط من sun إلى netb لأنه، أولاً، تم تحديدها عند تكوين SLIP على المضيف Sun، وثانيًا، رأينا وحدة الإرسال الكبرى (MTU) عندما قمنا بتشغيل أمر netstat في القسم من الفصل 3. الآن نريد تحديد MTU في اتجاه آخر. (يشرح هذا كيفية تحديد وحدة الإرسال الكبرى باستخدام SNMP.) بالنسبة للارتباطات من نقطة إلى نقطة، لا يلزم أن تكون وحدة الإرسال الكبرى هي نفسها في كلا الاتجاهين.

تم استخدام التقنية التالية لتحديد. قمنا بتشغيل اختبار ping من مضيف Solaris إلى مضيف bsdi، مما أدى إلى زيادة حجم حزمة البيانات حتى يتم تطبيق التجزئة على الحزم. تظهر العملية في الشكل 11.10.

الشكل 11.10 الأنظمة التي تم استخدامها لتحديد MTU لارتباط SLIP بين netb وsun.

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

لفهم ما كان يحدث بشكل أفضل، تم تشغيل tcpdump أيضًا على bsdi، وبعد ذلك أصبح من الواضح ما تم إرساله وما تم استلامه. ويبين الشكل 11.11 الإخراج.

>

1 0.0 سولاريس>
2 0.000000 (0.0000) بسدي>
3 0.000000 (0.0000) شمس>
بحاجة إلى التجزئة، MTU = 0 (DF)

4 0.738400 (0.7384) سولاريس > bsdi: icmp: طلب الصدى (DF)
5 0.748800 (0.0104) bsdi > سولاريس: icmp: رد الصدى (DF)
6 0.748800 (0.0000) شمس> bsdi: icmp: سولاريس غير قابل للوصول -
بحاجة إلى التجزئة، MTU = 0 (DF)

الشكل 11.11 إخراج برنامج tcpdump من ping إلى bsdi من Solaris مع مخطط بيانات IP يبلغ حجمه 600 بايت.

أولاً، يعني التعبير (DF) الموجود في كل سطر أنه تم تعيين بت "عدم التجزئة" في رأس IP على واحد. وهذا يعني أن Solaris 2.2 يقوم عادةً بتعيين هذا البت إلى واحد كجزء من آلية تحديد وحدة الإرسال الكبرى للنقل.

يشير السطر 1 إلى أن اختبار ping يمر عبر netb إلى الشمس بدون تجزئة ومع تعيين بت DF، لذلك يمكننا أن نستنتج أن حجم MTU الحرج لمضيف SLIP netb لم يتم الوصول إليه بعد.

لاحظ أيضًا من السطر رقم 2 أنه يتم نسخ علامة DF إلى كل استجابة صدى. وهذا هو بالضبط ما سبب المشكلة. رد الصدى هو نفس حجم طلب الصدى (ما يزيد قليلاً عن 600 بايت)، لكن وحدة الإرسال الكبرى لواجهة SLIP الصادرة لشمس المضيف هي 552. يجب أن يكون رد الصدى مجزأً، ولكن يتم تعيين علامة DF. يؤدي هذا إلى قيام شركة Sun بإنشاء خطأ يتعذر الوصول إليه في ICMP وإرساله إلى bsdi (حيث يتم إتلافه).

ولهذا السبب لم نشاهد أصداء من سولاريس. الردود لم تكن تمر عبر الشمس. يوضح الشكل 11.12 المسار الذي تسلكه الحزم.

الشكل 11.12 تبادل الحزم لهذا المثال.

أخيرًا، لاحظ أن التعبير mtu=0 في السطرين 3 و6 في الشكل 11.11 يشير إلى أن الشمس لا تُرجع وحدة الإرسال الكبرى للواجهة الصادرة في رسالة ICMP غير القابلة للوصول، كما هو موضح في الشكل 11.9. (في الفصل 25، سنحل هذه المشكلة باستخدام SNMP ونتأكد من أن SLIP MTU لواجهة netb هو 1500.)

تحديد MTU للنقل باستخدام Traceroute

نظرًا لأن معظم الأنظمة لا تدعم وظيفة تحديد وحدة الإرسال الكبرى للنقل، فسوف نقوم بتعديل برنامج التتبع () حتى يتمكن من تحديد وحدة الإرسال الكبرى للنقل. سوف نرسل حزمة مع مجموعة بتات عدم التجزئة. سيكون حجم الحزمة الأولى المرسلة مساويًا لوحدة الإرسال الكبرى للواجهة الصادرة. عندما يتم إرجاع خطأ ICMP "لا يمكن التجزئة"، سنقوم بتقليل الحزمة مقاس. إذا كان جهاز التوجيه الذي أرسل خطأ ICMP يدعم إصدارًا جديدًا يتضمن واجهة MTU الصادرة في رسالة ICMP، فإننا نستخدم القيمة الناتجة؛ وإلا فإننا سوف نحاول MTU الأصغر التالي. كما ينص RFC 1191 [Mogul and Deering 1990] على أن هناك عددًا محدودًا من قيم MTU، فإن برنامجنا يحتوي على جدول بالقيم المحتملة وسينتقل ببساطة إلى القيمة الأصغر التالية.

دعونا نجرب خوارزمية مشابهة من شمس المضيف إلى قسيمة المضيف، مع العلم أن قناة SLIP لديها MTU تبلغ 296:

>

شمس ٪ Traceroute.pmtu زلة

وحدة الإرسال الكبرى الصادرة = 1500
1 bsdi (140.252.13.35) 15 مللي ثانية 6 مللي ثانية 6 مللي ثانية
2 بسدي (140.252.13.35) 6 مللي ثانية
التجزئة المطلوبة وتعيين DF، ومحاولة MTU الجديدة = 1492
التجزئة المطلوبة وتعيين DF، ومحاولة MTU جديد = 1006
التجزئة المطلوبة وتعيين DF، ومحاولة MTU جديدة = 576
التجزئة المطلوبة وتعيين DF، ومحاولة MTU جديدة = 552
التجزئة المطلوبة وتعيين DF، ومحاولة MTU جديدة = 544
التجزئة المطلوبة وتعيين DF، ومحاولة MTU جديد = 512
التجزئة المطلوبة وتعيين DF، ومحاولة MTU جديدة = 508
التجزئة المطلوبة وتعيين DF، ومحاولة MTU جديدة = 296
2 قسيمة (140.252.13.65) 377 مللي ثانية 377 مللي ثانية 377 مللي ثانية

في هذا المثال، لم يُرجع جهاز التوجيه bsdi واجهة MTU الصادرة في رسالة ICMP، لذلك سننتقل إلى قيمة MTU الأقل التالية. يُبلغ السطر الأول من إخراج TTL المكون من 2 عن اسم المضيف bsdi، ولكن هذا لأنه تم إرجاعه بواسطة جهاز التوجيه في خطأ ICMP. السطر الأخير من الإخراج لـ TTL 2 هو بالضبط ما توقعناه.

من السهل تعديل كود ICMP على bsdi للحصول على MTU للواجهة الصادرة. وإذا فعلنا ذلك ورجعنا إلى برنامجنا سنحصل على النتيجة التالية:

>

شمس ٪ Traceroute.pmtu زلة
تتبع المسار للانزلاق (140.252.13.65)، 30 قفزة كحد أقصى
وحدة الإرسال الكبرى الصادرة = 1500
1 bsdi (140.252.13.35) 53 مللي ثانية 6 مللي ثانية 6 مللي ثانية
2 بسدي (140.252.13.35) 6 مللي ثانية
التجزئة مطلوبة وتعيين DF، الخطوة التالية MTU = 296
2 قسيمة (140.252.13.65) 377 مللي ثانية 378 مللي ثانية 377 مللي ثانية

لا فائدة من تجربة ثماني قيم MTU مختلفة هنا؛

الإنترنت العالمي

تم تشغيل نسخة معدلة من برنامج التتبع عدة مرات على مضيفين مختلفين حول العالم. وبمساعدتها، تم الوصول إلى 15 دولة (بما في ذلك القارة القطبية الجنوبية)، باستخدام قنوات مختلفة عبر المحيط الأطلسي وعبر المحيط الهادئ. ومع ذلك، قبل القيام بذلك، قمنا بزيادة MTU لارتباط الطلب الهاتفي SLIP بين شبكتنا الفرعية وجهاز توجيه netb (الشكل 11.12) إلى 1500، كما هو الحال في Ethernet.

من بين 18 مرة تم تشغيل البرنامج، في حالتين فقط كانت وحدة النقل الكبرى أقل من 1500. كان لدى أحد الروابط عبر الأطلسي وحدة MTU تبلغ 572 (قيمة غريبة لم يتم إدراجها حتى في RFC 1191)، ولم يُرجع جهاز التوجيه رسالة. خطأ ICMP في التنسيق الجديد. رابط آخر بين جهازي توجيه في اليابان لم يعالج إطارات بحجم 1500 بايت، ولم يُرجع جهاز التوجيه خطأ ICMP بالتنسيق الجديد. بعد أن تم تخفيض MTU إلى 1006، عملت كل شيء.

الاستنتاج الذي يمكننا استخلاصه من هذه التجارب هو أن معظم الشبكات واسعة النطاق (وليس كلها) يمكنها حاليًا التعامل مع حزم أكبر من 512 بايت. يتيح استخدام ميزة بحث MTU الخاصة بالنقل للتطبيقات أداءً أكثر كفاءة بشكل ملحوظ باستخدام وحدات MTU الأكبر حجمًا.

تحديد MTU للنقل عند استخدام UDP

دعونا نلقي نظرة على التفاعل بين التطبيق الذي يستخدم UDP وآلية اكتشاف MTU للنقل. نحتاج إلى معرفة ما يحدث عندما يرسل أحد التطبيقات مخطط بيانات كبيرًا جدًا بالنسبة لبعض القنوات الوسيطة.

نظرًا لأن النظام الوحيد الذي يدعم آلية اكتشاف MTU للنقل هو Solaris 2.x، فإننا نستخدمه كمضيف مصدر لإرسال مخطط بيانات بحجم 650 بايت. نظرًا لأن مضيف القسيمة موجود خلف ارتباط SLIP مع وحدة MTU تبلغ 296، فإن أي مخطط بيانات UDP أكبر من 268 بايت (296 - 20 - 8) مع مجموعة البت "عدم التجزئة" يجب أن يتسبب في حدوث خطأ ICMP "لا يمكن التجزئة" من bsdi جهاز التوجيه. ويبين الشكل 11.13 طوبولوجيا القنوات ووحدة الإرسال الكبرى (MTU).

الشكل 11.13 الأنظمة المستخدمة لتحديد وحدة الإرسال الكبرى (MTU) للنقل باستخدام UDP.

يقوم الأمر التالي بإنشاء عشرة مخططات بيانات UDP بحجم 650 بايت بفواصل زمنية مدتها 5 ثوانٍ:

> سولاريس % جورب -u -i -n10 -w650 -p5 زلة تجاهل

يوضح الشكل 11.14 مخرجات الأمر tcpdump. عند تشغيل هذا المثال، تم تكوين جهاز التوجيه bsdi بحيث لا يقوم بإرجاع وحدة الإرسال الكبرى (MTU) للخطوة التالية كجزء من خطأ ICMP "لا يمكن التجزئة".

يقوم مخطط البيانات الأول الذي تم إرساله مع مجموعة بت DF (السطر 1) بإنشاء الخطأ المتوقع من جهاز التوجيه bsdi (السطر 2). ومن المثير للاهتمام أن مخطط البيانات التالي، الذي تم إرساله أيضًا مع مجموعة بت DF (السطر 3)، يولد نفس خطأ ICMP (السطر 4). لقد توقعنا أن يتم إرسال مخطط البيانات هذا مع إيقاف تشغيل بت DF.

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

>

1 0.0 Solaris.38196 > الانزلاق.تجاهل: UDP 650 (DF)
2 0.004218 (0.0042) bsdi > سولاريس: icmp:

3 4.980528 (4.9763) Solaris.38196 > الانزلاق.تجاهل: UDP 650 (DF)
4 4.984503 (0.0040) bsdi > سولاريس: icmp:
الانزلاق غير قابل للوصول - بحاجة إلى التجزئة، mtu = 0 (DF)

5 9.870407 (4.8859) Solaris.38196 > Slip.discard: UDP 650 (frag 47942:552@0+)
6 9.960056 (0.0896) سولاريس> قسيمة: (جزء 47942:106@552)

7 14.940338 (4.9803) Solaris.38196 > الانزلاق.تجاهل: UDP 650 (DF)
8 14.944466 (0.0041) bsdi > سولاريس: icmp:
الانزلاق غير قابل للوصول - بحاجة إلى التجزئة، mtu = 0 (DF)

9 19.890015 (4.9455) Solaris.38196 > Slip.discard: UDP 650 (frag 47944:552@0+)
10 19.950463 (0.0604) سولاريس > قسيمة: (جزء 47944:106@552)

11 24.870401 (4.9199) Solaris.38196 > Slip.discard: UDP 650 (frag 47945:552@0+)
12 24.960038 (0.0896) سولاريس > قسيمة: (جزء 47945:106@552)

13 29.880182 (4.9201) Solaris.38196 > Slip.discard: UDP 650 (frag 47946:552@0+)
14 29.940498 (0.0603) سولاريس> قسيمة: (جزء 47946:106@552)

15 34.860607 (4.9201) Solaris.38196 > Slip.discard: UDP 650 (frag 47947:552@0+)
16 34.950051 (0.0894) سولاريس > قسيمة: (جزء 47947:106@552)

17 39.870216 (4.9202) Solaris.38196 > Slip.discard: UDP 650 (frag 47948:552@0+)
18 39.930443 (0.0602) سولاريس> قسيمة: (جزء 47948:106@552)

19 44.940485 (5.0100) Solaris.38196 > الانزلاق.تجاهل: UDP 650 (DF)
20 44.944432 (0.0039) bsdi > سولاريس: icmp:
الانزلاق غير قابل للوصول - بحاجة إلى التجزئة، mtu = 0 (DF)

الشكل 11.14 تحديد وحدة الإرسال الكبرى (MTU) للنقل باستخدام UDP.

نظرًا لأن رسالة ICMP "لا يمكن التجزئة" لا تحتوي على القفزة التالية MTU، فهذا يعني أن IP قرر أن الجميع سعداء بوحدة MTU تبلغ 576. يحتوي الجزء الأول (السطر 5) على 544 بايت من بيانات UDP، و8 بايت من رأس UDP و20 بايت من رأس IP، يبلغ الحجم الإجمالي لمخطط بيانات IP 572 بايت. يحتوي الجزء الثاني (السطر 6) على 106 بايت المتبقية من بيانات UDP ورأس IP ذو 20 بايت.

لسوء الحظ، يحتوي مخطط البيانات التالي، السطر 7، على مجموعة بت DF، لذلك يتم تجاهله بواسطة bsdi ويتم إرجاع خطأ ICMP. ما حدث هنا هو انتهاء صلاحية مؤقت IP، مما يطلب من IP التحقق لمعرفة ما إذا كانت وحدة الإرسال الكبرى للنقل قد زادت عن طريق إعادة تعيين بت DF. سوف نرى هذا يحدث مرة أخرى في السطرين 19 و 20. وبمقارنة الأوقات في السطرين 7 و 19 حيث تم ضبط بتة DF على واحد، نرى أنه يتم فحص MTU للنقل لزيادة كل 30 ثانية.

هذا الموقّت الذي يبلغ 30 ثانية قصير جدًا. يوصي RFC 1191 بضبط المؤقت على 10 دقائق. يمكن تغيير قيمة المؤقت باستخدام المعلمة ip_ire_pathmtu_interval (الملحق E، القسم). في Solaris 2.2، لا توجد طريقة لتعطيل اكتشاف MTU للنقل لتطبيق UDP واحد أو لكافة تطبيقات UDP. يمكن تمكينه أو تعطيله فقط للنظام بأكمله عن طريق تغيير المعلمة ip_path_mtu_discovery. كما نرى من هذا المثال، فإن تمكين ميزة اكتشاف MTU للنقل عندما ترسل تطبيقات UDP مخططات بيانات من المحتمل أن تكون مجزأة سيؤدي إلى تجاهل مخطط البيانات.

الحد الأقصى لحجم مخطط البيانات الذي تقبله طبقة IP على سولاريس (576 بايت) غير صحيح. لقد رأينا في الشكل 11.13 أن وحدة الإرسال الكبرى الفعلية تبلغ 296 بايت. وهذا يعني أن الأجزاء التي تم إنشاؤها بواسطة سولاريس يتم تجزئةها مرة أخرى على bsdi. يوضح الشكل 11.15 مخرجات tcpdump التي تم تلقيها في مضيف الوجهة (slip) لوصول مخطط البيانات الأول (السطران 5 و6 في الشكل 11.14).

>

1 0.0 Solaris.38196 > Slip.discard: UDP 650 (frag 47942:272@0+)
2 0.304513 (0.3045) سولاريس > قسيمة: (جزء 47942:272@272+)
3 0.334651 (0.0301) سولاريس> قسيمة: (جزء 47942:8@544+)
4 0.466642 (0.1320) سولاريس> قسيمة: (جزء 47942:106@552)

الشكل 11.15 وصول أول مخطط بيانات إلى مضيف القسيمة من سولاريس.

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

سنقوم الآن بتشغيل نفس المثال، ولكننا سنغير سلوك جهاز التوجيه bsdi بحيث يقوم بإرجاع الخطوة التالية MTU في رسالة ICMP "لا يمكن التجزئة". يوضح الشكل 11.16 الأسطر الستة الأولى من مخرجات tcpdump.

>

1 0.0 Solaris.37974 > الانزلاق.تجاهل: UDP 650 (DF)
2 0.004199 (0.0042) bsdi > سولاريس: icmp:

3 4.950193 (4.9460) Solaris.37974 > الانزلاق.تجاهل: UDP 650 (DF)
4 4.954325 (0.0041) bsdi > سولاريس: icmp:
الانزلاق غير قابل للوصول - بحاجة إلى التجزئة، MTU = 296 (DF)

5 9.779855 (4.8255) Solaris.37974 > Slip.discard: UDP 650 (frag 35278:272@0+)
6 9.930018 (0.1502) سولاريس> قسيمة: (جزء 35278:272@272+)
7 9.990170 (0.0602) سولاريس > قسيمة: (جزء 35278:114@544)

الشكل 11.16 تحديد وحدة الإرسال الكبرى (MTU) للنقل باستخدام UDP.

مرة أخرى نرى أنه تم إرسال أول مخططي بيانات مع مجموعة بت DF، وكلاهما تلقى أخطاء ICMP. حاليًا، يشير خطأ ICMP إلى وحدة الإرسال الكبرى (MTU) التالية للأمام، وهي 296.

في الأسطر 5 و6 و7، نرى أن المضيف المصدر يقوم بإجراء التجزئة، كما في الشكل 11.14. إذا كانت وحدة الإرسال الكبرى (MTU) للقفزة التالية معروفة، فسيتم إنشاء ثلاثة أجزاء فقط، مقارنة بالأجزاء الأربعة التي تم إنشاؤها بواسطة جهاز التوجيه bsdi في الشكل 11.15.

التفاعل بين UDP وARP

باستخدام UDP، يمكننا أن ننظر إلى التفاعل المثير للاهتمام بين UDP وتنفيذ ARP النموذجي.

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

>

بسدى % آرب -أتأكد من أن ذاكرة التخزين المؤقت لـ ARP فارغة
بسدى % جورب -u -i -n1 -w8192 svr4 تجاهل

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

>

1 0.0 ARP من لديه svr4 أخبر bsdi
2 0.001234 (0.0012) arp من لديه svr4 أخبر bsdi
3 0.001941 (0.0007) arp من لديه svr4 أخبر bsdi
4 0.002775 (0.0008) arp من لديه svr4 أخبر bsdi
5 0.003495 (0.0007) arp من لديه svr4 أخبر bsdi
6 0.004319 (0.0008) arp من لديه svr4 أخبر bsdi
7 0.008772 (0.0045) رد ARP svr4 موجود في 0:0:c0:c2:9b:26
8 0.009911 (0.0011) رد ARP svr4 موجود في 0:0:c0:c2:9b:26
9 0.011127 (0.0012) bsdi > svr4: (جزء 10863:800@7400)
10 0.011255 (0.0001) رد ARP svr4 موجود في 0:0:c0:c2:9b:26
11 0.012562 (0.0013) رد ARP svr4 موجود في 0:0:c0:c2:9b:26
12 0.013458 (0.0009) رد ARP svr4 موجود في 0:0:c0:c2:9b:26
13 0.014526 (0.0011) رد ARP svr4 موجود في 0:0:c0:c2:9b:26
14 0.015583 (0.0011) رد ARP svr4 موجود في 0:0:c0:c2:9b:26

الشكل 11.17 تبادل الحزم عند إرسال مخطط بيانات UDP سعة 8192 بايت عبر شبكة إيثرنت.

هذا الاستنتاج غير متوقع تماما. أولاً، قبل تلقي استجابة ARP الأولى، يتم إنشاء ستة طلبات ARP. كما قد تتخيل، يقوم IP بإنشاء ستة أجزاء بسرعة، ويتم إرسال طلب ARP لكل منها.

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

يتطلب RFC لمتطلبات المضيف تطبيقات لمنع تدفق ARP (إرسال طلبات ARP بشكل متكرر لنفس عنوان IP بتردد عالٍ). الحد الأقصى للتردد الموصى به هو مرة واحدة في الثانية. نرى هنا ستة طلبات ARP خلال 4.3 مللي ثانية. يتطلب RFC لمتطلبات المضيف أن يقوم ARP بتخزين حزمة واحدة على الأقل، ويجب أن تكون الحزمة الأحدث. وهذا هو بالضبط ما رأيناه هنا.

الشذوذ التالي غير المبرر هو أن svr4 أرسل سبعة ردود ARP، وليس ستة.

آخر شيء يجب ملاحظته هو أن tcpdump تم تشغيله لمدة 5 دقائق أخرى بعد عودة آخر استجابة لـ ARP، في انتظار رؤية svr4 يرسل خطأ ICMP "تم تجاوز الوقت أثناء إعادة التجميع". لم تظهر رسالة ICMP مطلقًا. (لقد أظهرنا تنسيق هذه الرسالة بتنسيق . يشير حقل الرمز المعين إلى 1 إلى انقضاء الوقت أثناء إعادة تجميع مخطط البيانات.)

يجب أن تبدأ طبقة IP مؤقتًا عند ظهور جزء مخطط البيانات الأول. هنا، تعني كلمة "الأول" الجزء الأول الذي يصل إلى مخطط بيانات معين، وليس الجزء الأول فقط (مع إزاحة الجزء 0). تتراوح قيمة المهلة النموذجية من 30 إلى 60 ثانية. إذا لم تصل جميع أجزاء مخطط البيانات هذا قبل انتهاء صلاحية المؤقت، فسيتم تجاهل جميع الأجزاء. إذا لم يتم ذلك، فإن الأجزاء التي لا تصل أبدًا (كما رأينا في هذا المثال) يمكن أن تتسبب في تجاوز سعة مخزن الاستلام المؤقت.

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

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

الحد الأقصى لحجم مخطط بيانات UDP

من الناحية النظرية، يمكن أن يكون الحد الأقصى لحجم مخطط بيانات IP 65535 بايت، وهو محدود بحقل كامل الطول بطول 16 بت في رأس IP (انظر). مع طول رأس IP يبلغ 20 بايت وطول رأس UDP يبلغ 8 بايت، يترك مخطط بيانات UDP حدًا أقصى قدره 65507 بايت لبيانات المستخدم. ومع ذلك، تستخدم معظم التطبيقات مخططات بيانات أصغر بكثير.

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

يتم تحديد القيد التالي من خلال تطبيق kernel TCP/IP. قد تكون هناك خصائص (أو أخطاء) في التنفيذ تحد من حجم مخطط بيانات UDP إلى أقل من 65535 بايت.

قام المؤلف بتجربة أحجام مختلفة من مخططات بيانات UDP باستخدام برنامج الجورب. باستخدام واجهة الاسترجاع ضمن SunOS 4.1.3، كان الحد الأقصى لحجم مخطط بيانات UDP هو 32767 بايت. لم يكن من الممكن استخدام قيمة أعلى. عند النقل عبر Ethernet من BSD/386 إلى SunOS 4.1.3، كان الحد الأقصى لحجم مخطط بيانات IP الذي يمكن لشركة Sun قبوله هو 32786 (مع 32758 بايت من بيانات المستخدم). باستخدام واجهة الاسترجاع في Solaris 2.2، كان الحد الأقصى لحجم مخطط بيانات IP الذي يمكن إرساله واستلامه هو 65535 بايت. عند النقل من Solaris 2.2 إلى AIX 3.2.2، كان من الممكن نقل مخطط بيانات IP بحجم أقصى يبلغ 65535 بايت.

اقتطاع مخطط البيانات

إن مجرد قدرة IP على إرسال واستقبال مخططات بيانات ذات حجم معين لا يعني أن التطبيق المتلقي جاهز لقراءة مخططات البيانات بهذا الحجم. تسمح UDP API للتطبيقات بتحديد الحد الأقصى لعدد البايتات المراد معالجتها في وقت واحد. ماذا يحدث إذا كانت مخططات البيانات المستلمة أكبر من مخططات البيانات التي يرغب التطبيق في قبولها؟

لسوء الحظ، تعتمد الإجابة على واجهة البرنامج وتنفيذه.

تقوم الإصدارات التقليدية من واجهة برمجة تطبيقات مقبس Berkeley باقتطاع مخططات البيانات، وتجاهل أي بيانات غير مناسبة. يعتمد ما إذا كان سيتم إخطار التطبيق على الإصدار. (4.3 يمكن لـ BSD Reno والإصدارات الأحدث إخطار التطبيق بأن مخطط البيانات قد تم اقتطاعه.) لا تقوم مآخذ توصيل API ضمن SVR4 (بما في ذلك Solaris 2.x) باقتطاع مخططات البيانات. تتم قراءة أي بيانات غير مناسبة بشكل تسلسلي. لا يتم إخطار التطبيق بدورات القراءة المتعددة وسيتم إرسال مخطط بيانات UDP واحد. لا تتجاهل واجهات برمجة تطبيقات TLI البيانات. بدلاً من ذلك، يتم إرجاع علامة تشير إلى وجود بيانات أكثر مما يمكن قراءتها مرة واحدة، لذلك يبدأ التطبيق في قراءة مخطط البيانات المتبقي بشكل تسلسلي.

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

خطأ في منع مصدر ICMP

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

يوضح الشكل 11.18 تنسيق خطأ منع مصدر ICMP. نحن في وضع مثالي لإنشاء خطأ مماثل على شبكة الاختبار الخاصة بنا. يمكننا إرسال مخططات البيانات من bsdi إلى جهاز التوجيه sun عبر Ethernet، ويجب توجيه مخططات البيانات هذه عبر قناة SLIP. نظرًا لأن قناة SLIP أبطأ بحوالي ألف مرة من شبكة Ethernet، فيمكننا بسهولة تجاوز سعة المخزن المؤقت. يرسل الأمر التالي 100 1024 بايت من مخططات البيانات من المضيف bsdi عبر جهاز التوجيه sun إلى Solaris. نقوم بإرسال مخططات البيانات إلى خدمة التجاهل القياسية، حيث سيتم تجاهلها:

>بسدي % جورب -u -i -w1024 -n100 سولاريس تجاهل

الشكل 11.18 خطأ في منع مصدر ICMP.

يوضح الشكل 11.19 مخرجات tcpdump المقابلة لهذا الأمر.

>

1 0.0 bsdi.1403 > سولاريس.تجاهل: udp 1024
26 سطراً غير معروضة
27 0.10 (0.00) bsdi.1403 > سولاريس.تجاهل: udp 1024
28 0.11 (0.01) شمس> bsdi: icmp: إخماد المصدر

29 0.11 (0.00) bsdi.1403 > سولاريس.تجاهل: udp 1024
30 0.11 (0.00) شمس> bsdi: icmp: إخماد المصدر
142 سطرًا غير معروضة
173 0.71 (0.06) bsdi.1403 > سولاريس.تجاهل: udp 1024
174 0.71 (0.00) الشمس > bsdi: icmp: إخماد المصدر

الشكل 11.19 منع مصدر ICMP من جهاز التوجيه Sun.

لقد قمنا بإزالة العديد من الأسطر من هذا الإخراج. تم استلام أول 26 مخطط بيانات بدون أخطاء: لقد أظهرنا فقط مخرجات المخطط الأول. بدءًا من مخطط البيانات السابع والعشرين، في كل مرة يتم فيها إرسال مخطط بيانات، نتلقى خطأً في منع المصدر. كان هناك 26+(74x2)=174 سطرًا من المخرجات إجمالاً.

من حساب عرض النطاق الترددي للخط التسلسلي في الفصل 2، يمكننا أن نرى أن الأمر سيستغرق أكثر من ثانية واحدة لإرسال مخطط بيانات 1024 بايت بسرعة 9600 بت في الثانية. (في مثالنا، سيستغرق هذا وقتًا أطول لأن مخطط البيانات المكون من 20+8+1024 بايت سيتم تجزئة لأن وحدة الإرسال الكبرى (MTU) لارتباط SLIP من sun إلى netb هي 552 بايت.) ومع ذلك، من الأوقات الموضحة في الشكل 11.19، نرى استقبل جهاز التوجيه Sun جميع مخططات البيانات المائة في أقل من ثانية واحدة قبل إرسال المخطط الأول على قناة SLIP. من الواضح أننا استخدمنا العديد من مخازنها المؤقتة.

على الرغم من أن RFC 1009 يتطلب من جهاز التوجيه إنشاء أخطاء لقمع المصدر عندما تصبح مخازنه المؤقتة ممتلئة، إلا أن متطلبات جهاز التوجيه RFC الجديدة [Almquist 1993] تغير هذا وتنص على أن جهاز التوجيه يجب ألا يقوم بإنشاء أخطاء لقمع المصدر.

النقطة التالية التي يجب ملاحظتها في المثال هي أن برنامج الجورب لم يتلق أبدًا إشعارات تفيد بمنع المصدر، أو إذا حدث ذلك، فإنه تجاهلها. يشير هذا إلى أن تطبيقات BSD عادةً ما تتجاهل رسائل منع المصدر المستلمة عند استخدام بروتوكول UDP. (في حالة TCP، عند تلقي إشعار، يتباطأ نقل البيانات على الاتصال الذي تم إنشاء منع المصدر له. سنناقش هذا في قسم الفصل 21.) المشكلة هي أن العملية التي ولدت البيانات وهذا بدوره يتسبب في أن يكون مصدر المنع قد اكتمل بالفعل عند تلقي رسالة المنع المصدر. في الواقع، إذا استخدمنا برنامج يونكس الزمني لتقدير المدة التي استغرقها تشغيل برنامج الجورب، نجد أنه تم تشغيله لمدة 0.5 ثانية فقط. ومع ذلك، رأينا في الشكل 11.19 أنه تم استلام بعض رسائل منع المصدر بعد 0.71 ثانية من إرسال مخطط البيانات الأول، أي بعد توقف العملية عن التشغيل. ماذا يحدث إذا قام برنامجنا بإنتاج 100 مخطط بيانات وخرج. لن يتم إرسال جميع مخططات البيانات المائة - سيكون بعضها في قائمة انتظار الإخراج.

يثبت هذا المثال أن UDP بروتوكول غير موثوق به ويظهر أهمية التحكم في التدفق. على الرغم من أن برنامج Sock نجح في إرسال 100 مخطط بيانات إلى الشبكة، إلا أن 26 منها فقط وصلت إلى وجهتها. على الأرجح تم التخلص من الـ 74 المتبقية بواسطة جهاز التوجيه الوسيط. إذا كان التطبيق لا يدعم بعض أشكال الإشعارات، فلن يعرف المرسل ما إذا كان المستلم قد قبل البيانات أم لا.

خادم يو دي بي

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

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

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

عنوان IP للعميل ورقم المنفذ

يصل مخطط بيانات UDP من العميل. يحتوي رأس IP على عناوين IP المصدر والوجهة، ويحتوي رأس UDP على أرقام منفذ UDP المصدر والوجهة. عندما يتلقى أحد التطبيقات مخطط بيانات UDP، يجب أن يخبره نظام التشغيل بمن أرسل الرسالة - عنوان IP المصدر ورقم المنفذ.

تسمح هذه الخاصية لخادم UDP واحد بالتعامل مع عملاء متعددين. يتم إرسال كل رد إلى العميل الذي أرسل الطلب.

عنوان IP الوجهة

تحتاج بعض التطبيقات إلى معرفة من هو مخطط البيانات، أي عنوان IP الوجهة. على سبيل المثال، يحدد RFC لمتطلبات المضيف أن خادم TFTP يجب أن يتجاهل مخططات البيانات المستلمة التي يتم إرسالها إلى عنوان البث. (سنقوم بوصف عنونة البث في ، و TFTP في .)

وهذا يعني أن نظام التشغيل يجب أن يمرر عنوان IP الوجهة من مخطط بيانات UDP المستلم إلى التطبيق. لسوء الحظ، ليس كل التطبيقات توفر هذه الميزة.

توفر واجهة برمجة تطبيقات مآخذ التوصيل هذه الإمكانية باستخدام خيار IP_RECVDSTADDR. من بين الأنظمة التي يغطيها النص، فقط BSD/386 و4.4BSD وAIX 3.2.2 يدعم هذا الخيار. SVR4 وSunOS 4.x وSolaris 2.x غير مدعومين.

قائمة انتظار إدخال UDP

قلنا في الفصل الأول أن معظم خوادم UDP يمكنها خدمة جميع الطلبات للعملاء باستخدام منفذ UDP واحد (منافذ الخادم المعروفة مسبقًا).

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

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

>

بسدى % جورب -s -u -v -E -R256 -r256 -P30 6666
من 140.252.13.33 إلى 140.252.13.63: 1111111111 من الشمس إلى عنوان البث
من 140.252.13.34 إلى 140.252.13.35: 4444444444444 من svr4 إلى العنوان الشخصي

استخدمنا العلامات التالية: -s، يقوم بتشغيل البرنامج كخادم، -u لـ UDP، -v، يطبع عنوان IP الخاص بالعميل، و -E يطبع عنوان IP الوجهة (في هذه الحالة يسمح النظام بذلك). بالإضافة إلى ذلك، قمنا بتعيين المخزن المؤقت لتلقي UDP لهذا المنفذ على 256 بايت ( -R)، بالإضافة إلى الحجم الذي يمكن قراءته بواسطة كل تطبيق ( -r). تخبرك العلامة -P30 بالانتظار لمدة 30 ثانية بعد مسح منفذ UDP قبل قراءة مخطط البيانات الأول. وهذا يمنحنا الوقت لبدء العملاء على مضيفين آخرين، وإرسال بعض مخططات البيانات ومعرفة كيفية عمل قائمة انتظار الاستلام.

بعد بدء تشغيل الخادم ومرور فترة توقف مدتها 30 ثانية، نبدأ تشغيل عميل واحد على شمس المضيف ونرسل ثلاث مخططات بيانات:

>

شمس ٪ جورب -u -v 140.252.13.63 6666إلى عنوان البث إيثرنت
متصل على 140.252.13.33.1252 إلى 140.252.13.63.6666
1111111111 11 بايت من البيانات (مع السطر الجديد)
222222222 10 بايت من البيانات (مع السطر الجديد)
33333333333 12 بايت من البيانات (مع السطر الجديد)

عنوان الوجهة هو عنوان البث (140.252.13.63). ثم بدأنا عميلاً آخر على مضيف svr4 وأرسلنا ثلاث مخططات بيانات أخرى:

>

SVR4% جورب -u -v bsdi 6666
متصل على 0.0.0.0.1042 إلى 140.252.13.35.6666
4444444444444 14 بايت من البيانات (مع السطر الجديد)
555555555555555 16 بايت من البيانات (مع السطر الجديد)
66666666 9 بايت من البيانات (مع السطر الجديد)

أول شيء يجب ملاحظته في المخرجات التفاعلية الموضحة سابقًا على bsdi هو أنه تم استلام مخططي بيانات فقط بواسطة التطبيق: الأولى من الشمس، والتي كانت كلها آحاد، والأولى من svr4، والتي كانت جميعها أربع. تم تجاهل مخططات البيانات الأربعة المتبقية.

يوضح إخراج الأمر tcpdump في الشكل 11.20 أنه تم تسليم جميع مخططات البيانات الستة إلى المضيف الوجهة. وصلت مخططات البيانات من عميلين بترتيب عكسي: أولاً من Sun، ثم من svr4 وهكذا. يمكننا أيضًا ملاحظة أنه تم تسليم الستة جميعها في حوالي 12 ثانية، خلال فترة 30 ثانية أثناء نوم الخادم.

>

1 0.0 sun.1252 > 140.252.13.63.6666: UDP 11
2 2.499184 (2.4992) svr4.1042 > bsdi.6666: UDP 14
3 4.959166 (2.4600) sun.1252 > 140.252.13.63.6666: UDP 10
4 7.607149 (2.6480) svr4.1042 > bsdi.6666: UDP 16
5 10.079059 (2.4719) sun.1252 > 140.252.13.63.6666: UDP 12
6 12.415943 (2.3369) svr4.1042 > bsdi.6666: UDP 9

الشكل 11.20: إخراج tcpdump لمخططات بيانات UDP المرسلة من قبل عميلين.

تجدر الإشارة أيضًا إلى أنه باستخدام الخيار -E، يمكن للخادم معرفة عنوان IP الوجهة لكل مخطط بيانات. يمكن للخادم اختيار ما يجب فعله بمخطط البيانات المستلم الأول الذي تم إرساله إلى عنوان البث.

في هذا المثال، من الضروري الانتباه إلى بعض الميزات الإضافية. أولاً، لم يُبلغ التطبيق عن امتلاء قائمة انتظار الإدخال. تم ببساطة تجاهل مخططات بيانات UDP الإضافية. نرى أيضًا في مخرجات tcpdump أنه لم يتم إرسال أي شيء مرة أخرى إلى العميل لإبلاغه بأنه تم تجاهل مخططات البيانات. لم يتم إرسال أي شيء يشبه رسالة منع مصدر ICMP، لا شيء على الإطلاق. أخيرًا، نود أن نشير إلى أن قائمة انتظار إدخال UDP تعمل على أساس FIFO (الوارد أولاً يخرج أولاً)، بينما، كما رأينا في قسم هذا الفصل، تعمل قائمة انتظار إدخال ARP على أساس LIFO (آخر ما يدخل، أولًا) الأساس.

قيود عنوان IP المحلي

تستخدم معظم خوادم UDP أحرف البدل لعناوين IP الخاصة بها عند إنشاء نقاط نهاية UDP. وهذا يعني أنه سيتم قبول مخطط بيانات UDP الوارد المخصص لمنفذ الخادم بواسطة أي واجهة محلية. على سبيل المثال، يمكننا تشغيل خادم UDP على المنفذ 7777:

>الشمس % جورب -u -s 7777

سنستخدم بعد ذلك الأمر netstat لعرض حالة نقطة النهاية هذه:

>

شمس ٪ netstat -a -n -f آينت
اتصالات الإنترنت النشطة (بما في ذلك الخوادم)
Proto Recv-Q Send-Q العنوان المحلي العنوان الخارجي (الولاية)
يو دي بي 0 0 *.7777 *.*

في هذا الإخراج، قمنا بإزالة العديد من الأسطر وتركنا فقط تلك التي تهمنا. تقوم العلامة -a بالإبلاغ عن كافة نقاط نهاية الشبكة. تقوم العلامة -n بطباعة عناوين IP بالتدوين العشري بدلاً من استخدام DNS وتحويل العناوين إلى أسماء، وتطبع أرقام المنافذ بدلاً من أسماء الخدمات. يقوم الخيار -f inet بالإبلاغ عن نقاط نهاية TCP وUDP فقط.

تتم طباعة العنوان المحلي كـ *.7777، حيث تعني العلامة النجمية أنه يمكن استبدال أي عنوان كعنوان IP المحلي.

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

>الشمس % جورب -u -s 140.252.1.29 7777

من مخططات البيانات التي تصل إلى واجهة SLIP، يختار مخططات بيانات بالعنوان 140.252.1.29. سيبدو إخراج أمر netstat كما يلي:

>


UDP 0 0 140.252.1.29.7777 *.*

إذا حاولنا إرسال مخطط بيانات إلى هذا الخادم من المضيف bsdi، وعنوانه 140.252.13.35، عبر Ethernet، فسيتم إرجاع خطأ ICMP بشأن عدم توفر المنفذ. لن يرى الخادم مخطط البيانات هذا أبدًا. ويبين الشكل 11.21 هذا بمزيد من التفصيل.

>

1 0.0 bsdi.1723 > sun.7777: udp 13
2 0.000822 (0.0008) الشمس> bsdi: icmp: منفذ sun udp 7777 غير قابل للوصول

الشكل 11.21 فشل معالجة مخطط بيانات UDP بسبب عدم تطابق العنوان المحلي للخادم.

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

يجب تحديد خيار مأخذ التوصيل في SO_REUSEADDR API. يتم ذلك عن طريق برنامج الجورب الخاص بنا باستخدام الخيار -A.

على مضيف الشمس يمكننا تشغيل خمسة خوادم مختلفة على نفس منفذ UDP (8888):

>

شمس ٪ جورب -u -s 140.252.1.29 8888لقناة SLIP
شمس ٪ جورب -u -s -A 140.252.13.33 8888للإيثرنت
شمس ٪ جورب -u -s -A 127.0.0.1 8888لواجهة الاسترجاع
شمس ٪ جورب -u -s -A 140.252.13.63 8888لطلبات بث إيثرنت
شمس ٪ جورب -u -s -A 8888لكل شيء آخر (الأحرف الأولية في عنوان IP)

كان من المتوقع أن يبدأ أول الخوادم بالعلامة -A، التي تخبر النظام أنه يمكن إعادة استخدام نفس رقم المنفذ. يُظهر إخراج أمر netstat الخوادم الخمسة التالية:

>

Proto Recv-Q Send-Q العنوان المحلي العنوان الخارجي (الولاية)
UDP 0 0 *.8888 *.*
UDP 0 0 140.252.13.63.8888 *.*
يو دي بي 0 0 127.0.0.1 8888 *.*
يو دي بي 0 0 140.252.13.33 8888 *.*
يو دي بي 0 0 140.252.1.29 8888 *.*

في هذا السيناريو، ستصل مخططات البيانات المخصصة للعنوان 140.252.1.255 فقط إلى الخادم باستخدام أحرف البدل المستخدمة كعنوان IP المحلي، لأن الخوادم الأربعة الأخرى تغطي جميع العناوين المحتملة.

عند استخدام استبدال عنوان IP، يتم استخدام نظام الأولوية. سيتم دائمًا اختيار نقطة نهاية بعنوان IP محدد يطابق عنوان IP الوجهة قبل عنوان حرف البدل. يتم استخدام نقطة نهاية حرف البدل فقط في حالة عدم العثور على تطابق للعنوان المحدد.

الحد من عناوين IP الخارجية

في جميع مخرجات أمر netstat الذي عرضناه سابقًا، تظهر عناوين IP البعيدة وأرقام المنافذ البعيدة على شكل *.*. وهذا يعني أن نقطة النهاية ستقبل مخططات بيانات UDP الواردة من أي عنوان IP وأي رقم منفذ. تسمح معظم التطبيقات لنقاط نهاية UDP بتقييد العناوين البعيدة.

بمعنى آخر، يمكن لنقطة النهاية قبول مخططات بيانات UDP فقط من عنوان IP ورقم المنفذ المحددين. يستخدم برنامج الجورب الخاص بنا الخيار -f لتحديد عنوان IP البعيد ورقم المنفذ:

>الشمس % جورب -u -s -f 140.252.13.35.4444 5555

في هذه الحالة، يتم تعيين عنوان IP البعيد على 140.252.13.35 (مضيفنا bsdi)، ورقم المنفذ البعيد على 4444. منفذ الخادم المعروف مسبقًا هو 5555. إذا قمنا بتشغيل netstat، فسنرى أن عنوان IP المحلي تم ضبطه أيضًا، حتى لو لم نحدده بشكل مباشر:

>

Proto Recv-Q Send-Q العنوان المحلي العنوان الخارجي (الولاية)
يو دي بي 0 0 140.252.13.33.5555 140.252.13.35.4444

يعد هذا أحد الآثار الجانبية لتحديد عنوان IP بعيد ورقم منفذ بعيد على أنظمة Berkeley: إذا لم يتم تحديد عنوان IP محلي عند تعيين العنوان البعيد، فسيتم تحديد العنوان المحلي تلقائيًا. يتم تعيين عنوان IP المحلي على عنوان IP الخاص بالواجهة التي تم تحديدها باستخدام توجيه IP للوصول إلى عنوان IP البعيد المحدد. في الواقع، في هذا المثال، عنوان IP الخاص بشبكة Ethernet المتصلة بالعنوان البعيد هو 140.252.13.33.

يوضح الشكل 11.22 الأنواع الثلاثة من العناوين والمنافذ التي يمكن لخادم UDP تعيينها لنفسه.

الشكل 11.22 تحديد عناوين IP المحلية والبعيدة ورقم المنفذ لخادم UDP.

في جميع الحالات، يكون lport هو المنفذ المعروف مسبقًا للخادم، ويجب أن يكون localIP هو عنوان IP الخاص بالواجهة المحلية. يُظهر الترتيب الذي تم ترتيب الأسطر الثلاثة به في الشكل 11.22 الترتيب الذي تحاول به وحدة UDP تحديد نقطة النهاية المحلية التي استقبلت مخطط بيانات واردًا. يتم تحديد اقتران العنوان بالمنفذ الأكثر تقييدًا (السطر الأول) أولاً، ويتم تحديد الرابط الأقل تقييدًا (السطر الأخير، حيث يتم تحديد كل من عنوان IP ورقم المنفذ كأحرف أولية) أخيرًا.

استقبال متعدد لكل منفذ

على الرغم من عدم تحديده في RFC، تسمح معظم التطبيقات بربط تطبيق واحد فقط في كل مرة بعنوان محلي واحد ورقم منفذ UDP واحد. عندما يصل مخطط بيانات UDP إلى المضيف الوجهة على عنوان IP ورقم المنفذ الخاص به، يتم تسليم نسخة واحدة إلى نقطة نهاية واحدة. يمكن تمثيل عنوان IP الخاص بنقطة النهاية كأحرف بدل، كما هو موضح سابقًا.

على سبيل المثال، في SunOS 4.1.3، قمنا بتشغيل خادم واحد على المنفذ 9999 بعنوان IP محلي على شكل أحرف بدل:

>الشمس % جورب -u -s 9999

إذا حاولنا بعد ذلك تشغيل خادم آخر بنفس عنوان حرف البدل المحلي ونفس المنفذ، فلن ينجح الأمر، حتى لو قمنا بتحديد الخيار -A:

>

شمس ٪ جورب -u -s 9999لا ينبغي أن يحدث بهذه الطريقة
لا يمكن ربط العنوان المحلي: العنوان قيد الاستخدام بالفعل

شمس ٪ جورب -u -s -A 9999لهذا السبب حددنا العلامة -A
لا يمكن ربط العنوان المحلي: العنوان قيد الاستخدام بالفعل

بالنسبة للأنظمة التي تدعم البث المتعدد (انظر)، الأمور مختلفة. يمكن أن تستخدم نقاط النهاية المتعددة نفس العنوان المحلي ورقم منفذ UDP، ولكن يجب أن يخبر التطبيق واجهة برمجة التطبيقات (API) بأن هذا مسموح به (تستخدم العلامة -A خيار مأخذ التوصيل SO_REUSEADDR).

4.4BSD، الذي يدعم البث المتعدد، يتطلب من التطبيق تعيين خيار مأخذ توصيل مختلف (SO_REUSEPORT) للسماح لنقاط نهاية متعددة بمشاركة نفس المنفذ. علاوة على ذلك، يجب أن تحدد كل نقطة نهاية هذا الخيار، بما في ذلك نقطة النهاية الأولى التي تستخدم هذا المنفذ.

عندما يصل مخطط بيانات UDP إلى عنوان IP الوجهة الخاص به، وهو عنوان بث أو بث متعدد، وتوجد نقاط نهاية متعددة مرتبطة بعنوان IP ورقم المنفذ، يتم إرسال نسخة من مخطط البيانات الوارد إلى كل نقطة نهاية. (يمكن تحديد عنوان IP المحلي لنقطة النهاية كأحرف بدل وسيطابق أي عنوان IP للوجهة.) ومع ذلك، إذا كان مخطط بيانات IP القادم يحتوي على عنوان IP للوجهة وهو عنوان خاص، فسيتم تسليم نسخة واحدة فقط من مخطط البيانات إلى نقطة نهاية واحدة. تعتمد نقطة النهاية التي تستقبل مخطط بيانات العنوان الشخصي على التنفيذ.

استنتاجات موجزة

UDP هو بروتوكول بسيط. المواصفات الرسمية RFC 768 [Postel 1980] يبلغ طولها ثلاث صفحات فقط. الخدمات التي تقدمها لعمليات المستخدم فوق وخلف IP هي أرقام المنافذ والمجاميع الاختبارية الاختيارية. استخدمنا UDP لمراجعة حسابات المجموع الاختباري ومعرفة كيفية عمل التجزئة.

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

لقد تحققنا من أنه يمكن إرسال خطأ منع مصدر ICMP بواسطة نظام يتلقى مخططات بيانات IP بشكل أسرع مما يمكنه معالجته. من الممكن إنشاء أخطاء ICMP هذه بسهولة باستخدام UDP.

تمارين

  1. في قسم هذا الفصل، قمنا بتجزئة شبكة الإيثرنت عن طريق كتابة مخطط بيانات UDP بحجم بيانات مستخدم يبلغ 1473 بايت. ما هو أصغر حجم لبيانات المستخدم الذي يمكن أن يتسبب في تجزئة شبكة Ethernet في حالة استخدام تغليف IEEE 802 (الفصل 2، القسم)؟
  2. اقرأ RFC 791 [Postel 1981a] وأخبرني لماذا يجب أن يكون طول جميع الأجزاء باستثناء الجزء الأخير مضاعفًا لـ 8 بايت.
  3. تخيل مخطط بيانات Ethernet وUDP يحتوي على 8192 بايت من بيانات المستخدم. كم عدد الأجزاء التي سيتم نقلها وما هو طول الإزاحة لكل جزء؟
  4. بالاستمرار في المثال السابق، تخيل أنه يتم بعد ذلك نقل مخططات البيانات هذه إلى قناة SLIP مع وحدة MTU تبلغ 552. عليك أن تتذكر أن كمية البيانات في كل جزء (كل شيء باستثناء رأس IP) يجب أن تكون من مضاعفات 8 بايت. ما هو عدد الأجزاء المنقولة وما هو إزاحة كل جزء وطوله؟
  5. يرسل التطبيق الذي يستخدم UDP مخطط بيانات مجزأ إلى 4 أجزاء. تخيل أن الجزأين 1 و2 وصلا إلى وجهتهما، في حين فُقدت الجزأتان 3 و4. تنتهي مهلة التطبيق ثم، بعد 10 ثوانٍ، يعيد إرسال مخطط بيانات UDP. يتم تجزئة مخطط البيانات هذا بنفس طريقة الإرسال الأول (نفس الإزاحة ونفس الطول). تخيل الآن أن الجزأين 1 و2 مفقودان، لكن الجزأين 3 و4 يصلان إلى وجهتهما. تم ضبط مؤقت إعادة التجميع على المضيف المتلقي على 60 ثانية، لذلك عندما وصلت الجزأتان 3 و4 إلى وجهتهما النهائية، لم يتم التخلص من الجزأين 1 و2 من الإرسال الأول بعد. هل يستطيع المستلم تجميع مخطط بيانات IP من هذه الأجزاء الأربعة؟
  6. كيف يمكنك معرفة أن الأجزاء الموجودة في الشكل 11.15 تتوافق فعليًا مع الصفين 5 و6 في الشكل 11.14؟
  7. )، توجيه المصدر الثابت والفضفاض (الفصل 8، القسم). كيف تعتقد أنه يتم التعامل مع هذه الخيارات أثناء التجزئة؟ قارن إجابتك مع RFC 791.
  8. قلنا أنه يتم إلغاء تعدد إرسال مخططات بيانات IP الواردة بناءً على رقم منفذ UDP الوجهة. هل هذا صحيح؟

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

مقدمة

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

تعتبر بنية الإنترنت هذه صحيحة بما يكفي للتفاعل بين العميل والخادم، عندما يمكن تحديد موقع العملاء على شبكات خاصة، ويكون للخوادم عنوان عالمي. لكنه يخلق صعوبات في الاتصال المباشر بين العقدتين متنوعالشبكات الخاصة. يعد الاتصال المباشر بين عقدتين أمرًا مهمًا لتطبيقات نظير إلى نظير مثل النقل الصوتي (Skype) أو الوصول إلى الكمبيوتر عن بعد (TeamViewer) أو الألعاب عبر الإنترنت.

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

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

في هذه الحالة، لضمان تسليم الحزم بشكل مضمون، من الضروري تنفيذ بروتوكول على مستوى التطبيق يوفر الوظائف الضرورية ويعمل فوق UDP.

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

متطلبات البروتوكول

  1. تسليم موثوق للحزم يتم تنفيذه من خلال آلية ردود الفعل الإيجابية (ما يسمى الإقرار الإيجابي)
  2. الحاجة إلى النقل الفعال للبيانات الضخمة، أي. يجب أن يتجنب البروتوكول عمليات إعادة إرسال الحزم غير الضرورية
  3. يجب أن يكون من الممكن تجاوز آلية تأكيد التسليم (القدرة على العمل كبروتوكول UDP "خالص")
  4. إمكانية تنفيذ وضع الأوامر مع تأكيد كل رسالة
  5. الوحدة الأساسية لنقل البيانات بموجب البروتوكول يجب أن تكون رسالة
هذه المتطلبات هي إلى حد كبير نفس متطلبات بروتوكول البيانات الموثوقة الموضحة في RFC 908 وRFC 1151، وقد استندت في تطوير هذا البروتوكول إلى هذه المعايير.

لفهم هذه المتطلبات، دعونا نلقي نظرة على المخططات الزمنية لنقل البيانات بين عقدتين للشبكة باستخدام بروتوكولي TCP وUDP. دعونا نفقد حزمة واحدة في كلتا الحالتين.

نقل البيانات غير التفاعلية عبر TCP:


كما ترون من الرسم البياني، في حالة فقدان الحزمة، سيكتشف TCP الحزمة المفقودة ويبلغ المرسل بها عن طريق طلب رقم القطعة المفقودة.

نقل البيانات عبر بروتوكول UDP:



لا يتخذ UDP أي خطوات للكشف عن الخسائر. تقع مسؤولية التحكم في أخطاء الإرسال في بروتوكول UDP بالكامل على عاتق التطبيق.

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

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

يمكن العثور على مزيد من التفاصيل حول بروتوكول TCP في rfc 793، مع UDP في rfc 768، حيث تم تعريفهما في الواقع.

مما سبق، من الواضح أنه من أجل إنشاء بروتوكول تسليم رسائل موثوق به عبر UDP (سنسميه فيما يلي UDP موثوق)، فمن الضروري تنفيذ آليات نقل البيانات المشابهة لـ TCP. يسمى:

  • حفظ حالة الاتصال
  • استخدام ترقيم القطعة
  • استخدم حزم التأكيد الخاصة
  • استخدام آلية نافذة مبسطة لزيادة إنتاجية البروتوكول
بالإضافة إلى ذلك، مطلوب:
  • الإشارة إلى بداية الرسالة لتخصيص الموارد للاتصال
  • الإشارة إلى نهاية الرسالة، لتمرير الرسالة المستلمة إلى التطبيق الرئيسي وتحرير موارد البروتوكول
  • السماح لبروتوكول خاص بالاتصال بتعطيل آلية إقرار التسليم لتعمل كـ UDP "خالص".

رأس UDP موثوق

تذكر أن مخطط بيانات UDP مغلف ضمن مخطط بيانات IP. وبالتالي يتم "تغليف" حزمة UDP الموثوقة في مخطط بيانات UDP.

تغليف رأس UDP موثوق به:



هيكل رأس UDP الموثوق به بسيط للغاية:

  • الأعلام – أعلام التحكم في الحزمة
  • نوع الرسالة – ​​نوع الرسالة، الذي تستخدمه التطبيقات الأولية للاشتراك في رسائل محددة
  • TransmissionId - رقم الإرسال، بالإضافة إلى عنوان ومنفذ المستلم، يحدد الاتصال بشكل فريد
  • رقم الحزمة - رقم الحزمة
  • الخيارات – خيارات البروتوكول الإضافية. وفي حالة الحزمة الأولى، يتم استخدامها للإشارة إلى حجم الرسالة
الأعلام هي كما يلي:
  • FirstPacket - الحزمة الأولى من الرسالة
  • NoAsk - لا تتطلب الرسالة تفعيل آلية التأكيد
  • LastPacket - حزمة الرسائل الأخيرة
  • RequestForPacket - حزمة التأكيد أو طلب الحزمة المفقودة

المبادئ العامة للبروتوكول

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

يتم استخدام آلية مماثلة لإكمال الاتصال. يتم تعيين علامة LastPacket في حزمة الرسائل الأخيرة. تشير حزمة الاستجابة إلى رقم الحزمة الأخيرة + 1، وهو ما يعني بالنسبة للجانب المتلقي تسليم الرسالة بنجاح.

رسم تخطيطي لإنشاء الاتصال وإنهائه:



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

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

مخطط إعادة الإرسال:


مهلات البروتوكول والموقتات

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

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

مخطط حالة نقل UDP الموثوق به

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

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

إرسال الحزمة الأولى- الحالة الأولية التي يكون فيها الاتصال الصادر عند إرسال الرسالة.

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

SendingCycle- الحالة الأساسية لإرسال رزم الرسائل.

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

تم استلام الحزمة الأولى- الحالة الأولية لمستلم الرسالة.

فهو يتحقق من صحة بداية الإرسال، وينشئ الهياكل اللازمة، ويرسل إقرارًا باستلام الحزمة الأولى.

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

تجميع- الحالة الأساسية لتلقي حزم الرسائل.

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

مكتمل– إغلاق الاتصال في حالة استلام الرسالة بأكملها بنجاح.

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

أعمق في الكود. وحدة التحكم في ناقل الحركة

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

بعض أعضاء فئة ReliableUdpConnectionControlBlock:

فئة داخلية ReliableUdpConnectionControlBlock: IDisposable ( // مصفوفة بايت للمفتاح المحدد. تستخدم لتجميع الرسائل الواردة public ConcurrentDictionary ، بايت> IncomingStreams ( get؛ مجموعة خاصة؛) // صفيف بايت للمفتاح المحدد. يستخدم لإرسال الرسائل الصادرة. القاموس المتزامن العام ، بايت> OutcomingStreams ( get؛ مجموعة خاصة؛ ) // سجل الاتصال للمفتاح المحدد. القاموس المتزامن الخاص للقراءة فقط ReliableUdpConnectionRecord> m_listOfHandlers; // قائمة المشتركين في الرسالة. قائمة خاصة للقراءة فقط m_subscribers; // مأخذ توصيل محلي مقبس خاص m_socketIn; // منفذ للرسائل الواردة Private int m_port; // عنوان IP المحلي IPAddress الخاص m_ipAddress; // نقطة النهاية المحلية public IPEndPoint LocalEndpoint ( get; Private set; ) // مجموعة حالات // جهاز الحالة التي تمت تهيئتها مسبقًا public StatesCollection States ( get; Private set; ) // مولد أرقام عشوائية. يُستخدم لإنشاء TransmissionId الخاص للقراءة فقط RNGCryptoServiceProvider m_randomCrypto؛ //...)


تنفيذ خادم UDP غير متزامن:

تلقي باطل خاص () (EndPoint ConnectClient = new IPEndPoint (IPAddress.Any، 0)؛ // إنشاء مخزن مؤقت جديد لكل مقبس.BeginReceiveFrom بايت المخزن المؤقت = بايت جديد؛ // تمرير المخزن المؤقت كمعلمة للطريقة غير المتزامنة this. m_socketIn.BeginReceiveFrom(buffer, 0, buffer.Length,ocketFlags.None, refconnectClient, EndReceive, buffer); Private void EndReceive(IAsyncResult ar) (EndPointconnectClient = new IPEndPoint(IPAddress.Any, 0); int bytesRead = this. m_socketIn .EndReceiveFrom(ar, refconnectClient); // تم استلام الحزمة، وهي جاهزة لاستقبال التلقي التالي () // لأن أبسط طريقة لحل مشكلة المخزن المؤقت هي الحصول على رابط لها // من IAsyncResult.AsyncState بايت bytes = ((byte ) ar.AsyncState).Slice(0, bytesRead); // الحصول على رأس الحزمة ReliableUdpHeader header; if (!ReliableUdpStateTools.ReadReliableUdpHeader(bytes, out header)) (// وصلت حزمة غير صحيحة - تجاهلها return;) // إنشاء المفتاح لتحديد سجل الاتصال لحزمة Tuple المفتاح = Tuple الجديد (connectedClient, header.TransmissionId); // احصل على سجل اتصال موجود أو قم بإنشاء سجل جديد ReliableUdpConnectionRecord Record = m_listOfHandlers.GetOrAdd(key, new ReliableUdpConnectionRecord(key, this, header.ReliableUdpMessageType)); // إطلاق الحزمة للمعالجة في جهاز الحالة Record.State.ReceivePacket(record, header, bytes); )


لكل إرسال رسالة، يتم إنشاء بنية تحتوي على معلومات الاتصال. ويسمى هذا الهيكل سجل الاتصال.

بعض أعضاء فئة ReliableUdpConnectionRecord:

فئة داخلية ReliableUdpConnectionRecord: IDisposable ( // صفيف بايت مع الرسالة البايت العام IncomingStream ( get; set; ) // مرجع إلى حالة آلة الحالة المحدودة public ReliableUdpState State ( get; set; ) // زوج يحدد بشكل فريد سجل الاتصال // في كتلة التحكم ينقل Tuple العام المفتاح (get؛ مجموعة خاصة؛) // الحد الأدنى لنافذة الاستقبال public int WindowLowerBound; // نقل حجم النافذة للقراءة العامة فقط int WindowSize; // رقم الحزمة المراد إرساله بشكل عام int SndNext; // عدد الحزم التي سيتم إرسالها بشكل عام int NumberOfPackets; // رقم الإرسال (هذا هو الجزء الثاني من Tuple) // لكل رسالة معرف النقل Int32 TransmissionId الخاص بها للقراءة فقط؛ // نقطة نهاية IP البعيدة - المستلم الفعلي للرسالة العامة للقراءة فقط IPEndPoint RemoteClient؛ // حجم الحزمة، لتجنب التجزئة على مستوى IP // يجب ألا يتجاوز MTU - (IP.Header + UDP.Header + RelaibleUDP.Header) public readonly int BufferSize; // كتلة التحكم في الإرسال عامة للقراءة فقط ReliableUdpConnectionControlBlock Tcb; // يلخص نتائج عملية غير متزامنة لـ BeginSendMessage/EndSendMessage للقراءة العامة فقط AsyncResultSendMessage AsyncResult; // لا ترسل حزم التأكيد public bool IsNoAnswerNeeded; // آخر حزمة تم استلامها بشكل صحيح (يتم تعيينها دائمًا على أعلى رقم) public int RcvCurrent; // مصفوفة بأعداد الحزم المفقودة public int LostPackets ( get; Private set; ) // ما إذا كانت الحزمة الأخيرة قد وصلت أم لا. تستخدم كبول. int العام IsLastPacketReceived = 0; //...)

أعمق في الكود. تنص على

تنفذ الدول جهاز الحالة الخاص ببروتوكول UDP الموثوق، والذي تتم فيه المعالجة الرئيسية للحزم. توفر الفئة المجردة ReliableUdpState واجهة للحالة:

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

طريقة DisposeByTimeout

تعد طريقة DisposeByTimeout مسؤولة عن تحرير موارد الاتصال عند انتهاء المهلة والإشارة إلى نجاح/فشل تسليم الرسالة.

ريليابليودبستات.ديسبوسيبيتيميوت:

الفراغ الظاهري المحمي DisposeByTimeout (سجل الكائن) ( ReliableUdpConnectionRecord ConnectionRecord = (ReliableUdpConnectionRecord) سجل؛ إذا (record.AsyncResult != null) (connectionRecord.AsyncResult.SetAsCompleted(false); ) ConnectionRecord.Dispose(); )


ولا يتم تجاوزه إلا في الدولة مكتمل.

مكتمل.التخلص من المهلة:

تجاوز محمي void DisposeByTimeout(سجل الكائن) (ReliableUdpConnectionRecord ConnectionRecord = (ReliableUdpConnectionRecord) سجل؛ // الإبلاغ عن الاستلام الناجح للرسالة SetAsCompleted(connectionRecord);)

طريقة حزم العمليات

تعد طريقة ProcessPackets مسؤولة عن المعالجة الإضافية للحزمة أو الحزم. يتم الاتصال به مباشرة أو من خلال مؤقت انتظار الحزمة.

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

تجميع.حزم العملية:

التجاوز العام void ProcessPackets(ReliableUdpConnectionRecord ConnectionRecord) ( if (connectionRecord.IsDone != 0) return; if (!ReliableUdpStateTools.CheckForNoPacketLoss(connectionRecord, ConnectionRecord.IsLastPacketReceived != 0)) ( // هناك حزم مفقودة، أرسل طلبات لها لكل منها ( int seqNum in ConnectionRecord.LostPackets) ( if (seqNum != 0) ( ReliableUdpStateTools.SendAskForLostPacket(connectionRecord, seqNum); ) ) // اضبط المؤقت مرة ثانية لإعادة محاولة النقل if (!connectionRecord.TimerSecondTry) (connectionRecord. WaitForPacketsTimer .Change(connectionRecord.ShortTimerPeriod, -1); ConnectionRecord.TimerSecondTry = true; return; = 0) // فحص ناجح ( // إرسال تأكيد استلام كتلة البيانات ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord); ConnectionRecord.State = ConnectionRecord.Tcb.States.Completed; ConnectionRecord.State.ProcessPackets(connectionRecord); // بدلاً من التنفيذ الفوري للموارد // نبدأ مؤقتًا في حالة // عدم وصول الإقرار الأخير إلى المرسل وطلبه مرة أخرى. // عند تشغيل المؤقت - نقوم بتنفيذ الموارد // في الحالة المكتملة، يتم تجاوز طريقة المؤقت StartCloseWaitTimer(connectionRecord); ) // هذه هي الحالة عند فقدان الإشعار على كتلة من الحزم else ( if (!connectionRecord.TimerSecondTry) ( ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1); ConnectionRecord. TimerSecondTry = true return ) // بدء مؤقت اكتمال الاتصال StartCloseWaitTimer(connectionRecord);


قادر SendingCycleيتم استدعاء هذه الطريقة فقط على جهاز توقيت، وهي مسؤولة عن إعادة إرسال الرسالة الأخيرة، وكذلك لبدء مؤقت إغلاق الاتصال.

SendingCycle.ProcessPackets:

التجاوز العام void ProcessPackets(ReliableUdpConnectionRecord communicationRecord) ( if (connectionRecord.IsDone != 0) return; // إعادة إرسال الحزمة الأخيرة // (إذا تمت استعادة الاتصال، ستعيد العقدة المتلقية إرسال الطلبات التي لم تصل إليها) ReliableUdpStateTools.SendPacket (connectionRecord, ReliableUdpStateTools.RetransmissionCreateUdpPayload(connectionRecord, ConnectionRecord.SndNext - 1)); // تمكين مؤقت CloseWait - لانتظار استعادة الاتصال أو اكتماله StartCloseWaitTimer(connectionRecord);


قادر مكتملتقوم الطريقة بإيقاف مؤقت التشغيل وتنقل رسالة إلى المشتركين.

اكتملت.حزم العمليات:

التجاوز العام void ProcessPackets(ReliableUdpConnectionRecord communicationRecord) ( if (connectionRecord.WaitForPacketsTimer != null) ConnectionRecord.WaitForPacketsTimer.Dispose(); // اجمع الرسالة وأرسلها إلى المشتركين ReliableUdpStateTools.CreateMessageFromMemoryStream(connectionRecord);)

طريقة تلقي الحزمة

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

FirstPacketReceived.ReceivePacket:

التجاوز العام void CancelPacket(ReliableUdpConnectionRecordconnectionRecord, ReliableUdpHeader header, byte payload) ( if (!header.Flags.HasFlag(ReliableUdpHeaderFlags.FirstPacket)) // تجاهل إرجاع الحزمة؛ // مزيج من علامتين - FirstPacket و LastPacket - يقول ذلك لدينا الرسالة الوحيدة إذا (header.Flags.HasFlag(ReliableUdpHeaderFlags.FirstPacket) & header.Flags.HasFlag(ReliableUdpHeaderFlags.LastPacket)) (ReliableUdpStateTools.CreateMessageFromSinglePacket(connectionRecord, header, payload.Slice(ReliableUdpHeader.Length ، الحمولة .الطول) ); !header.Flags.HasFlag(ReliableUdpHeaderFlags.NoAsk)) ( // إرسال حزمة التأكيد ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);) SetAsCompleted(connectionRecord); ReliableUdpStateTools.InitIncomingBytesStorage(connectionRecord, header); ReliableUdpStateTools.WritePacketData(connectionRecord, header, payload); // حساب عدد الحزم التي يجب أن تصل ConnectionRecord.NumberOfPackets = (int)Math.Ceiling((double) ((double)connectionRecord.IncomingStream.Length/(double)connectionRecord.BufferSize)); // سجل رقم آخر حزمة مستلمة (0) ConnectionRecord.RcvCurrent = header.PacketNumber; // بعد ذلك قمنا بنقل نافذة الاستقبال بمقدار 1 ConnectionRecord.WindowLowerBound++; // تبديل الحالة ConnectionRecord.State = ConnectionRecord.Tcb.States.Assembling; // إذا لم تكن هناك حاجة إلى آلية تأكيد // ابدأ مؤقتًا يحرر جميع الهياكل if (header.Flags.HasFlag(ReliableUdpHeaderFlags.NoAsk)) (connectionRecord.CloseWaitTimer = new Timer(DisposeByTimeout,connectionRecord,connectionRecord.ShortTimerPeriod, -1 );


قادر SendingCycleيتم تجاوز هذه الطريقة لقبول تأكيدات التسليم وطلبات إعادة الإرسال.

SendingCycle.ReceivePacket:

تجاوز عام باطل تلقي حزمة (ReliableUdpConnectionRecord ConnectionRecord، رأس ReliableUdpHeader، حمولة البايت) ( if (connectionRecord.IsDone != 0) return; if (!header.Flags.HasFlag(ReliableUdpHeaderFlags.RequestForPacket)) return; // حساب حدود النافذة النهائية // يتم أخذ حدود النافذة + 1 لتلقي تأكيدات التسليم int windowHighestBound = Math.Min((connectionRecord.WindowLowerBound + ConnectionRecord.WindowSize), (connectionRecord.NumberOfPackets)); // التحقق من ضرب النافذة إذا (header.PacketNumber)< connectionRecord.WindowLowerBound || header.PacketNumber >windowHighestBound) return; ConnectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1); if (connectionRecord.CloseWaitTimer != null) ConnectionRecord.CloseWaitTimer.Change(-1, -1); // تحقق من وجود الحزمة الأخيرة: if (header.PacketNumber == ConnectionRecord.NumberOfPackets) ( // اكتمل الإرسال Interlocked.Increment(ref communicationRecord.IsDone); SetAsCompleted(connectionRecord); return;) // هذه هي الاستجابة لـ الحزمة الأولى مع التأكيد if ((header.Flags.HasFlag(ReliableUdpHeaderFlags.FirstPacket) && header.PacketNumber == 1)) ( // بدون إزاحة النافذة SendPacket(connectionRecord);) // وصل تأكيد استلام كتلة البيانات وإلا إذا (header.PacketNumber == windowHighestBound) (// تحويل اتصال نافذة الاستقبال/الإرسالRecord.WindowLowerBound += ConnectionRecord.WindowSize؛ // إعادة ضبط صفيف التحكم في الإرسال ConnectionRecord.WindowControlArray.Nullify()؛ // إرسال كتلة من الحزم SendPacket( ConnectionRecord ) // هذا طلب لتكرار الإرسال - أرسل الحزمة المطلوبة else ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.RetransmissionCreateUdpPayload(connectionRecord, header.PacketNumber)); )


قادر تجميعفي طريقة ريسيفباكيت، يتم العمل الرئيسي لتجميع الرسالة من الحزم الواردة.

التجميع.تلقي الحزمة:

التجاوز العام void CancelPacket(ReliableUdpConnectionRecordconnectionRecord, ReliableUdpHeader header, byte payload) ( if (connectionRecord.IsDone != 0) return; // معالجة الحزم مع تعطيل آلية تأكيد التسليم if (header.Flags.HasFlag(ReliableUdpHeaderFlags.NoAsk)) ( // إعادة ضبط المؤقت ConnectionRecord.CloseWaitTimer.Change(connectionRecord.LongTimerPeriod, -1); // اكتب البيانات ReliableUdpStateTools.WritePacketData(connectionRecord, header, payload); header.Flags.HasFlag); (ReliableUdpHeaderFlags.LastPacket)) (connectionRecord.State = ConnectionRecord.Tcb.States.Completed;connectionRecord.State.ProcessPackets(connectionRecord);) // حساب حدود النافذة النهائية int windowHighestBound = Math.Min((connectionRecord.WindowLowerBound + ConnectionRecord.WindowSize - 1), (connectionRecord.NumberOfPackets - 1)); // تجاهل الحزم التي لا تقع في النافذة if (header.PacketNumber< connectionRecord.WindowLowerBound || header.PacketNumber >(windowHighestBound)) return; // تجاهل التكرارات if (connectionRecord.WindowControlArray.Contains(header.PacketNumber)) return; // كتابة البيانات ReliableUdpStateTools.WritePacketData(connectionRecord, header, payload); // زيادة عداد الحزم ConnectionRecord.PacketCounter++; // اكتب رقم الحزمة الحالية إلى صفيف التحكم في النافذة ConnectionRecord.WindowControlArray = header.PacketNumber; // تعيين أكبر حزمة واردة إذا (header.PacketNumber > ConnectionRecord.RcvCurrent) ConnectionRecord.RcvCurrent = header.PacketNumber; // إعادة تشغيل الموقتات ConnectionRecord.TimerSecondTry = false; ConnectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1); if (connectionRecord.CloseWaitTimer != null) ConnectionRecord.CloseWaitTimer.Change(-1, -1); // إذا وصلت الحزمة الأخيرة if (header.Flags.HasFlag(ReliableUdpHeaderFlags.LastPacket)) ( Interlocked.Increment(ref communicationRecord.IsLastPacketReceived);) // إذا تلقينا جميع حزم النافذة، فقم بإعادة تعيين العداد // وأرسل حزمة تأكيد else if (connectionRecord.PacketCounter ==connectionRecord.WindowSize) (// إعادة تعيين العداد.connectionRecord.PacketCounter = 0; // تحول نافذة الإرسال ConnectionRecord.WindowLowerBound += ConnectionRecord.WindowSize; // إعادة تعيين الإرسال اتصال صفيف التحكمRecord.WindowControlArray.Nullify() ; ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord) // إذا كانت الحزمة الأخيرة متاحة بالفعل if (Thread.VolatileRead(ref communicationRecord.IsLastPacketReceived) != 0) (// تحقق من الحزم ProcessPackets(connectionRecord ) ))


قادر مكتملالمهمة الوحيدة لهذه الطريقة هي إرسال تأكيد متكرر لتسليم الرسالة بنجاح.

اكتمل.تلقي الحزمة:

التجاوز العام void CancelPacket(ReliableUdpConnectionRecordconnectionRecord, ReliableUdpHeader header, byte payload) (// إعادة إرسال الحزمة الأخيرة بسبب // حقيقة أن آخر ack لم يصل إلى المرسل if (header.Flags.HasFlag(ReliableUdpHeaderFlags.LastPacket) ) ( ReliableUdpStateTools .SendAcknowledgePacket(connectionRecord);

طريقة إرسال الحزمة

قادر إرسال الحزمة الأولىترسل هذه الطريقة الحزمة الأولى من البيانات، أو الرسالة بأكملها إذا كانت الرسالة لا تتطلب تأكيد التسليم.

FirstPacketSending.SendPacket:

تجاوز عام void SendPacket(ReliableUdpConnectionRecordconnectionRecord) (connectionRecord.PacketCounter = 0;connectionRecord.SndNext = 0;connectionRecord.WindowLowerBound = 0; // إذا لم يكن التأكيد مطلوبًا، أرسل جميع الحزم // وقم بتحرير الموارد if (connectionRecord.IsNoAnswerNeeded) ( // يحدث هنا الإرسال كما هو ( ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.CreateUdpPayload(connectionRecord, ReliableUdpStateTools.CreateReliableUdpHeader(connectionRecord))); ConnectionRecord.SndNext++; ) while (connectionRecord.SndNext< connectionRecord.NumberOfPackets); SetAsCompleted(connectionRecord); return; } // создаем заголовок пакета и отправляем его ReliableUdpHeader header = ReliableUdpStateTools.CreateReliableUdpHeader(connectionRecord); ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.CreateUdpPayload(connectionRecord, header)); // увеличиваем счетчик connectionRecord.SndNext++; // сдвигаем окно connectionRecord.WindowLowerBound++; connectionRecord.State = connectionRecord.Tcb.States.SendingCycle; // Запускаем таймер connectionRecord.WaitForPacketsTimer = new Timer(CheckByTimer, connectionRecord, connectionRecord.ShortTimerPeriod, -1); }


قادر SendingCycleفي هذه الطريقة، يتم إرسال كتلة من الحزم.

SendingCycle.SendPacket:

التجاوز العام void SendPacket(ReliableUdpConnectionRecord communicationRecord) (// إرسال كتلة من الحزم لـ (connectionRecord.PacketCounter = 0؛ ConnectionRecord.PacketCounter< connectionRecord.WindowSize && connectionRecord.SndNext < connectionRecord.NumberOfPackets; connectionRecord.PacketCounter++) { ReliableUdpHeader header = ReliableUdpStateTools.CreateReliableUdpHeader(connectionRecord); ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.CreateUdpPayload(connectionRecord, header)); connectionRecord.SndNext++; } // на случай большого окна передачи, перезапускаем таймер после отправки connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1); if (connectionRecord.CloseWaitTimer != null) { connectionRecord.CloseWaitTimer.Change(-1, -1); } }

أعمق في الكود. إنشاء وتأسيس الاتصالات

الآن بعد أن أصبحنا على دراية بالحالات والأساليب الأساسية المستخدمة لمعالجة الحالات، يمكننا أن ننظر بمزيد من التفصيل في عدة أمثلة لعمل البروتوكول.

مخطط نقل البيانات في ظل الظروف العادية:



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

إنشاء اتصال صادر:

StartTransmission (ReliableUdpMessage ReliableUdpMessage، EndPoint endPoint، AsyncResultSendMessage asyncResult) ( if (m_isListenerStarted == 0) ( if (this.LocalEndpoint == null) ( throw new ArgumentNullException(""، "يجب عليك استخدام المنشئ مع المعلمات أو بدء تشغيل المستمع قبل إرسال رسالة"); ) // بدء معالجة الحزم الواردة StartListener(LocalEndpoint);) // إنشاء مفتاح للقاموس بناءً على EndPoint وReliableUdpHeader.TransmissionId byte communicationId = new byte; // إنشاء رقم عشوائي communicationId m_randomCrypto.GetBytes( معرف الإرسال) ؛ المفتاح = Tuple الجديد (endPoint, BitConverter.ToInt32(transmissionId, 0)); // أنشئ سجلاً جديدًا للاتصال وتحقق من وجود هذا الرقم بالفعل في قواميسنا if (!m_listOfHandlers.TryAdd(key, new ReliableUdpConnectionRecord(key, this, ReliableUdpMessage, asyncResult))) ( // إذا كان موجودًا ثم كرر إنشاء رقم عشوائي m_randomCrypto.GetBytes(transmissionId); (endPoint, BitConverter.ToInt32(transmissionId, 0)); if (!m_listOfHandlers.TryAdd(key, new ReliableUdpConnectionRecord(key, this, ReliableUdpMessage, asyncResult))) // إذا فشل مرة أخرى، قم بطرح استثناء throw new ArgumentException("Pair TransmissionId & EndPoint موجود بالفعل في القاموس"); ) // بدأت حالة المعالجة m_listOfHandlers.State.SendPacket(m_listOfHandlers); )


إرسال الحزمة الأولى (حالة FirstPacketSending):

التجاوز العام void SendPacket(ReliableUdpConnectionRecordconnectionRecord) (connectionRecord.PacketCounter = 0;connectionRecord.SndNext = 0;connectionRecord.WindowLowerBound = 0; // ... // إنشاء رأس الحزمة وإرسالها ReliableUdpHeader header = ReliableUdpStateTools.CreateReliableUdpHeader(connection Record) ); RELIABLEDPTATOTOLS.SENDPACKET (ConnectionRephorus, ReliaBleudpstateOls.createDPAYLOAD (ConnectionRECORD, HeADER)) // تعزيز ConnectionReCord.SNDNEXT ++meter; ConnectionReCord.windowLowerBound ++; ConnectionRecord.ShortTimerPeriod, -1 );


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

إنشاء اتصال على الجانب المتلقي:

Private void EndReceive(IAsyncResult ar) ( // ... // الحزمة المستلمة // تحليل رأس الحزمة ReliableUdpHeader header; if (!ReliableUdpStateTools.ReadReliableUdpHeader(bytes, out header)) ( // وصلت حزمة غير صحيحة - تجاهلها وإرجاعها ;) // أنشئ مفتاحًا لتحديد سجل الاتصال لحزمة Tuple المفتاح = Tuple الجديد (connectedClient, header.TransmissionId); // احصل على سجل اتصال موجود أو قم بإنشاء سجل جديد ReliableUdpConnectionRecord Record = m_listOfHandlers.GetOrAdd(key, new ReliableUdpConnectionRecord(key, this, header.ReliableUdpMessageType)); // إطلاق الحزمة للمعالجة في جهاز الحالة Record.State.ReceivePacket(record, header, bytes); )


استلام الحزمة الأولى وإرسال إقرار (حالة FirstPacketReceived):

التجاوز العام void CancelPacket(ReliableUdpConnectionRecord ConnectionRecord, ReliableUdpHeader header, byte payload) ( if (!header.Flags.HasFlag(ReliableUdpHeaderFlags.FirstPacket)) // تجاهل إرجاع الحزمة؛ // ... // حسب التصميم تبدأ جميع أرقام الحزم من 0 ; if (header.PacketNumber != 0) return; // تهيئة المصفوفة لتخزين أجزاء الرسالة ReliableUdpStateTools.InitIncomingBytesStorage(connectionRecord, header); عدد الحزم التي يجب أن تصل ConnectionRecord.NumberOfPackets = (int)Math.Ceiling((double) ((double) ConnectionRecord.IncomingStream.Length/(double) ConnectionRecord.BufferSize)); // سجل رقم آخر حزمة تم استلامها (0) ) ConnectionRecord.RcvCurrent = header.PacketNumber; // بعد نقل نافذة الاستقبال بمقدار 1 ConnectionRecord.WindowLowerBound++; إذا (/*إذا لم تكن آلية التأكيد مطلوبة*/) // ... else ( // أرسل التأكيد ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);connectionRecord.WaitForPacketsTimer = new Timer(CheckByTimer, ConnectionRecord, ConnectionRecord.ShortTimerPeriod, -1); ))

أعمق في الكود. إغلاق الاتصال بسبب انتهاء المهلة

تعد معالجة المهلات جزءًا مهمًا من UDP الموثوق. فكر في مثال تفشل فيه العقدة الوسيطة ويصبح تسليم البيانات في كلا الاتجاهين مستحيلاً.

رسم تخطيطي لإغلاق الاتصال عن طريق المهلة:



كما يتبين من الرسم البياني، يبدأ مؤقت عمل المرسل فورًا بعد إرسال كتلة من الحزم. يحدث هذا في طريقة SendPacket الخاصة بالحالة SendingCycle.

تمكين مؤقت العمل (حالة SendingCycle):

التجاوز العام void SendPacket(ReliableUdpConnectionRecord communicationRecord) ( // إرسال كتلة من الحزم // ... // أعد تشغيل المؤقت بعد الإرسال ConnectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1); if (connectionRecord.CloseWaitTimer != null ) ConnectionRecord.CloseWaitTimer.Change(-1, -1);


يتم ضبط فترات المؤقت عند إنشاء الاتصال. بشكل افتراضي، ShortTimerPeriod هو 5 ثواني. في المثال تم ضبطه على 1.5 ثانية.

بالنسبة للاتصال الوارد، يبدأ المؤقت بعد استلام آخر حزمة بيانات مستلمة؛ ويحدث هذا في طريقة الاستقبال الخاصة بالحالة تجميع

تمكين مؤقت العمل (حالة التجميع):

تجاوز عام باطل تلقي حزمة (ReliableUdpConnectionRecordconnectionRecord, ReliableUdpHeader header, byte payload) ( // ... // إعادة تشغيل الموقتات ConnectionRecord.TimerSecondTry = false; ConnectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1); if (connectionRecord.CloseWaitTimer! = null) ConnectionRecord.CloseWaitTimer.Change(-1, -1); // ... )


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

إرسال طلبات إعادة التسليم (حالة التجميع):

التجاوز العام void ProcessPackets(ReliableUdpConnectionRecord communicationRecord) ( // ... if (/*تحقق من الحزم المفقودة */) ( // إرسال طلبات إعادة التسليم // اضبط المؤقت مرة ثانية لإعادة محاولة الإرسال if (!connectionRecord. TimerSecondTry) (connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1); ConnectionRecord.TimerSecondTry = true; return;) // إذا لم يكن من الممكن تلقي الحزم بعد محاولتين لتشغيل WaitForPacketTimer //، فابدأ مؤقت إكمال الاتصال StartCloseWaitTimer(connectionRecord); ) else if (/* وصلت الحزمة الأخيرة وتم التحقق بنجاح */) ( // ... StartCloseWaitTimer(connectionRecord);) // إذا تم فقدان الإشعار الخاص بكتلة الحزم else ( if (! ConnectionRecord.TimerSecondTry) ( // إعادة إرسال ack ConnectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1); ConnectionRecord.TimerSecondTry = true; return; // بدء مؤقت إكمال الاتصال StartCloseWaitTimer(connectionRecord); ))


تم تعيين المتغير TimerSecondTry على حقيقي. هذا المتغير مسؤول عن إعادة تشغيل مؤقت العمل.

ومن جانب المرسل، يتم أيضًا تشغيل مؤقت العمل وإعادة إرسال آخر حزمة مرسلة.

تمكين مؤقت إغلاق الاتصال (حالة SendingCycle):

التجاوز العام void ProcessPackets(ReliableUdpConnectionRecordconnectionRecord) ( // ... // إعادة إرسال الحزمة الأخيرة // ... // تمكين مؤقت CloseWait - لانتظار استعادة الاتصال أو اكتماله StartCloseWaitTimer(connectionRecord);)


بعد ذلك، يبدأ مؤقت إغلاق الاتصال في الاتصال الصادر.

ريليابليودبستات.ستارتكلوسيوايتتايمر:

StartCloseWaitTimer (ReliableUdpConnectionRecord ConnectionRecord) ( if (connectionRecord.CloseWaitTimer != null) ConnectionRecord.CloseWaitTimer.Change(connectionRecord.LongTimerPeriod, -1); ;)


فترة المهلة الافتراضية لمؤقت إغلاق الاتصال هي 30 ثانية.

بعد وقت قصير، يتم تشغيل مؤقت العمل على الجانب المتلقي مرة أخرى، ويتم إرسال الطلبات مرة أخرى، وبعد ذلك يبدأ مؤقت إغلاق الاتصال للاتصال الوارد

عند تشغيل مؤقتات الإغلاق، يتم تحرير كافة موارد كلا سجلي الاتصال. يقوم المرسل بالإبلاغ عن فشل التسليم إلى التطبيق الرئيسي ( راجع UDP الموثوق به لواجهة برمجة التطبيقات (API).).

تحرير سجل الاتصال"الموارد:

الفراغ العام Dispose() ( حاول ( System.Threading.Monitor.Enter(this.LockerReceive); ) أخيرًا ( Interlocked.Increment(ref this.IsDone); if (WaitForPacketsTimer != null) ( WaitForPacketsTimer.Dispose(); ) إذا (CloseWaitTimer != null) ( CloseWaitTimer.Dispose(); ) Tcb.IncomingStreams.TryRemove(Key, outstream = null; .Threading.Monitor.Exit(this.LockerReceive);

أعمق في الكود. استعادة نقل البيانات

رسم تخطيطي لاستعادة نقل البيانات في حالة فقدان الحزمة:



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

إرسال طلبات إعادة تسليم الطرود (حالة التجميع):

التجاوز العام void ProcessPackets(ReliableUdpConnectionRecordconnectionRecord) ( //... if (!ReliableUdpStateTools.CheckForNoPacketLoss(connectionRecord, ConnectionRecord.IsLastPacketReceived != 0)) ( // هناك حزم مفقودة، أرسل طلبات لها لكل (int seqNum in ConnectionRecord. LostPackets ) ( if (seqNum != 0) ( ReliableUdpStateTools.SendAskForLostPacket(connectionRecord, seqNum); ) ) // ... ) )


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

إعادة إرسال الحزم المفقودة (حالة SendingCycle):

التجاوز العام void CancelPacket(ReliableUdpConnectionRecordconnectionRecord, ReliableUdpHeader header, byte payload) (//...connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1); // إعادة تعيين مؤقت إغلاق الاتصال إذا (connectionRecord.CloseWaitTimer != null) ConnectionRecord .CloseWaitTimer.Change(-1, -1); // ... // هذا طلب لإعادة الإرسال - أرسل الحزمة المطلوبة else ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.RetransmissionCreateUdpPayload(connectionRecord, header.PacketNumber));


يتم استلام الحزمة المرسلة (الحزمة رقم 3 في الرسم التخطيطي) بواسطة الاتصال الوارد. يتم إجراء فحص للتأكد من امتلاء نافذة الاستلام واستعادة النقل الطبيعي للبيانات.

التحقق من النتائج في نافذة الاستقبال (حالة التجميع):

التجاوز العام void CancelPacket(ReliableUdpConnectionRecord ConnectionRecord, ReliableUdpHeader header, byte payload) ( // ... // زيادة عداد الحزمة ConnectionRecord.PacketCounter++; // اكتب رقم الحزمة الحالية إلى صفيف التحكم في النافذة ConnectionRecord.WindowControlArray = header.PacketNumber; // تعيين أكبر حزمة تم استلامها if (header.PacketNumber > ConnectionRecord.RcvCurrent) ConnectionRecord.RcvCurrent = header.PacketNumber; // إعادة تشغيل الموقتات communicationRecord.TimerSecondTry = false; null) ConnectionRecord.CloseWaitTimer.Change(-1, -1); // ... // إذا تلقينا جميع حزم النافذة، فقم بإعادة تعيين العداد // وأرسل حزمة تأكيد وإلا إذا (connectionRecord.PacketCounter == ConnectionRecord WindowSize) ( // إعادة ضبط العداد.connectionRecord.PacketCounter = 0; // نقل نافذة الإرسال ConnectionRecord.WindowLowerBound += ConnectionRecord.WindowSize; // إبطال صفيف التحكم في الإرسال ConnectionRecord.WindowControlArray.Nullify(); ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord); ) // ... )

واجهة برمجة تطبيقات UDP الموثوقة

للتفاعل مع بروتوكول نقل البيانات، هناك فئة مفتوحة موثوقة Udp، وهي عبارة عن غلاف فوق كتلة التحكم في النقل. فيما يلي أهم أعضاء الفصل:
فئة عامة مختومة ReliableUdp: IDisposable ( // تحصل على نقطة نهاية محلية public IPEndPoint LocalEndpoint // تنشئ مثيلاً لـ ReliableUdp وتبدأ // الاستماع للحزم الواردة على عنوان IP المحدد // والمنفذ. القيمة 0 للمنفذ تعني استخدام / / منفذ مخصص ديناميكيًا public ReliableUdp (IPAddress localAddress, int port = 0) // الاشتراك لتلقي الرسائل الواردة public ReliableUdpSubscribeObject SubmitOnMessages(ReliableUdpMessageCallback callback, ReliableUdpMessageTypes messageType = ReliableUdpMessageTypes.Any, IPEndPoint ipEndPoint = null) // un الاشتراك من تلقي الرسائل باطلة العامة Unsubscribe(ReliableUdpSubscribeObject SubmitObject) // إرسال رسالة بشكل غير متزامن // ملاحظة: لم يتم فقدان التوافق مع XP وServer 2003، نظرًا لاستخدام المهمة العامة .NET Framework 4.0 SendMessageAsync(ReliableUdpMessage ReliableUdpMessage, IPEndPoint RemoteEndPoint, CancellationToken cToken) // بدء إرسال الرسائل غير المتزامنة العامة IAsyncResult BeginSendMessage(ReliableUdpMessage ReliableUdpMessage, IPEndPoint RemoteEndPoint, AsyncCallback asyncCallback, Object State) // الحصول على نتيجة إرسال غير متزامن منطقي عام EndSendMessage(IAsyncResult asyncResult) // مسح الموارد الفراغ العام التخلص ()
يتم تلقي الرسائل عن طريق الاشتراك. توقيع المفوض لطريقة رد الاتصال:
المفوض العام void ReliableUdpMessageCallback(ReliableUdpMessage ReliableUdpMessage, IPEndPoint RemoteClient);
رسالة:
public class ReliableUdpMessage ( // نوع الرسالة، تعداد بسيط نوع ReliableUdpMessageTypes العام ( get; مجموعة خاصة; ) // بيانات الرسالة جسم البايت العام ( get; مجموعة خاصة; ) // إذا تم التعيين على "صحيح"، فسيتم تعطيل آلية تأكيد التسليم / / لإرسال رسالة محددة bool NoAsk العام ( get; Private set; ) )
للاشتراك في نوع رسالة محدد و/أو مرسل محدد، يتم استخدام معلمتين اختياريتين: ReliableUdpMessageTypes messageType وIPEndPoint ipEndPoint.

أنواع الرسائل:
التعداد العام ReliableUdpMessageTypes: قصير ( // Any Any = 0، // طلب إلى خادم STUN StunRequest = 1، // استجابة من خادم STUN StunResponse = 2، // نقل الملفات FileTransfer = 3، // ... )

يتم إرسال الرسالة بشكل غير متزامن، ولهذا الغرض، يطبق البروتوكول نموذج برمجة غير متزامن:
IAsyncResult العامة BeginSendMessage (ReliableUdpMessage ReliableUdpMessage، IPEndPoint RemoteEndPoint، AsyncCallback asyncCallback، حالة الكائن)
ستكون نتيجة إرسال الرسالة صحيحة - إذا وصلت الرسالة بنجاح إلى المستلم وخطأ - إذا تم إغلاق الاتصال بسبب انتهاء المهلة:
المنطق العام EndSendMessage (IAsyncResult asyncResult)

خاتمة

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

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

أشكركم على اهتمامكم، وأنا في انتظار تعليقاتكم وتعليقاتكم.
تنفيذ نموذج البرمجة غير المتزامنة CLR وكيفية تنفيذ نمط التصميم IAsyncResult

  • نقل نموذج البرمجة غير المتزامن إلى النمط غير المتزامن القائم على المهام (APM إلى TAP):
    TPL والبرمجة التقليدية غير المتزامنة .NET
    التفاعل مع الأنماط والأنواع الأخرى غير المتزامنة
  • تحديث: شكرًا لـ mayorovp وsidristij على فكرة إضافة مهمة إلى الواجهة، ولم يتأثر توافق المكتبة مع أنظمة التشغيل الأقدم، نظرًا لأن الإطار الرابع يدعم كلاً من خادم XP و2003.