پیاده سازی پروتکل Reliable Udp برای Net

اینترنت خیلی وقت پیش تغییر کرده است. یکی از پروتکل های اصلی اینترنت - UDP توسط برنامه های کاربردی نه تنها برای ارائه دیتاگرام و پخش، بلکه برای ارائه اتصالات "همتا به همتا" بین گره های شبکه استفاده می شود. این پروتکل به دلیل طراحی ساده، کاربردهای زیادی دارد که قبلاً برنامه ریزی نشده بود، با این حال کاستی های پروتکل مانند عدم تحویل تضمینی در هیچ کجا از بین نرفته است. این مقاله پیاده سازی پروتکل تحویل تضمین شده را بر روی UDP توضیح می دهد.
فهرست مطالب:ورود
الزامات پروتکل
هدر UDP قابل اعتماد
اصول کلی پروتکل
زمان‌بندی‌ها و تایمرهای پروتکل
نمودار وضعیت انتقال UDP قابل اعتماد
عمیق تر به کد. واحد کنترل انتقال
عمیق تر به کد. ایالت ها

عمیق تر به کد. ایجاد و ایجاد ارتباطات
عمیق تر به کد. بستن اتصال در زمان پایان
عمیق تر به کد. بازیابی انتقال داده
UDP API قابل اعتماد
نتیجه
لینک ها و مقالات مفید

ورود

معماری اولیه اینترنت یک فضای آدرس همگن را در نظر گرفت که در آن هر گره یک آدرس IP جهانی و منحصر به فرد داشت و می توانست مستقیماً با گره های دیگر ارتباط برقرار کند. اکنون اینترنت، در واقع، معماری متفاوتی دارد - یک منطقه از آدرس های IP جهانی و بسیاری از مناطق با آدرس های خصوصی پنهان شده در پشت دستگاه های NAT.در این معماری، تنها دستگاه‌هایی که در فضای آدرس جهانی هستند می‌توانند به راحتی با هر کسی در شبکه ارتباط برقرار کنند، زیرا دارای یک آدرس IP منحصر به فرد و قابل مسیریابی جهانی هستند. یک گره در یک شبکه خصوصی می تواند به گره های دیگر در همان شبکه متصل شود و همچنین می تواند به سایر گره های شناخته شده در فضای آدرس جهانی متصل شود. این تعامل عمدتاً به دلیل مکانیسم ترجمه آدرس شبکه حاصل می شود. دستگاه های NAT، مانند روترهای Wi-Fi، ورودی های جدول ترجمه ویژه ای را برای اتصالات خروجی ایجاد می کنند و آدرس های IP و شماره پورت ها را در بسته ها تغییر می دهند. این اجازه می دهد تا اتصالات خروجی از شبکه خصوصی به میزبان ها در فضای آدرس جهانی برقرار شود. اما در عین حال، دستگاه‌های NAT معمولاً تمام ترافیک ورودی را مسدود می‌کنند مگر اینکه قوانین جداگانه‌ای برای اتصالات ورودی تنظیم شده باشد.

این معماری اینترنت برای ارتباط کلاینت و سرور به اندازه کافی صحیح است، جایی که کلاینت ها می توانند در شبکه های خصوصی باشند و سرورها یک آدرس جهانی دارند. اما برای اتصال مستقیم دو گره بین آنها مشکلاتی ایجاد می کند مختلف شبکه های خصوصی ارتباط مستقیم بین دو گره برای برنامه‌های نظیر به نظیر مانند انتقال صدا (اسکایپ)، دسترسی از راه دور به رایانه (TeamViewer) یا بازی‌های آنلاین مهم است.

یکی از موثرترین روش ها برای برقراری ارتباط همتا به همتا بین دستگاه ها در شبکه های خصوصی مختلف، سوراخ کاری نام دارد. این تکنیک بیشتر در برنامه های کاربردی مبتنی بر پروتکل UDP استفاده می شود.

اما اگر برنامه شما نیاز به تحویل تضمینی داده دارد، به عنوان مثال، فایل ها را بین رایانه ها منتقل می کنید، استفاده از UDP با مشکلات زیادی روبرو خواهد شد زیرا UDP یک پروتکل تحویل تضمین شده نیست و برخلاف TCP تحویل بسته را به ترتیب ارائه نمی دهد. پروتکل

در این مورد، برای اطمینان از تحویل تضمینی بسته، لازم است یک پروتکل لایه کاربردی پیاده سازی شود که عملکردهای لازم را فراهم می کند و روی UDP کار می کند.

می خواهم فوراً متذکر شوم که یک تکنیک سوراخ کردن TCP برای ایجاد اتصالات TCP بین گره ها در شبکه های خصوصی مختلف وجود دارد، اما به دلیل عدم پشتیبانی بسیاری از دستگاه های NAT از آن، معمولاً به عنوان راه اصلی برای اتصال در نظر گرفته نمی شود. چنین گره هایی

در ادامه این مقاله، من فقط بر اجرای پروتکل تحویل تضمینی تمرکز خواهم کرد. اجرای تکنیک سوراخ کاری UDP در مقالات بعدی توضیح داده خواهد شد.

الزامات پروتکل

  1. تحویل بسته قابل اعتماد که از طریق مکانیسم بازخورد مثبت (به اصطلاح تصدیق مثبت) اجرا می شود.
  2. نیاز به انتقال کارآمد داده های بزرگ، به عنوان مثال. پروتکل باید از ارسال غیر ضروری بسته جلوگیری کند
  3. باید امکان لغو مکانیسم تأیید تحویل (قابلیت عملکرد به عنوان یک پروتکل UDP خالص) وجود داشته باشد.
  4. امکان پیاده سازی حالت فرمان، با تایید هر پیام
  5. واحد اصلی انتقال داده از طریق پروتکل باید یک پیام باشد

این الزامات تا حد زیادی با الزامات پروتکل داده قابل اعتماد که در شرح داده شده است مطابقت دارد 908 и 1151و من هنگام توسعه این پروتکل به آن استانداردها اعتماد کردم.

برای درک این الزامات، اجازه دهید به زمان‌بندی انتقال داده بین دو گره شبکه با استفاده از پروتکل‌های TCP و UDP نگاهی بیندازیم. اجازه دهید در هر دو حالت یک بسته از دست داده شود.
انتقال داده های غیر تعاملی از طریق TCP:پیاده سازی پروتکل Reliable Udp برای Net

همانطور که در نمودار مشاهده می کنید، در صورت از دست رفتن بسته، TCP بسته از دست رفته را شناسایی کرده و با درخواست شماره قطعه از دست رفته، آن را به فرستنده گزارش می دهد.
انتقال داده از طریق پروتکل UDP:پیاده سازی پروتکل Reliable Udp برای Net

UDP هیچ گامی برای تشخیص ضرر انجام نمی دهد. کنترل خطاهای انتقال در پروتکل UDP کاملاً بر عهده برنامه است.

تشخیص خطا در پروتکل TCP با برقراری ارتباط با یک گره پایانی، ذخیره وضعیت آن اتصال، نشان دادن تعداد بایت های ارسال شده در هر هدر بسته و اطلاع رسانی رسیدها با استفاده از یک شماره تایید به دست می آید.

علاوه بر این، برای بهبود عملکرد (یعنی ارسال بیش از یک بخش بدون دریافت تایید)، پروتکل TCP از پنجره انتقال به اصطلاح استفاده می کند - تعداد بایت های داده ای که فرستنده بخش انتظار دریافت آن را دارد.

برای اطلاعات بیشتر در مورد پروتکل TCP رجوع کنید 793، از UDP به 768جایی که در واقع آنها تعریف شده اند.

با توجه به موارد فوق، واضح است که به منظور ایجاد یک پروتکل تحویل پیام قابل اعتماد از طریق UDP (از این پس به عنوان UDP قابل اعتماد، برای پیاده سازی مکانیسم های انتقال داده مشابه TCP لازم است. برای مثال:

  • ذخیره وضعیت اتصال
  • از شماره گذاری بخش استفاده کنید
  • از بسته های تایید ویژه استفاده کنید
  • از یک مکانیسم پنجره سازی ساده برای افزایش توان عملیاتی پروتکل استفاده کنید

علاوه بر این، شما نیاز دارید:

  • سیگنال شروع یک پیام، برای تخصیص منابع برای اتصال
  • سیگنال پایان یک پیام، برای ارسال پیام دریافتی به برنامه بالادستی و انتشار منابع پروتکل
  • به پروتکل مخصوص اتصال اجازه دهید تا مکانیسم تأیید تحویل را غیرفعال کند تا به عنوان UDP "خالص" عمل کند

هدر UDP قابل اعتماد

به یاد بیاورید که یک دیتاگرام UDP در یک دیتاگرام IP کپسوله شده است. بسته UDP قابل اعتماد به طور مناسب در یک دیتاگرام UDP پیچیده شده است.
محصور کردن هدر UDP قابل اعتماد:پیاده سازی پروتکل Reliable Udp برای Net

ساختار هدر Reliable UDP بسیار ساده است:

پیاده سازی پروتکل Reliable Udp برای Net

  • پرچم - پرچم های کنترل بسته
  • MessageType - نوع پیام که توسط برنامه های بالادستی برای اشتراک در پیام های خاص استفاده می شود
  • TransmissionId - شماره ارسال، همراه با آدرس و پورت گیرنده، به طور منحصر به فرد اتصال را شناسایی می کند.
  • PacketNumber - شماره بسته
  • گزینه ها - گزینه های پروتکل اضافی. در مورد بسته اول، برای نشان دادن اندازه پیام استفاده می شود

پرچم ها به شرح زیر است:

  • FirstPacket - اولین بسته پیام
  • NoAsk - پیام نیازی به یک مکانیسم تأیید برای فعال کردن ندارد
  • LastPacket - آخرین بسته پیام
  • RequestForPacket - بسته تایید یا درخواست یک بسته گم شده

اصول کلی پروتکل

از آنجایی که UDP قابل اعتماد بر انتقال پیام تضمین شده بین دو گره متمرکز است، باید بتواند با طرف دیگر ارتباط برقرار کند. برای برقراری ارتباط، فرستنده بسته ای را با پرچم FirstPacket ارسال می کند که پاسخ به آن به معنای برقراری ارتباط خواهد بود. همه بسته‌های پاسخ، یا به عبارت دیگر، بسته‌های تایید، همیشه مقدار فیلد PacketNumber را یک عدد بیشتر از بزرگترین مقدار PacketNumber بسته‌های با موفقیت دریافت می‌کنند. فیلد Options برای اولین بسته ارسالی اندازه پیام است.

مکانیزم مشابهی برای پایان دادن به اتصال استفاده می شود. پرچم LastPacket روی آخرین بسته پیام تنظیم شده است. در بسته پاسخ، شماره آخرین بسته + 1 نشان داده شده است که برای طرف گیرنده به معنای تحویل موفقیت آمیز پیام است.
نمودار برقراری و خاتمه اتصال:پیاده سازی پروتکل Reliable Udp برای Net

هنگامی که اتصال برقرار شد، انتقال داده آغاز می شود. داده ها در بلوک های بسته منتقل می شوند. هر بلوک، به جز آخرین بلوک، حاوی تعداد ثابتی از بسته ها است. برابر با اندازه پنجره دریافت/انتقال است. آخرین بلوک داده ممکن است بسته های کمتری داشته باشد. پس از ارسال هر بلوک، طرف فرستنده منتظر تایید تحویل یا درخواستی برای تحویل مجدد بسته های گم شده می ماند و پنجره دریافت/انتقال را برای دریافت پاسخ ها باز می گذارد. پس از دریافت تاییدیه تحویل بلوک، پنجره دریافت/انتقال تغییر می کند و بلوک بعدی داده ارسال می شود.

طرف دریافت کننده بسته ها را دریافت می کند. هر بسته بررسی می شود تا ببینیم آیا در پنجره انتقال قرار می گیرد یا خیر. بسته ها و موارد تکراری که به پنجره نمی افتند فیلتر می شوند. زیرا اگر اندازه پنجره ثابت باشد و برای گیرنده و فرستنده یکسان باشد، در صورتی که بلوکی از بسته ها بدون از دست دادن تحویل داده شود، پنجره به دریافت بسته های بلوک بعدی داده منتقل می شود و تأیید تحویل داده می شود. ارسال شد. اگر پنجره در مدت زمان تعیین شده توسط تایمر کار پر نشود، چکی شروع می شود که بسته ها تحویل نشده اند و درخواست برای تحویل مجدد ارسال می شود.
نمودار ارسال مجدد:پیاده سازی پروتکل Reliable Udp برای Net

زمان‌بندی‌ها و تایمرهای پروتکل

دلایل متعددی وجود دارد که باعث می شود اتصال برقرار نشود. برای مثال، اگر طرف دریافت کننده آفلاین باشد. در این حالت، هنگام تلاش برای برقراری یک اتصال، اتصال با تایم اوت بسته خواهد شد. پیاده‌سازی Reliable UDP از دو تایمر برای تنظیم زمان‌بندی استفاده می‌کند. اولی، تایمر کار، برای منتظر ماندن برای پاسخ از میزبان راه دور استفاده می شود. اگر در سمت فرستنده شلیک شود، آخرین بسته ارسال شده مجددا ارسال می شود. اگر تایمر در گیرنده منقضی شود، بررسی بسته های گم شده انجام می شود و درخواست برای تحویل مجدد ارسال می شود.

تایمر دوم برای بستن اتصال در صورت عدم ارتباط بین گره ها مورد نیاز است. برای طرف فرستنده، بلافاصله پس از انقضای تایمر کار شروع می شود و منتظر پاسخ از گره راه دور می ماند. اگر در مدت زمان مشخص شده پاسخی داده نشود، اتصال قطع شده و منابع آزاد می شوند. برای سمت گیرنده، تایمر بستن اتصال پس از دوبار منقضی شدن تایمر کار شروع می شود. این برای اطمینان از گم شدن بسته تأیید ضروری است. هنگامی که تایمر منقضی می شود، اتصال نیز قطع می شود و منابع آزاد می شوند.

نمودار وضعیت انتقال UDP قابل اعتماد

اصول پروتکل در یک ماشین حالت محدود پیاده سازی می شود که هر حالت مسئولیت منطق خاصی از پردازش بسته ها را بر عهده دارد.
نمودار وضعیت UDP قابل اعتماد:

پیاده سازی پروتکل Reliable Udp برای Net

تعطیل - در واقع یک حالت نیست، یک نقطه شروع و پایان برای خودکار است. برای ایالت تعطیل یک بلوک کنترل انتقال دریافت می شود که با اجرای یک سرور UDP ناهمزمان، بسته ها را به اتصالات مناسب ارسال می کند و پردازش حالت را شروع می کند.

FirstPacketSending – حالت اولیه ای که در آن ارتباط خروجی هنگام ارسال پیام است.

در این حالت اولین بسته برای پیام های عادی ارسال می شود. برای پیام‌های بدون تأیید ارسال، این تنها حالتی است که کل پیام در آن ارسال می‌شود.

چرخه ارسال - حالت پایه برای انتقال بسته های پیام.

انتقال به آن از دولت FirstPacketSending پس از ارسال اولین بسته پیام انجام می شود. در این حالت است که همه قدردانی ها و درخواست ها برای ارسال مجدد انجام می شود. خروج از آن در دو حالت امکان پذیر است - در صورت تحویل موفقیت آمیز پیام یا با مهلت زمانی.

FirstPacketReceived – حالت اولیه برای گیرنده پیام.

صحت شروع انتقال را بررسی می کند، ساختارهای لازم را ایجاد می کند و تاییدیه دریافت اولین بسته را ارسال می کند.

برای پیامی که از یک بسته تشکیل شده و بدون استفاده از مدرک تحویل ارسال شده است، این تنها حالت است. پس از پردازش چنین پیامی، اتصال بسته می شود.

مونتاژ - حالت اولیه برای دریافت بسته های پیام.

بسته‌ها را برای ذخیره‌سازی موقت می‌نویسد، از دست رفتن بسته‌ها را بررسی می‌کند، تأییدیه‌هایی را برای تحویل بلوکی از بسته‌ها و کل پیام ارسال می‌کند و درخواست‌هایی را برای تحویل مجدد بسته‌های گمشده ارسال می‌کند. در صورت دریافت موفقیت آمیز کل پیام، اتصال به حالت می رود تکمیل شده، در غیر این صورت، مهلت زمانی خارج می شود.

تکمیل شده - بستن اتصال در صورت دریافت موفقیت آمیز کل پیام.

این حالت برای مونتاژ پیام و برای مواردی که تأییدیه تحویل پیام در راه فرستنده گم شده است ضروری است. این حالت با یک مهلت زمانی خارج می شود، اما اتصال با موفقیت بسته شده است.

عمیق تر به کد. واحد کنترل انتقال

یکی از عناصر کلیدی Reliable 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;
  //...
}

عمیق تر به کد. ایالت ها

ایالت ها ماشین حالت پروتکل Reliable UDP را پیاده سازی می کنند، جایی که پردازش اصلی بسته ها انجام می شود. کلاس انتزاعی ReliableUdpState یک رابط برای حالت فراهم می کند:

پیاده سازی پروتکل Reliable Udp برای Net

کل منطق پروتکل توسط کلاس های ارائه شده در بالا، همراه با یک کلاس کمکی که روش های ثابتی را ارائه می دهد، مانند، برای مثال، ساخت هدر 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();
}

فقط در ایالت نادیده گرفته شده است تکمیل شده.
Completed.DisposeByTimeout:

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

روش ProcessPackets

متد ProcessPackets مسئول پردازش اضافی یک بسته یا بسته ها است. تماس مستقیم یا از طریق تایمر بسته انتظار.

قادر مونتاژ این روش نادیده گرفته شده است و مسئول بررسی بسته های گم شده و انتقال به حالت است تکمیل شده، در صورت دریافت آخرین بسته و گذراندن چک موفقیت آمیز
Assembling.Process Packets:

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

قادر تکمیل شده این روش تایمر در حال اجرا را متوقف می کند و پیام را برای مشترکین ارسال می کند.
Completed.ProcessPackets:

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

روش ReceivePacket

قادر FirstPacketReceived وظیفه اصلی روش تعیین اینکه آیا اولین بسته پیام واقعاً به رابط رسیده است یا خیر و همچنین جمع آوری پیامی متشکل از یک بسته واحد است.
FirstPacketReceived.ReceivePacket:

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، کار اصلی مونتاژ یک پیام از بسته های ورودی انجام می شود.
Assembling.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);
  }
}

قادر تکمیل شده تنها وظیفه روش ارسال تأییدیه مجدد از تحویل موفقیت آمیز پیام است.
Completed.ReceivePacket:

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

عمیق تر به کد. ایجاد و ایجاد ارتباطات

اکنون که حالت‌های اصلی و روش‌های مورد استفاده برای رسیدگی به حالت‌ها را دیدیم، اجازه دهید چند نمونه از نحوه عملکرد پروتکل را با جزئیات بیشتری بیان کنیم.
نمودار انتقال داده در شرایط عادی:پیاده سازی پروتکل Reliable Udp برای Net

ایجاد را با جزئیات در نظر بگیرید رکورد اتصال برای اتصال و ارسال اولین بسته. انتقال همیشه توسط برنامه ای که پیام ارسال پیام را فراخوانی می کند، آغاز می شود. در مرحله بعد، روش 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 بسته ارسالی را دریافت می کند و یک بسته جدید ایجاد می کند رکورد اتصال و این بسته را با یک هدر از پیش تجزیه شده به متد ReceivePacket وضعیت برای پردازش ارسال می کند. FirstPacketReceived
ایجاد یک اتصال در سمت گیرنده:

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

دریافت اولین بسته و ارسال تاییدیه (FirstPacketReceived State):

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 قابل اعتماد است. مثالی را در نظر بگیرید که در آن یک گره میانی شکست خورد و تحویل داده در هر دو جهت غیرممکن شد.
نمودار بسته شدن اتصال با وقفه زمانی:پیاده سازی پروتکل Reliable Udp برای Net

همانطور که از نمودار مشاهده می شود، تایمر کاری فرستنده بلافاصله پس از ارسال یک بلوک از بسته ها شروع می شود. این اتفاق در روش SendPacket ایالت می‌افتد چرخه ارسال.
فعال کردن تایمر کار (SendingCycle State):

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

دوره های تایمر هنگام ایجاد اتصال تنظیم می شوند. ShortTimerPeriod پیش فرض 5 ثانیه است. در مثال، روی 1,5 ثانیه تنظیم شده است.

برای اتصال ورودی، تایمر پس از دریافت آخرین بسته داده ورودی شروع می شود، این در روش ReceivePacket حالت رخ می دهد. مونتاژ
فعال کردن تایمر کار (حالت مونتاژ):

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

در حالی که منتظر تایمر کار بود، هیچ بسته دیگری در اتصال ورودی وارد نشد. تایمر خاموش شد و روش ProcessPackets را فراخوانی کرد، جایی که بسته‌های گمشده پیدا شدند و درخواست‌های تحویل مجدد برای اولین بار ارسال شدند.
ارسال درخواست‌های تحویل مجدد (وضعیت مونتاژ):

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 روی تنظیم شده است درست. این متغیر مسئول راه اندازی مجدد تایمر کار می باشد.

در سمت فرستنده، تایمر کار نیز راه اندازی می شود و آخرین بسته ارسال شده مجددا ارسال می شود.
فعال کردن تایمر بستن اتصال (SendingCycle State):

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 ثانیه است.

پس از مدت کوتاهی، تایمر کار در سمت گیرنده دوباره فعال می شود، درخواست ها دوباره ارسال می شوند و پس از آن تایمر بسته شدن اتصال برای اتصال ورودی شروع می شود.

هنگامی که تایمرهای بسته فعال می شوند، تمام منابع هر دو رکورد اتصال آزاد می شوند. فرستنده خطای تحویل را به برنامه بالادستی گزارش می کند (Reliable 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);
  }
}

عمیق تر به کد. بازیابی انتقال داده

نمودار بازیابی انتقال داده در صورت از دست دادن بسته:پیاده سازی پروتکل Reliable Udp برای Net

همانطور که قبلاً در بستن اتصال در زمان پایان بحث شد، هنگامی که تایمر کار منقضی شود، گیرنده بسته های گم شده را بررسی می کند. در صورت مفقود شدن بسته، لیستی از تعداد بسته هایی که به دست گیرنده نرسیده اند، تهیه می شود. این اعداد در آرایه LostPackets یک اتصال خاص وارد می‌شوند و درخواست‌های تحویل مجدد ارسال می‌شوند.
ارسال درخواست برای تحویل مجدد بسته ها (وضعیت مونتاژ):

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

فرستنده درخواست تحویل مجدد را می پذیرد و بسته های گم شده را ارسال می کند. شایان ذکر است که در این لحظه فرستنده قبلاً تایمر بستن اتصال را شروع کرده است و هنگامی که درخواست دریافت می شود، تنظیم مجدد می شود.
ارسال مجدد بسته های گم شده (وضعیت SendingCycle):

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 قابل اعتماد

برای تعامل با پروتکل انتقال داده، یک کلاس Reliable 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: draft-ietf-sigtran-reliable-udp-00
  4. پروتکل داده قابل اعتماد: 908 и 1151
  5. اجرای ساده تایید تحویل بر روی UDP: کنترل کامل شبکه خود را با دات نت و UDP در دست بگیرید
  6. مقاله ای که مکانیسم های پیمایش NAT را توضیح می دهد: ارتباط همتا به همتا در میان مترجمان آدرس شبکه
  7. پیاده سازی مدل برنامه نویسی ناهمزمان: پیاده سازی مدل برنامه نویسی ناهمزمان CLR и نحوه پیاده سازی الگوی طراحی IAsyncResult
  8. انتقال مدل برنامه نویسی ناهمزمان به الگوی ناهمزمان مبتنی بر وظیفه (APM در TAP):
    TPL و برنامه نویسی ناهمزمان دات نت سنتی
    با سایر الگوها و انواع ناهمزمان تعامل داشته باشید

به روز رسانی: متشکرم mayorovp и sidristij برای ایده اضافه کردن یک کار به رابط. سازگاری کتابخانه با سیستم عامل های قدیمی نقض نمی شود، زیرا فریم ورک چهارم از سرور XP و 4 پشتیبانی می کند.

منبع: www.habr.com

اضافه کردن نظر