د .Net لپاره د باور وړ Udp پروتوکول پلي کول

انټرنیټ ډیر وخت بدل شوی. د انټرنیټ یو له اصلي پروتوکولونو څخه - UDP د غوښتنلیکونو لخوا نه یوازې د ډیټاګرامونو او نشراتو وړاندې کولو لپاره کارول کیږي ، بلکه د شبکې نوډونو ترمینځ د "پییر-ټو-پییر" اړیکې چمتو کولو لپاره هم کارول کیږي. د دې ساده ډیزاین له امله، دا پروتوکول ډیری پخوا غیر پلان شوي کارونې لري، په هرصورت، د پروتوکول نیمګړتیاوې، لکه د تضمین شوي تحویل نشتوالی، چیرته ورک شوی نه دی. دا مقاله د UDP په اړه د تضمین شوي تحویلي پروتوکول پلي کول تشریح کوي.
محتويات:د ننوتلو
د پروتوکول اړتیاوې
د باور وړ UDP سرلیک
د پروتوکول عمومي اصول
مهال ویش او پروتوکول ټایمر
د اعتبار وړ UDP لیږد حالت ډیاګرام
په کوډ کې ژور. د لیږد کنټرول واحد
په کوډ کې ژور. ایالتونه

په کوډ کې ژور. د اړیکو رامینځته کول او رامینځته کول
په کوډ کې ژور. د وخت په تیریدو سره پیوستون بندول
په کوډ کې ژور. د معلوماتو لیږد بیرته راګرځول
د اعتبار وړ UDP API
پایلې
ګټورې لینکونه او مقالې

د ننوتلو

د انټرنیټ اصلي جوړښت د یو همغږي پته ځای په غاړه اخیستی چې پکې هر نوډ یو نړیوال او ځانګړی IP پته لري او کولی شي مستقیم د نورو نوډونو سره اړیکه ونیسي. اوس انټرنیټ، په حقیقت کې، یو مختلف جوړښت لري - د نړیوال IP پتې یوه ساحه او ډیری سیمې چې د NAT وسیلو تر شا پټ شوي شخصي پتې لري.په دې جوړښت کې، یوازې د نړیوال آدرس ځای کې وسایل کولی شي په اسانۍ سره په شبکه کې د هرچا سره اړیکه ونیسي ځکه چې دوی یو ځانګړی، په نړیواله کچه د تګ وړ IP پته لري. په خصوصي شبکه کې یو نوډ کولی شي په ورته شبکه کې نورو نوډونو سره وصل شي، او کولی شي د نړیوال ادرس ځای کې نورو پیژندل شویو نوډونو سره هم وصل شي. دا تعامل په لویه کچه د شبکې پتې د ژباړې میکانیزم له امله ترلاسه کیږي. د NAT وسیلې، لکه د وائی فای روټرونه، د وتلو اړیکو لپاره د ژباړې ځانګړي میز ننوتنې رامینځته کوي او په پیکټونو کې د IP پتې او پورټ شمیرې بدلوي. دا د خصوصي شبکې څخه بهر ته د نړیوال ادرس ځای کې کوربه ته اجازه ورکوي. مګر په ورته وخت کې، د NAT وسایل معمولا ټول راتلونکی ټرافیک بندوي پرته لدې چې د راتلونکو اړیکو لپاره جلا مقررات ترتیب شوي وي.

د انټرنیټ دا جوړښت د مراجعینو-سرور اړیکو لپاره کافي سم دی، چیرته چې پیرودونکي په خصوصي شبکو کې وي، او سرورونه یو نړیوال پته لري. مګر دا د دوه نوډونو ترمینځ مستقیم ارتباط لپاره ستونزې رامینځته کوي مختلف خصوصي شبکې. د دوه نوډونو تر مینځ مستقیم اړیکه د پییر-ټو-پییر غوښتنلیکونو لپاره مهمه ده لکه د غږ لیږد (سکایپ) ، کمپیوټر ته د ریموټ لاسرسي ترلاسه کول (TeamViewer) ، یا آنلاین لوبو.

په مختلفو خصوصي شبکو کې د وسیلو تر مینځ د پییر-ټو پییر پیوستون رامینځته کولو لپاره یو له خورا مؤثره میتودونو څخه د سوراخ پنچنګ په نوم یادیږي. دا تخنیک ډیری وختونه د UDP پروتوکول پراساس غوښتنلیکونو سره کارول کیږي.

مګر که ستاسو غوښتنلیک د ډیټا تضمین شوي تحویل ته اړتیا ولري ، د مثال په توګه ، تاسو د کمپیوټرونو ترمینځ فایلونه لیږدئ ، نو د UDP کارول به ډیری ستونزې ولري ځکه چې UDP د تحویلي تحویلي پروتوکول نه دی او د TCP برعکس د پاکټ تحویل چمتو نه کوي. پروتوکول

پدې حالت کې ، د تضمین شوي پاکټ تحویلۍ ډاډ ترلاسه کولو لپاره ، دا اړینه ده چې د غوښتنلیک پرت پروتوکول پلي کړئ چې اړین فعالیت چمتو کوي او په UDP کې کار کوي.

زه غواړم سمدلاسه یادونه وکړم چې په بیلابیلو خصوصي شبکو کې د نوډونو ترمینځ د TCP پیوستون رامینځته کولو لپاره د TCP سوراخ پنچنګ تخنیک شتون لري ، مګر د ډیری NAT وسیلو لخوا د دې لپاره د ملاتړ نشتوالي له امله ، دا معمولا د نښلولو اصلي لاره نه ګڼل کیږي. داسې نوډونه

د دې مقالې پاتې برخې لپاره، زه به یوازې د تضمین شوي تحویلي پروتوکول پلي کولو باندې تمرکز وکړم. د UDP سوري پنچنګ تخنیک پلي کول به په لاندې مقالو کې تشریح شي.

د پروتوکول اړتیاوې

  1. د باور وړ کڅوړې تحویل د مثبت فیډبیک میکانیزم له لارې پلي کیږي (د مثبت اعتراف په نوم یادیږي)
  2. د لویو معلوماتو اغیزمن لیږد ته اړتیا، د بیلګې په توګه. پروتوکول باید د غیر ضروري بسته بندۍ مخه ونیسي
  3. دا باید ممکنه وي چې د تحویلي تایید میکانیزم لغوه کړئ (د "خالص" UDP پروتوکول په توګه د فعالیت کولو وړتیا)
  4. د هر پیغام تایید سره د کمانډ حالت پلي کولو وړتیا
  5. د پروتوکول په اوږدو کې د معلوماتو لیږد بنسټیز واحد باید یو پیغام وي

دا اړتیاوې په لویه کچه د باور وړ ډیټا پروتوکول اړتیاو سره مطابقت لري چې په کې تشریح شوي rfc908 и rfc1151، او ما په دې معیارونو تکیه وکړه کله چې د دې پروتوکول رامینځته کول.

د دې اړتیاو د پوهیدو لپاره، راځئ چې د TCP او UDP پروتوکولونو په کارولو سره د دوه شبکې نوډونو ترمنځ د معلوماتو لیږد وخت وګورو. اجازه راکړئ چې په دواړو حالتونو کې موږ یو پاکټ له لاسه ورکړو.
په TCP کې د غیر متقابل معلوماتو لیږد:د .Net لپاره د باور وړ Udp پروتوکول پلي کول

لکه څنګه چې تاسو د ډیاګرام څخه لیدلی شئ، د کڅوړې د ضایع کیدو په صورت کې، TCP به ورک شوی پاکټ کشف کړي او د ورک شوي برخې د شمیر په غوښتنه به یې لیږونکي ته راپور ورکړي.
د UDP پروتوکول له لارې د معلوماتو لیږد:د .Net لپاره د باور وړ Udp پروتوکول پلي کول

UDP د زیان موندنې کوم ګامونه نه اخلي. په UDP پروتوکول کې د لیږد غلطیو کنټرول په بشپړ ډول د غوښتنلیک مسؤلیت دی.

په TCP پروتوکول کې د تېروتنې کشف د پای نوډ سره د پیوستون په رامینځته کولو سره ترلاسه کیږي ، د دې اړیکې حالت ذخیره کول ، د هر پیکټ سرلیک کې د لیږل شوي بایټ شمیر په ګوته کول ، او د تایید شمیرې په کارولو سره رسیدونو ته خبر ورکول.

برسیره پردې، د فعالیت ښه کولو لپاره (د بیلګې په توګه د اعتراف ترلاسه کولو پرته له یوې څخه ډیرې برخې لیږل)، د TCP پروتوکول د انتقالي کړکۍ په نامه یادیږي - د معلوماتو د بایټ شمیره چې د برخې لیږونکي تمه لري چې ترلاسه کړي.

د TCP پروتوکول په اړه د نورو معلوماتو لپاره، وګورئ rfc793له UDP څخه تر rfc768چیرته چې، په حقیقت کې، دوی تعریف شوي.

له پورته څخه، دا روښانه ده چې د UDP په اړه د باور وړ پیغام رسولو پروتوکول رامینځته کولو لپاره (له دې وروسته ورته اشاره کیږي د باور وړ UDP)، دا اړینه ده چې د TCP په څیر د معلوماتو لیږد میکانیزم پلي کړي. یعنې:

  • د پیوستون حالت خوندي کړئ
  • د برخې شمیره وکاروئ
  • د ځانګړي تایید کڅوړې وکاروئ
  • د پروتوکول له لارې د زیاتوالي لپاره د کړکۍ ساده میکانیزم وکاروئ

سربیره پردې، تاسو اړتیا لرئ:

  • د پیغام پیل ته اشاره وکړئ، ترڅو د اړیکو لپاره سرچینې تخصیص کړي
  • د پیغام پای ته اشاره وکړئ، تر څو ترلاسه شوي پیغام د اپسټریم غوښتنلیک ته انتقال کړئ او د پروتوکول سرچینې خوشې کړئ
  • د ارتباط ځانګړي پروتوکول ته اجازه ورکړئ چې د تحویلي تصدیق میکانیزم غیر فعال کړي ترڅو د "خالص" UDP په توګه کار وکړي

د باور وړ UDP سرلیک

په یاد ولرئ چې د UDP ډیټاګرام په IP ډیټاګرام کې پوښل شوی. د باور وړ UDP پاکټ په مناسب ډول د UDP ډیټاګرام کې "لپټ شوی" دی.
د باور وړ UDP سرلیک encapsulation:د .Net لپاره د باور وړ Udp پروتوکول پلي کول

د باور وړ UDP سرلیک جوړښت خورا ساده دی:

د .Net لپاره د باور وړ Udp پروتوکول پلي کول

  • بیرغونه - د کڅوړې کنټرول بیرغونه
  • د پیغام ډول - د پیغام ډول چې د اپسټریم غوښتنلیکونو لخوا کارول کیږي ترڅو ځانګړي پیغامونو کې ګډون وکړي
  • د لیږد ID - د لیږد شمیره، د ترلاسه کونکي پته او بندر سره یوځای، په ځانګړي ډول پیژني
  • PacketNumber - د بسته نمبر
  • اختیارونه - اضافي پروتوکول اختیارونه. د لومړۍ کڅوړې په حالت کې، دا د پیغام اندازه ښودلو لپاره کارول کیږي

بیرغونه په لاندې ډول دي:

  • لومړی پیکټ - د پیغام لومړۍ کڅوړه
  • NoAsk - پیغام د فعالولو لپاره د اعتراف میکانیزم ته اړتیا نلري
  • LastPacket - د پیغام وروستی پاکټ
  • RequestForPacket - د تایید پاکټ یا د ورک شوي پاکټ غوښتنه

د پروتوکول عمومي اصول

څرنګه چې د اعتبار وړ UDP د دوو نوډونو ترمنځ د تضمین شوي پیغام لیږد تمرکز کوي، دا باید د دې وړتیا ولري چې د بل لوري سره اړیکه ټینګه کړي. د پیوستون رامینځته کولو لپاره ، لیږونکی د فرسټ پیکټ بیرغ سره پیکټ لیږي ، د کوم ځواب چې دا معنی لري چې اړیکه رامینځته شوې. ټول غبرګون پاکټونه، یا، په بل عبارت، د اعتراف کڅوړې، تل د PacketNumber ساحې ارزښت په بریالیتوب سره ترلاسه شوي پاکټونو څخه د لوی PacketNumber ارزښت څخه یو ډیر ته ټاکي. د لیږل شوي لومړۍ کڅوړې لپاره د اختیارونو ساحه د پیغام اندازه ده.

ورته میکانیزم د پیوستون ختمولو لپاره کارول کیږي. د LastPacket بیرغ د پیغام په وروستي کڅوړه کې تنظیم شوی. د ځواب په کڅوړه کې، د وروستي کڅوړې شمیره + 1 ښودل شوې، کوم چې د ترلاسه کونکي اړخ لپاره د پیغام بریالیتوب لیږد معنی لري.
د پیوستون تاسیس او ختمولو ډیاګرام:د .Net لپاره د باور وړ Udp پروتوکول پلي کول

کله چې اړیکه جوړه شي، د معلوماتو لیږد پیل کیږي. ډاټا د کڅوړو په بلاکونو کې لیږدول کیږي. هر بلاک، پرته له وروستي یو، د پاکټونو یو ثابت شمیر لري. دا د ترلاسه کولو / لیږد کړکۍ اندازې سره مساوي دی. د معلوماتو وروستی بلاک ممکن لږ پاکټونه ولري. د هر بلاک له لیږلو وروسته، د لیږلو اړخ د سپارلو تایید یا د ورک شوي کڅوړو د بیا رسولو غوښتنې ته انتظار کوي، د ترلاسه کولو / لیږد کړکۍ د ځوابونو ترلاسه کولو لپاره خلاص پریږدي. د بلاک تحویلي تصدیق ترلاسه کولو وروسته ، ترلاسه کول / لیږد کړکۍ بدلیږي او د معلوماتو راتلونکی بلاک لیږل کیږي.

ترلاسه کوونکی اړخ پاکټونه ترلاسه کوي. هر پاکټ چک کیږي ترڅو وګوري چې ایا دا د لیږد کړکۍ کې راځي. کڅوړې او نقلونه چې کړکۍ ته نه راځي فلټر شوي. ځکه که چیرې د کړکۍ اندازه ټاکل شوې وي او د ترلاسه کونکي او لیږونکي لپاره یو شان وي، نو په هغه صورت کې چې د کڅوړو بلاک د ضایع کیدو پرته لیږدول کیږي، کړکۍ لیږدول کیږي ترڅو د معلوماتو د راتلونکي بلاک کڅوړې ترلاسه کړي او د لیږد تصدیق وي. لیږل که چیرې کړکۍ د کاري ټیمر لخوا ټاکل شوې مودې کې ډکې نشي، نو بیا به یو چک پیل شي چې په کوم کې پاکټونه ندي سپارل شوي او د بیا سپارلو غوښتنې به لیږل کیږي.
د بیا لیږد ډیاګرام:د .Net لپاره د باور وړ Udp پروتوکول پلي کول

مهال ویش او پروتوکول ټایمر

ډیری دلیلونه شتون لري چې ولې اړیکه نشي رامینځته کیدی. د مثال په توګه، که چیرې ترلاسه کونکي ګوند آفلاین وي. پدې حالت کې ، کله چې د پیوستون رامینځته کولو هڅه وکړئ ، اړیکه به د وخت په تیریدو سره وتړل شي. د اعتبار وړ UDP پلي کول د وخت ټاکلو لپاره دوه ټایمر کاروي. لومړی، کاري ټیمر، د لیرې کوربه څخه د ځواب انتظار کولو لپاره کارول کیږي. که چیرې دا د لیږونکي اړخ باندې ډزې وکړي، نو وروستی لیږل شوی پاکټ بیا لیږل کیږي. که چیرې ټایمر په ترلاسه کونکي کې پای ته ورسیږي ، نو د ورک شوي کڅوړو لپاره چیک ترسره کیږي او د بیا تحویلۍ غوښتنې لیږل کیږي.

د نوډونو ترمینځ د ارتباط نشتوالي په صورت کې د پیوستون بندولو لپاره دوهم ټایمر ته اړتیا ده. د لیږونکي اړخ لپاره، دا د کاري ټیمر پای ته رسیدو وروسته سمدلاسه پیل کیږي، او د ریموټ نوډ څخه ځواب ته انتظار کوي. که چیرې په ټاکلې موده کې هیڅ ځواب شتون ونلري، اړیکه فسخه کیږي او سرچینې خوشې کیږي. د ترلاسه کونکي اړخ لپاره ، د اړیکې نږدې ټایمر د کاري ټیمر دوه ځله پای ته رسیدو وروسته پیل کیږي. دا اړینه ده چې د تصدیق کڅوړې له لاسه ورکولو پروړاندې بیمه وکړئ. کله چې ټایمر پای ته ورسیږي، پیوستون هم ختمیږي او سرچینې خوشې کیږي.

د اعتبار وړ UDP لیږد حالت ډیاګرام

د پروتوکول اصول په یوه محدود دولتي ماشین کې پلي کیږي، چې هر حالت یې د پیکټ پروسس کولو یو ځانګړي منطق لپاره مسؤل دی.
د اعتبار وړ UDP حالت ډیاګرام:

د .Net لپاره د باور وړ Udp پروتوکول پلي کول

تړل - واقعیا یو دولت ندی ، دا د اتوماتیک لپاره د پیل او پای ټکی دی. د دولت لپاره تړل د لیږد کنټرول بلاک ترلاسه کیږي، کوم چې د غیر متمرکز UDP سرور پلي کول، مناسبو اړیکو ته پاکټونه لیږدوي او د دولت پروسس پیل کوي.

FirstPacketSending - لومړنی حالت چې په هغه کې د وتلو اړیکه ده کله چې پیغام لیږل کیږي.

پدې حالت کې ، د عادي پیغامونو لپاره لومړی کڅوړه لیږل کیږي. د لیږلو تایید پرته د پیغامونو لپاره، دا یوازینی حالت دی چیرې چې ټول پیغام لیږل کیږي.

د لیږلو سایکل - د پیغام پاکټونو لیږد لپاره د ځمکې حالت.

له دولت څخه دې ته لیږد FirstPacketSending د پیغام لومړۍ بسته لیږلو وروسته ترسره کیږي. دا په دې حالت کې دی چې د بیا لیږد لپاره ټول اعترافونه او غوښتنې راځي. له دې څخه وتل په دوو حالتونو کې ممکن دي - د پیغام بریالیتوب یا د وخت پای ته رسیدو په صورت کې.

لومړی پاکټ ترلاسه شو - د پیغام ترلاسه کونکي لپاره لومړنی حالت.

دا د لیږد د پیل سموالي معاینه کوي، اړین جوړښتونه رامینځته کوي، او د لومړي کڅوړې د رسیدو اعتراف لیږي.

د یو پیغام لپاره چې یو واحد پاکټ لري او د تحویلي ثبوت کارولو پرته لیږل شوی و ، دا یوازینی حالت دی. د داسې پیغام پروسس کولو وروسته، پیوستون تړل شوی.

برابرول - د پیغام پاکټونو ترلاسه کولو لپاره اساسي حالت.

دا موقتي ذخیرې ته پاکټونه لیکي، د پاکټ له لاسه ورکولو لپاره چک کوي، د کڅوړو د بلاک او ټول پیغام رسولو لپاره اعترافونه لیږي، او د ورک شوي پاکټونو د بیا سپارلو لپاره غوښتنې لیږي. د ټول پیغام د بریالي رسیدلو په صورت کې، اړیکه دولت ته ځي بشپړ شويکه نه نو، یو وخت پای ته رسیږي.

بشپړ شوي - د ټول پیغام د بریالي رسیدلو په صورت کې د پیوستون بندول.

دا حالت د پیغام راټولولو او د هغه قضیې لپاره اړین دی کله چې لیږونکي ته په لاره کې د پیغام رسولو تایید ورک شوی وي. دا حالت د وخت پای ته رسیدلی، مګر اړیکه په بریالیتوب سره تړل شوې ګڼل کیږي.

په کوډ کې ژور. د لیږد کنټرول واحد

د باور وړ UDP یو له مهمو عناصرو څخه د لیږد کنټرول بلاک دی. د دې بلاک دنده د اوسني اتصالاتو او مرستندویه عناصرو ذخیره کول ، اړوندو پیکټو ته اړونده پیکټو ویشل ، پیکټونو ته د پیکټ لیږلو لپاره انٹرفیس چمتو کول او د پروتوکول API پلي کول دي. د لیږد کنټرول بلاک د UDP پرت څخه پاکټونه ترلاسه کوي او د پروسس لپاره دولتي ماشین ته لیږدوي. د پاکټونو ترلاسه کولو لپاره، دا یو غیر متناسب UDP سرور پلي کوي.
د ReliableUdpConnectionControlBlock ټولګي ځینې غړي:

internal class ReliableUdpConnectionControlBlock : IDisposable
{
  // массив байт для указанного ключа. Используется для сборки входящих сообщений    
  public ConcurrentDictionary<Tuple<EndPoint, Int32>, byte[]> IncomingStreams { get; private set;}
  // массив байт для указанного ключа. Используется для отправки исходящих сообщений.
  public ConcurrentDictionary<Tuple<EndPoint, Int32>, byte[]> OutcomingStreams { get; private set; }
  // connection record для указанного ключа.
  private readonly ConcurrentDictionary<Tuple<EndPoint, Int32>, ReliableUdpConnectionRecord> m_listOfHandlers;
  // список подписчиков на сообщения.
  private readonly List<ReliableUdpSubscribeObject> m_subscribers;    
  // локальный сокет    
  private Socket m_socketIn;
  // порт для входящих сообщений
  private int m_port;
  // локальный IP адрес
  private IPAddress m_ipAddress;    
  // локальная конечная точка    
  public IPEndPoint LocalEndpoint { get; private set; }    
  // коллекция предварительно инициализированных
  // состояний конечного автомата
  public StatesCollection States { get; private set; }
  // генератор случайных чисел. Используется для создания TransmissionId
  private readonly RNGCryptoServiceProvider m_randomCrypto;    	
  //...
}

د غیر متناسب UDP سرور پلي کول:

private void Receive()
{
  EndPoint connectedClient = new IPEndPoint(IPAddress.Any, 0);
  // создаем новый буфер, для каждого socket.BeginReceiveFrom 
  byte[] buffer = new byte[DefaultMaxPacketSize + ReliableUdpHeader.Length];
  // передаем буфер в качестве параметра для асинхронного метода
  this.m_socketIn.BeginReceiveFrom(buffer, 0, buffer.Length, SocketFlags.None, ref connectedClient, EndReceive, buffer);
}   

private void EndReceive(IAsyncResult ar)
{
  EndPoint connectedClient = new IPEndPoint(IPAddress.Any, 0);
  int bytesRead = this.m_socketIn.EndReceiveFrom(ar, ref connectedClient);
  //пакет получен, готовы принимать следующий        
  Receive();
  // т.к. простейший способ решить вопрос с буфером - получить ссылку на него 
  // из IAsyncResult.AsyncState        
  byte[] bytes = ((byte[]) ar.AsyncState).Slice(0, bytesRead);
  // получаем заголовок пакета        
  ReliableUdpHeader header;
  if (!ReliableUdpStateTools.ReadReliableUdpHeader(bytes, out header))
  {          
    // пришел некорректный пакет - отбрасываем его
    return;
  }
  // конструируем ключ для определения connection record’а для пакета
  Tuple<EndPoint, Int32> key = new Tuple<EndPoint, Int32>(connectedClient, header.TransmissionId);
  // получаем существующую connection record или создаем новую
  ReliableUdpConnectionRecord record = m_listOfHandlers.GetOrAdd(key, new ReliableUdpConnectionRecord(key, this, header.ReliableUdpMessageType));
  // запускаем пакет в обработку в конечный автомат
  record.State.ReceivePacket(record, header, bytes);
}

د هر پیغام لیږد لپاره، یو جوړښت رامینځته کیږي چې د ارتباط په اړه معلومات لري. دا ډول جوړښت بلل کیږي د ارتباط ریکارډ.
د ReliableUdpConnectionRecord ټولګي ځینې غړي:

internal class ReliableUdpConnectionRecord : IDisposable
{    
  // массив байт с сообщением    
  public byte[] IncomingStream { get; set; }
  // ссылка на состояние конечного автомата    
  public ReliableUdpState State { get; set; }    
  // пара, однозначно определяющая connection record
  // в блоке управления передачей     
  public Tuple<EndPoint, Int32> Key { get; private set;}
  // нижняя граница приемного окна    
  public int WindowLowerBound;
  // размер окна передачи
  public readonly int WindowSize;     
  // номер пакета для отправки
  public int SndNext;
  // количество пакетов для отправки
  public int NumberOfPackets;
  // номер передачи (именно он и есть вторая часть Tuple)
  // для каждого сообщения свой	
  public readonly Int32 TransmissionId;
  // удаленный IP endpoint – собственно получатель сообщения
  public readonly IPEndPoint RemoteClient;
  // размер пакета, во избежание фрагментации на IP уровне
  // не должен превышать MTU – (IP.Header + UDP.Header + RelaibleUDP.Header)
  public readonly int BufferSize;
  // блок управления передачей
  public readonly ReliableUdpConnectionControlBlock Tcb;
  // инкапсулирует результаты асинхронной операции для BeginSendMessage/EndSendMessage
  public readonly AsyncResultSendMessage AsyncResult;
  // не отправлять пакеты подтверждения
  public bool IsNoAnswerNeeded;
  // последний корректно полученный пакет (всегда устанавливается в наибольший номер)
  public int RcvCurrent;
  // массив с номерами потерянных пакетов
  public int[] LostPackets { get; private set; }
  // пришел ли последний пакет. Используется как bool.
  public int IsLastPacketReceived = 0;
  //...
}

په کوډ کې ژور. ایالتونه

ایالتونه د باور وړ UDP پروتوکول دولتي ماشین پلي کوي ، چیرې چې د پاکټونو اصلي پروسس ترسره کیږي. د خلاصې ټولګي ReliableUdpState د دولت لپاره انٹرفیس چمتو کوي:

د .Net لپاره د باور وړ Udp پروتوکول پلي کول

د پروتوکول ټول منطق د پورته وړاندې شوي ټولګیو لخوا پلي کیږي، د یو معاون ټولګي سره یوځای چې جامد میتودونه وړاندې کوي، لکه د مثال په توګه، د ارتباط ریکارډ څخه د ReliableUdp سرلیک جوړول.

بیا، موږ به په تفصیل سره د انٹرفیس میتودونو پلي کولو ته پام وکړو چې د پروتوکول اساسي الګوریتمونه ټاکي.

د DisposeByTimeout طریقه

د DisposeByTimeout میتود د وخت پای ته رسیدو وروسته د پیوستون سرچینې خوشې کولو او د بریالي / ناکام پیغام رسولو سیګنال کولو لپاره مسؤل دی.
ReliableUdpState.DisposeByTimeout:

protected virtual void DisposeByTimeout(object record)
{
  ReliableUdpConnectionRecord connectionRecord = (ReliableUdpConnectionRecord) record;      
  if (record.AsyncResult != null)
  {
    connectionRecord.AsyncResult.SetAsCompleted(false);
  }
  connectionRecord.Dispose();
}

دا یوازې په دولت کې له پامه غورځول کیږي بشپړ شوي.
بشپړ شوی.DisposeByTimeout:

protected override void DisposeByTimeout(object record)
{
  ReliableUdpConnectionRecord connectionRecord = (ReliableUdpConnectionRecord) record;
  // сообщаем об успешном получении сообщения
  SetAsCompleted(connectionRecord);        
}

د پروسس پیکټ طریقه

د ProcessPackets میتود د کڅوړې یا کڅوړو اضافي پروسس کولو مسؤلیت لري. مستقیم یا د کڅوړې انتظار ټیمر له لارې تلیفون شوی.

وړ برابرول میتود له پامه غورځول شوی او د ورک شوي کڅوړو چک کولو او دولت ته د لیږد مسؤلیت لري بشپړ شويد وروستي پاکټ ترلاسه کولو او بریالي چک تیرولو په صورت کې
د پروسس پیکټونه راټولول:

public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
  if (connectionRecord.IsDone != 0)
    return;
  if (!ReliableUdpStateTools.CheckForNoPacketLoss(connectionRecord, connectionRecord.IsLastPacketReceived != 0))
  {
    // есть потерянные пакеты, отсылаем запросы на них
    foreach (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;
    }
    // если после двух попыток срабатываний WaitForPacketTimer 
    // не удалось получить пакеты - запускаем таймер завершения соединения
    StartCloseWaitTimer(connectionRecord);
  }
  else if (connectionRecord.IsLastPacketReceived != 0)
  // успешная проверка 
  {
    // высылаем подтверждение о получении блока данных
    ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
    connectionRecord.State = connectionRecord.Tcb.States.Completed;
    connectionRecord.State.ProcessPackets(connectionRecord);
    // вместо моментальной реализации ресурсов
    // запускаем таймер, на случай, если
    // если последний ack не дойдет до отправителя и он запросит его снова.
    // по срабатыванию таймера - реализуем ресурсы
    // в состоянии Completed метод таймера переопределен
    StartCloseWaitTimer(connectionRecord);
  }
  // это случай, когда ack на блок пакетов был потерян
  else
  {
    if (!connectionRecord.TimerSecondTry)
    {
      ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
      connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
      connectionRecord.TimerSecondTry = true;
      return;
    }
    // запускаем таймер завершения соединения
    StartCloseWaitTimer(connectionRecord);
  }
}

وړ د لیږلو سایکل دا طریقه یوازې په ټایمر کې ویل کیږي، او د وروستي پیغام د بیا لیږلو مسولیت لري، او همدارنګه د پیوستون نږدې ټایمر فعالوي.
SendingCycle.ProcessPackets:

public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
  if (connectionRecord.IsDone != 0)
    return;        
  // отправляем повторно последний пакет 
  // ( в случае восстановления соединения узел-приемник заново отправит запросы, которые до него не дошли)        
  ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.RetransmissionCreateUdpPayload(connectionRecord, connectionRecord.SndNext - 1));
  // включаем таймер CloseWait – для ожидания восстановления соединения или его завершения
  StartCloseWaitTimer(connectionRecord);
}

وړ بشپړ شوي میتود د چلولو ټایمر ودروي او پیرودونکو ته پیغام لیږي.
بشپړ شوي پروسس پیکټونه:

public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
  if (connectionRecord.WaitForPacketsTimer != null)
    connectionRecord.WaitForPacketsTimer.Dispose();
  // собираем сообщение и передаем его подписчикам
  ReliableUdpStateTools.CreateMessageFromMemoryStream(connectionRecord);
}

د ترلاسه کولو پیکټ طریقه

وړ لومړی پاکټ ترلاسه شو د میتود اصلي دنده دا ده چې معلومه کړي چې ایا لومړی پیغام پاکټ واقعیا انٹرفیس ته رسیدلی ، او همدارنګه د پیغام راټولول چې یو واحد پاکټ لري.
لومړی پیکټ ترلاسه شوی.پیکټ ترلاسه کول:

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  if (!header.Flags.HasFlag(ReliableUdpHeaderFlags.FirstPacket))
    // отбрасываем пакет
    return;
  // комбинация двух флагов - FirstPacket и LastPacket - говорит что у нас единственное сообщение
  if (header.Flags.HasFlag(ReliableUdpHeaderFlags.FirstPacket) &
      header.Flags.HasFlag(ReliableUdpHeaderFlags.LastPacket))
  {
    ReliableUdpStateTools.CreateMessageFromSinglePacket(connectionRecord, header, payload.Slice(ReliableUdpHeader.Length, payload.Length));
    if (!header.Flags.HasFlag(ReliableUdpHeaderFlags.NoAsk))
    {
      // отправляем пакет подтверждение          
      ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
    }
    SetAsCompleted(connectionRecord);
    return;
  }
  // by design все packet numbers начинаются с 0;
  if (header.PacketNumber != 0)          
    return;
  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);
  }
  else
  {
    ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
    connectionRecord.WaitForPacketsTimer = new Timer(CheckByTimer, connectionRecord, connectionRecord.ShortTimerPeriod, -1);
  }
}

وړ د لیږلو سایکل دا طریقه د سپارلو اعترافونو او د بیا لیږد غوښتنې منلو لپاره له پامه غورځول کیږي.
SendingCycle.ReceivePacket:

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  if (connectionRecord.IsDone != 0)
    return;
  if (!header.Flags.HasFlag(ReliableUdpHeaderFlags.RequestForPacket))
    return;
  // расчет конечной границы окна
  // берется граница окна + 1, для получения подтверждений доставки
  int windowHighestBound = Math.Min((connectionRecord.WindowLowerBound + connectionRecord.WindowSize), (connectionRecord.NumberOfPackets));
  // проверка на попадание в окно        
  if (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 connectionRecord.IsDone);
    SetAsCompleted(connectionRecord);
    return;
  }
  // это ответ на первый пакет c подтверждением         
  if ((header.Flags.HasFlag(ReliableUdpHeaderFlags.FirstPacket) && header.PacketNumber == 1))
  {
    // без сдвига окна
    SendPacket(connectionRecord);
  }
  // пришло подтверждение о получении блока данных
  else if (header.PacketNumber == windowHighestBound)
  {
    // сдвигаем окно прием/передачи
    connectionRecord.WindowLowerBound += connectionRecord.WindowSize;
    // обнуляем массив контроля передачи
    connectionRecord.WindowControlArray.Nullify();
    // отправляем блок пакетов
    SendPacket(connectionRecord);
  }
  // это запрос на повторную передачу – отправляем требуемый пакет          
  else
    ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.RetransmissionCreateUdpPayload(connectionRecord, header.PacketNumber));
}

وړ برابرول د ReceivePacket میتود کې، د راتلونکو پاکټونو څخه د پیغام راټولولو اصلي کار ترسره کیږي.
راټولول.پیکټ ترلاسه کول:

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, 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);
    // если получили пакет с последним флагом - делаем завершаем          
    if (header.Flags.HasFlag(ReliableUdpHeaderFlags.LastPacket))
    {
      connectionRecord.State = connectionRecord.Tcb.States.Completed;
      connectionRecord.State.ProcessPackets(connectionRecord);
    }
    return;
  }        
  // расчет конечной границы окна
  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 - connectionRecord.WindowLowerBound] = header.PacketNumber;
  // устанавливаем наибольший пришедший пакет        
  if (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 connectionRecord.IsLastPacketReceived);
  }
  // если нам пришли все пакеты окна, то сбрасываем счетчик
  // и высылаем пакет подтверждение
  else if (connectionRecord.PacketCounter == connectionRecord.WindowSize)
  {
    // сбрасываем счетчик.      
    connectionRecord.PacketCounter = 0;
    // сдвинули окно передачи
    connectionRecord.WindowLowerBound += connectionRecord.WindowSize;
    // обнуление массива управления передачей
    connectionRecord.WindowControlArray.Nullify();
    ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
  }
  // если последний пакет уже имеется        
  if (Thread.VolatileRead(ref connectionRecord.IsLastPacketReceived) != 0)
  {
    // проверяем пакеты          
    ProcessPackets(connectionRecord);
  }
}

وړ بشپړ شوي د میتود یوازینۍ دنده دا ده چې د پیغام بریالۍ تحویلۍ بیا اعتراف واستول شي.
بشپړ شوی.د ترلاسه کولو پیکټ:

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  // повторная отправка последнего пакета в связи с тем,
  // что последний ack не дошел до отправителя
  if (header.Flags.HasFlag(ReliableUdpHeaderFlags.LastPacket))
  {
    ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
  }
}

د بسته بندۍ طریقه

وړ FirstPacketSending دا طریقه د معلوماتو لومړۍ کڅوړه لیږي، یا که پیغام د سپارلو تایید ته اړتیا نلري، ټول پیغام.
FirstPacketSending.SendPacket:

public override void SendPacket(ReliableUdpConnectionRecord connectionRecord)
{
  connectionRecord.PacketCounter = 0;
  connectionRecord.SndNext = 0;
  connectionRecord.WindowLowerBound = 0;       
  // если подтверждения не требуется - отправляем все пакеты
  // и высвобождаем ресурсы
  if (connectionRecord.IsNoAnswerNeeded)
  {
    // Здесь происходит отправка As Is
    do
    {
      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.SendPacket:

public override void SendPacket(ReliableUdpConnectionRecord connectionRecord)
{      
  // отправляем блок пакетов      
  for (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 );
  }
}

په کوډ کې ژور. د اړیکو رامینځته کول او رامینځته کول

اوس چې موږ لومړني دولتونه او هغه میتودونه لیدلي چې د دولتونو اداره کولو لپاره کارول کیږي ، راځئ چې یو څو مثالونه مات کړو چې څنګه پروتوکول په یو څه نور تفصیل سره کار کوي.
د نورمال شرایطو لاندې د معلوماتو لیږد ډیاګرام:د .Net لپاره د باور وړ Udp پروتوکول پلي کول

په تفصیل سره د جوړولو په اړه فکر وکړئ د ارتباط ریکارډ لومړی بسته وصل کړئ او واستوئ. لیږد تل د غوښتنلیک لخوا پیل کیږي چې د لیږلو پیغام API ته زنګ ووهي. بیا، د لیږد کنټرول بلاک د StartTransmission میتود غوښتنه کیږي، کوم چې د نوي پیغام لپاره د معلوماتو لیږد پیل کوي.
د وتلو اړیکې رامینځته کول:

private void StartTransmission(ReliableUdpMessage reliableUdpMessage, EndPoint endPoint, AsyncResultSendMessage asyncResult)
{
  if (m_isListenerStarted == 0)
  {
    if (this.LocalEndpoint == null)
    {
      throw new ArgumentNullException( "", "You must use constructor with parameters or start listener before sending message" );
    }
    // запускаем обработку входящих пакетов
    StartListener(LocalEndpoint);
  }
  // создаем ключ для словаря, на основе EndPoint и ReliableUdpHeader.TransmissionId        
  byte[] transmissionId = new byte[4];
  // создаем случайный номер transmissionId        
  m_randomCrypto.GetBytes(transmissionId);
  Tuple<EndPoint, Int32> key = new Tuple<EndPoint, Int32>(endPoint, BitConverter.ToInt32(transmissionId, 0));
  // создаем новую запись для соединения и проверяем, 
  // существует ли уже такой номер в наших словарях
  if (!m_listOfHandlers.TryAdd(key, new ReliableUdpConnectionRecord(key, this, reliableUdpMessage, asyncResult)))
  {
    // если существует – то повторно генерируем случайный номер 
    m_randomCrypto.GetBytes(transmissionId);
    key = new Tuple<EndPoint, Int32>(endPoint, BitConverter.ToInt32(transmissionId, 0));
    if (!m_listOfHandlers.TryAdd(key, new ReliableUdpConnectionRecord(key, this, reliableUdpMessage, asyncResult)))
      // если снова не удалось – генерируем исключение
      throw new ArgumentException("Pair TransmissionId & EndPoint is already exists in the dictionary");
  }
  // запустили состояние в обработку         
  m_listOfHandlers[key].State.SendPacket(m_listOfHandlers[key]);
}

د لومړۍ کڅوړې لیږل (FirstPacketSending State):

public override void SendPacket(ReliableUdpConnectionRecord connectionRecord)
{
  connectionRecord.PacketCounter = 0;
  connectionRecord.SndNext = 0;
  connectionRecord.WindowLowerBound = 0;       
  // ... 
  // создаем заголовок пакета и отправляем его 
  ReliableUdpHeader header = ReliableUdpStateTools.CreateReliableUdpHeader(connectionRecord);
  ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.CreateUdpPayload(connectionRecord, header));
  // увеличиваем счетчик
  connectionRecord.SndNext++;
  // сдвигаем окно
  connectionRecord.WindowLowerBound++;
  // переходим в состояние SendingCycle
  connectionRecord.State = connectionRecord.Tcb.States.SendingCycle;
  // Запускаем таймер
  connectionRecord.WaitForPacketsTimer = new Timer(CheckByTimer, connectionRecord, connectionRecord.ShortTimerPeriod, -1);
}

د لومړۍ کڅوړې له لیږلو وروسته ، لیږونکی دولت ته ننوځي د لیږلو سایکل - د کڅوړې تحویلۍ تایید ته انتظار وکړئ.
د ترلاسه کولو اړخ، د EndReceive میتود په کارولو سره، لیږل شوی پاکټ ترلاسه کوي، یو نوی رامینځته کوي د ارتباط ریکارډ او دا پاکټ د پری پارس شوي سرلیک سره د پروسس لپاره د ریاست ترلاسه کولو پیکټ میتود ته لیږدوي لومړی پاکټ ترلاسه شو
په ترلاسه کونکي اړخ کې د اړیکې رامینځته کول:

private void EndReceive(IAsyncResult ar)
{
  // ...
  // пакет получен
  // парсим заголовок пакета        
  ReliableUdpHeader header;
  if (!ReliableUdpStateTools.ReadReliableUdpHeader(bytes, out header))
  {          
    // пришел некорректный пакет - отбрасываем его
    return;
  }
  // конструируем ключ для определения connection record’а для пакета
  Tuple<EndPoint, Int32> key = new Tuple<EndPoint, Int32>(connectedClient, header.TransmissionId);
  // получаем существующую connection record или создаем новую
  ReliableUdpConnectionRecord record = m_listOfHandlers.GetOrAdd(key, new ReliableUdpConnectionRecord(key, this, header. ReliableUdpMessageType));
  // запускаем пакет в обработку в конечный автомат
  record.State.ReceivePacket(record, header, bytes);
}

د لومړۍ کڅوړې ترلاسه کول او د اعتراف لیږل (لومړی پیکټ ترلاسه شوی حالت):

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  if (!header.Flags.HasFlag(ReliableUdpHeaderFlags.FirstPacket))
    // отбрасываем пакет
    return;
  // ...
  // by design все packet numbers начинаются с 0;
  if (header.PacketNumber != 0)          
    return;
  // инициализируем массив для хранения частей сообщения
  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 (/*если не требуется механизм подтверждение*/)
  // ...
  else
  {
    // отправляем подтверждение
    ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
    connectionRecord.WaitForPacketsTimer = new Timer(CheckByTimer, connectionRecord, connectionRecord.ShortTimerPeriod, -1);
  }
}

په کوډ کې ژور. د وخت په تیریدو سره پیوستون بندول

د وخت پای ته رسیدل د باور وړ UDP یوه مهمه برخه ده. یو مثال په پام کې ونیسئ په کوم کې چې یو منځنی نوډ ناکام شو او په دواړو لارښوونو کې د معلوماتو تحویل ناممکن شو.
د وخت په تیریدو سره د پیوستون بندولو لپاره ډیاګرام:د .Net لپاره د باور وړ Udp پروتوکول پلي کول

لکه څنګه چې د ډیاګرام څخه لیدل کیدی شي، د لیږونکي کاري ټیمر د کڅوړو بلاک لیږلو سمدستي وروسته پیل کیږي. دا د دولت د SendPacket میتود کې پیښیږي د لیږلو سایکل.
د کاري ټیمر فعالول (د لیږلو سایکل حالت):

public override void SendPacket(ReliableUdpConnectionRecord connectionRecord)
{      
  // отправляем блок пакетов   
  // ...   
  // перезапускаем таймер после отправки
  connectionRecord.WaitForPacketsTimer.Change( connectionRecord.ShortTimerPeriod, -1 );
  if ( connectionRecord.CloseWaitTimer != null )
    connectionRecord.CloseWaitTimer.Change( -1, -1 );
}

د ټایمر دورې ټاکل کیږي کله چې اړیکه رامینځته شي. د ډیفالټ لنډ وخت موده 5 ثانیې ده. په مثال کې، دا 1,5 ثانیو ته ټاکل شوی.

د راتلوونکي پیکټ لپاره ، ټایمر د وروستي راتلونکي ډیټا پاکټ ترلاسه کولو وروسته پیل کیږي ، دا د ریاست ترلاسه کولو پیکټ میتود کې پیښیږي برابرول
د کاري ټیمر فعالول (د راټولولو حالت):

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  // ... 
  // перезапускаем таймеры        
  connectionRecord.TimerSecondTry = false;
  connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
  if (connectionRecord.CloseWaitTimer != null)
    connectionRecord.CloseWaitTimer.Change(-1, -1);
  // ...
}

د کاري ټایمر په انتظار کې نور کڅوړې راتلونکي پیکټ ته ندي رسیدلي. ټایمر روان شو او د پروسس پیکټ میتود ته یې زنګ وواهه ، چیرې چې ورک شوي پاکټونه وموندل شول او د لومړي ځل لپاره د بیرته سپارلو غوښتنې واستول شوې.
د بیا سپارلو غوښتنې لیږل (د ریاست راټولول):

public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
  // ...        
  if (/*проверка на потерянные пакеты */)
  {
    // отправляем запросы на повторную доставку
    // устанавливаем таймер во второй раз, для повторной попытки передачи
    if (!connectionRecord.TimerSecondTry)
    {
      connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
    connectionRecord.TimerSecondTry = true;
    return;
    }
  // если после двух попыток срабатываний WaitForPacketTimer 
  // не удалось получить пакеты - запускаем таймер завершения соединения
  StartCloseWaitTimer(connectionRecord);
  }
  else if (/*пришел последний пакет и успешная проверка */)
  {
    // ...
    StartCloseWaitTimer(connectionRecord);
  }
  // если ack на блок пакетов был потерян
  else
  { 
    if (!connectionRecord.TimerSecondTry)
    {
      // повторно отсылаем ack
      connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
      connectionRecord.TimerSecondTry = true;
      return;
    }
    // запускаем таймер завершения соединения
    StartCloseWaitTimer(connectionRecord);
  }
}

د TimerSecondTry متغیر ټاکل شوی دی رښتيا. دا متغیر د کاري ټایمر بیا پیل کولو لپاره مسؤل دی.

د لیږونکي اړخ کې، کاري ټایمر هم پیل شوی او وروستی لیږل شوی پاکټ بیا لیږل کیږي.
د پیوستون نږدې ټایمر فعالول (د لیږلو سایکل حالت):

public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
  // ...        
  // отправляем повторно последний пакет 
  // ...        
  // включаем таймер CloseWait – для ожидания восстановления соединения или его завершения
  StartCloseWaitTimer(connectionRecord);
}

له هغې وروسته، د پیوستون نږدې ټایمر په وتلو پیوستون کې پیل کیږي.
ReliableUdpState.StartCloseWaitTimer:

protected void StartCloseWaitTimer(ReliableUdpConnectionRecord connectionRecord)
{
  if (connectionRecord.CloseWaitTimer != null)
    connectionRecord.CloseWaitTimer.Change(connectionRecord.LongTimerPeriod, -1);
  else
    connectionRecord.CloseWaitTimer = new Timer(DisposeByTimeout, connectionRecord, connectionRecord.LongTimerPeriod, -1);
}

د پیوستون نږدې ټایمر دوره د ډیفالټ له مخې 30 ثانیې ده.

د لږ وخت وروسته ، د ترلاسه کونکي اړخ کې کاري ټایمر بیا ډزې کوي ، غوښتنې بیا لیږل کیږي ، له هغې وروسته د اړیکې نږدې ټایمر د راتلونکي پیوستون لپاره پیل کیږي

کله چې نږدې ټایمر ډزې کوي، د دواړو پیوستون ریکارډ ټولې سرچینې خوشې کیږي. لیږونکی د اپ سټریم غوښتنلیک ته د تحویلي ناکامۍ راپور ورکوي (د اعتبار وړ UDP API وګورئ).
د پیوستون ریکارډ سرچینې خوشې کول:

public void Dispose()
{
  try
  {
    System.Threading.Monitor.Enter(this.LockerReceive);
  }
  finally
  {
    Interlocked.Increment(ref this.IsDone);
    if (WaitForPacketsTimer != null)
    {
      WaitForPacketsTimer.Dispose();
    }
    if (CloseWaitTimer != null)
    {
      CloseWaitTimer.Dispose();
    }
    byte[] stream;
    Tcb.IncomingStreams.TryRemove(Key, out stream);
    stream = null;
    Tcb.OutcomingStreams.TryRemove(Key, out stream);
    stream = null;
    System.Threading.Monitor.Exit(this.LockerReceive);
  }
}

په کوډ کې ژور. د معلوماتو لیږد بیرته راګرځول

د پیکټ له لاسه ورکولو په صورت کې د ډیټا لیږد بیا رغونې ډیاګرام:د .Net لپاره د باور وړ Udp پروتوکول پلي کول

لکه څنګه چې دمخه د وخت په تیریدو سره د پیوستون بندولو په اړه بحث شوی ، کله چې کاري ټایمر پای ته ورسیږي ، ترلاسه کونکی به ورک شوي پاکټونه وګوري. د پاکټ د ضایع کیدو په صورت کې، د هغو کڅوړو لیست چې ترلاسه کونکي ته ندي رسیدلي، به ترتیب شي. دا شمیرې د یو ځانګړي پیوستون لوسټ پیکټ سرې ته داخل شوي ، او د بیا تحویلۍ غوښتنې لیږل کیږي.
د بیا رسولو کڅوړو ته د غوښتنې لیږل (د ریاست راټولول):

public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
  //...
  if (!ReliableUdpStateTools.CheckForNoPacketLoss(connectionRecord, connectionRecord.IsLastPacketReceived != 0))
  {
    // есть потерянные пакеты, отсылаем запросы на них
    foreach (int seqNum in connectionRecord.LostPackets)
    {
      if (seqNum != 0)
      {
        ReliableUdpStateTools.SendAskForLostPacket(connectionRecord, seqNum);
      }
    }
    // ...
  }
}

لیږونکی به د بیا سپارلو غوښتنه ومني او ورک شوي کڅوړې به واستوي. دا د یادونې وړ ده چې پدې وخت کې لیږونکي دمخه د اړیکې نږدې ټایمر پیل کړی او کله چې غوښتنه ترلاسه شي ، نو بیا تنظیم کیږي.
ورک شوي پاکټونه بیا لیږل (د لیږلو سایکل حالت):

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  // ...
  connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
  // сброс таймера закрытия соединения 
  if (connectionRecord.CloseWaitTimer != null)
    connectionRecord.CloseWaitTimer.Change(-1, -1);
  // ...
  // это запрос на повторную передачу – отправляем требуемый пакет          
  else
    ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.RetransmissionCreateUdpPayload(connectionRecord, header.PacketNumber));
}

بیا لیږل شوی پاکټ (په ډیاګرام کې کڅوړه #3) د راتلونکی پیوستون لخوا ترلاسه کیږي. یو چک ترسره کیږي ترڅو وګوري چې ایا د ترلاسه کولو کړکۍ ډکه ده او د معلوماتو نورمال لیږد بحال شوی.
د ترلاسه کولو کړکۍ کې د هټ لپاره چک کول (د راټولولو حالت):

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  // ...
  // увеличиваем счетчик пакетов        
  connectionRecord.PacketCounter++;
  // записываем в массив управления окном текущий номер пакета        
  connectionRecord.WindowControlArray[header.PacketNumber - connectionRecord.WindowLowerBound] = header.PacketNumber;
  // устанавливаем наибольший пришедший пакет        
  if (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);
  // ...
  // если нам пришли все пакеты окна, то сбрасываем счетчик
  // и высылаем пакет подтверждение
  else if (connectionRecord.PacketCounter == connectionRecord.WindowSize)
  {
    // сбрасываем счетчик.      
    connectionRecord.PacketCounter = 0;
    // сдвинули окно передачи
    connectionRecord.WindowLowerBound += connectionRecord.WindowSize;
    // обнуление массива управления передачей
    connectionRecord.WindowControlArray.Nullify();
    ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
  }
  // ...
}

د اعتبار وړ UDP API

د ډیټا لیږد پروتوکول سره متقابل عمل کولو لپاره ، د خلاص باور وړ Udp ټولګي شتون لري ، کوم چې د لیږد کنټرول بلاک باندې پوښ دی. دلته د ټولګي ترټولو مهم غړي دي:

public sealed class ReliableUdp : IDisposable
{
  // получает локальную конечную точку
  public IPEndPoint LocalEndpoint    
  // создает экземпляр ReliableUdp и запускает
  // прослушивание входящих пакетов на указанном IP адресе
  // и порту. Значение 0 для порта означает использование
  // динамически выделенного порта
  public ReliableUdp(IPAddress localAddress, int port = 0) 
  // подписка на получение входящих сообщений
  public ReliableUdpSubscribeObject SubscribeOnMessages(ReliableUdpMessageCallback callback, ReliableUdpMessageTypes messageType = ReliableUdpMessageTypes.Any, IPEndPoint ipEndPoint = null)    
  // отписка от получения сообщений
  public void Unsubscribe(ReliableUdpSubscribeObject subscribeObject)
  // асинхронно отправить сообщение 
  // Примечание: совместимость с XP и Server 2003 не теряется, т.к. используется .NET Framework 4.0
  public Task<bool> SendMessageAsync(ReliableUdpMessage reliableUdpMessage, IPEndPoint remoteEndPoint, CancellationToken cToken)
  // начать асинхронную отправку сообщения
  public IAsyncResult BeginSendMessage(ReliableUdpMessage reliableUdpMessage, IPEndPoint remoteEndPoint, AsyncCallback asyncCallback, Object state)
  // получить результат асинхронной отправки
  public bool EndSendMessage(IAsyncResult asyncResult)  
  // очистить ресурсы
  public void Dispose()    
}

پیغامونه د ګډون له لارې ترلاسه کیږي. د زنګ وهلو میتود لپاره د استازو لاسلیک:

public delegate void ReliableUdpMessageCallback( ReliableUdpMessage reliableUdpMessage, IPEndPoint remoteClient );

Сообщение:

public class ReliableUdpMessage
{
  // тип сообщения, простое перечисление
  public ReliableUdpMessageTypes Type { get; private set; }
  // данные сообщения
  public byte[] Body { get; private set; }
  // если установлено в true – механизм подтверждения доставки будет отключен
  // для передачи конкретного сообщения
  public bool NoAsk { get; private set; }
}

د ځانګړي پیغام ډول او/یا ځانګړي لیږونکي ته د ګډون لپاره، دوه اختیاري پیرامیټونه کارول کیږي: ReliableUdpMessageTypes messageType او IPendPoint ipEndPoint.

د پیغام ډولونه:

public enum ReliableUdpMessageTypes : short
{ 
  // Любое
  Any = 0,
  // Запрос к STUN server 
  StunRequest = 1,
  // Ответ от STUN server
  StunResponse = 2,
  // Передача файла
  FileTransfer =3,
  // ...
}

پیغام په غیر متناسب ډول لیږل کیږي؛ د دې لپاره، پروتوکول د غیر متناسب پروګرام کولو ماډل پلي کوي:

public IAsyncResult BeginSendMessage(ReliableUdpMessage reliableUdpMessage, IPEndPoint remoteEndPoint, AsyncCallback asyncCallback, Object state)

د پیغام لیږلو پایله به ریښتیا وي - که چیرې پیغام په بریالیتوب سره ترلاسه کونکي ته ورسیږي او غلط - که اړیکه د وخت په تیریدو سره تړل شوې وي:

public bool EndSendMessage(IAsyncResult asyncResult)

پایلې

په دې مقاله کې ډیر څه ندي تشریح شوي. د تار سره سمون میکانیزمونه، استثنا او د خطا اداره کول، د غیر متناسب پیغام لیږلو میتودونو پلي کول. مګر د پروتوکول اساس ، د پاکټونو پروسس کولو لپاره د منطق توضیحات ، د پیوستون رامینځته کول ، او د مهال ویش اداره کول باید تاسو ته روښانه وي.

د اعتبار وړ تحویلي پروتوکول ښودل شوي نسخه خورا قوي او انعطاف منونکی دی چې دمخه ټاکل شوي اړتیاوې پوره کړي. مګر زه غواړم اضافه کړم چې تشریح شوي پلي کول ښه کیدی شي. د مثال په توګه، د ټرپټ زیاتولو او په متحرک ډول د ټایمر دورې بدلولو لپاره، میکانیزمونه لکه سلیډینګ کړکۍ او RTT پروتوکول کې اضافه کیدی شي، دا به د ارتباط نوډونو ترمنځ د MTU ټاکلو لپاره میکانیزم پلي کولو لپاره هم ګټور وي (مګر یوازې هغه وخت چې لوی پیغامونه لیږل کیږي) .

ستاسو د پاملرنې څخه مننه، زه ستاسو نظرونو او تبصرو ته سترګې په لار یم.

PS د هغو کسانو لپاره چې توضیحاتو سره علاقه لري یا یوازې د پروتوکول ازموینه کول غواړي ، په GitHube کې د پروژې لینک:
د باور وړ UDP پروژه

ګټورې لینکونه او مقالې

  1. د TCP پروتوکول مشخصات: په انګلیسي کې и په روسي ژبه
  2. د UDP پروتوکول مشخصات: په انګلیسي کې и په روسي ژبه
  3. د RUDP پروتوکول بحث: مسوده-ietf-sigtran-reliable-udp-00
  4. د اعتبار وړ ډیټا پروتوکول: rfc908 и rfc1151
  5. د UDP په اړه د تحویلي تصدیق ساده پلي کول: د .NET او UDP سره ستاسو د شبکې بشپړ کنټرول واخلئ
  6. مقاله چې د NAT لیږد میکانیزمونه تشریح کوي: د شبکې د ادرس ژباړونکو په اوږدو کې د همکار سره اړیکه
  7. د غیر متوازن پروګرام کولو ماډل پلي کول: د CLR اسینکرونوس پروګرام کولو ماډل پلي کول и د IAsyncResult ډیزاین نمونه پلي کولو څرنګوالی
  8. د غیر متناسب پروګرامینګ ماډل د کاري پراساس اسینکرونس نمونې ته پورټ کول (APM په TAP کې):
    TPL او دودیز .NET اسینکرونوس پروګرامونه
    د نورو غیر متناسب نمونو او ډولونو سره اړیکه ونیسئ

تازه کول: مننه mayorovp и sidristij انٹرفیس ته د دندې اضافه کولو نظر لپاره. د زاړه عملیاتي سیسټمونو سره د کتابتون مطابقت نه سرغړونه کیږي، ځکه چې څلورم چوکاټ د XP او 4 سرور دواړو ملاتړ کوي.

سرچینه: www.habr.com

Add a comment