.Net uchun ishonchli Udp protokolini amalga oshirish

Internet ancha oldin o'zgargan. Internetning asosiy protokollaridan biri - UDP dasturlar tomonidan nafaqat datagrammalar va translyatsiyalarni etkazib berish, balki tarmoq tugunlari o'rtasida "tengdoshga" ulanishni ta'minlash uchun ham qo'llaniladi. Oddiy dizayni tufayli ushbu protokol ilgari rejalashtirilmagan ko'plab foydalanishga ega, ammo protokolning kamchiliklari, masalan, kafolatlangan etkazib berishning yo'qligi hech qaerda yo'qolmagan. Ushbu maqola UDP orqali kafolatlangan yetkazib berish protokolini amalga oshirishni tavsiflaydi.
Mundarija:kirish
Protokol talablari
Ishonchli UDP sarlavhasi
Protokolning umumiy tamoyillari
Taymerlar va protokol taymerlari
Ishonchli UDP uzatish holati diagrammasi
Kodga chuqurroq. uzatishni boshqarish bloki
Kodga chuqurroq. davlatlar

Kodga chuqurroq. Aloqalarni yaratish va o'rnatish
Kodga chuqurroq. Vaqt tugashi bilan ulanishni yopish
Kodga chuqurroq. Ma'lumotlar uzatishni tiklash
Ishonchli UDP API
xulosa
Foydali havolalar va maqolalar

kirish

Internetning asl arxitekturasi har bir tugun global va noyob IP-manzilga ega bo'lgan va boshqa tugunlar bilan to'g'ridan-to'g'ri aloqa qila oladigan bir hil manzil maydonini nazarda tutgan. Endi Internet, aslida, boshqa arxitekturaga ega - global IP-manzillarning bir sohasi va NAT qurilmalari orqasida maxfiy manzillar yashiringan ko'plab sohalar.Ushbu arxitekturada faqat global manzil maydonidagi qurilmalar tarmoqdagi har qanday odam bilan osongina bog'lana oladi, chunki ular noyob, global yo'naltiriladigan IP-manzilga ega. Shaxsiy tarmoqdagi tugun bir xil tarmoqdagi boshqa tugunlarga ulanishi mumkin, shuningdek, global manzil maydonidagi boshqa taniqli tugunlarga ham ulanishi mumkin. Ushbu o'zaro ta'sirga asosan tarmoq manzillarini tarjima qilish mexanizmi tufayli erishiladi. Wi-Fi routerlar kabi NAT qurilmalari chiquvchi ulanishlar uchun maxsus tarjima jadvali yozuvlarini yaratadi va paketlardagi IP manzillari va port raqamlarini o'zgartiradi. Bu shaxsiy tarmoqdan global manzil maydonidagi xostlarga chiqish ulanishlariga imkon beradi. Biroq, shu bilan birga, NAT qurilmalari, agar kiruvchi ulanishlar uchun alohida qoidalar o'rnatilmagan bo'lsa, odatda barcha kiruvchi trafikni bloklaydi.

Internetning ushbu arxitekturasi mijoz-server aloqasi uchun etarlicha to'g'ri, bu erda mijozlar shaxsiy tarmoqlarda bo'lishi mumkin va serverlar global manzilga ega. Ammo bu ikkita tugunning to'g'ridan-to'g'ri ulanishi uchun qiyinchiliklar tug'diradi har xil xususiy tarmoqlar. Ikki tugun o'rtasidagi to'g'ridan-to'g'ri aloqa ovozli uzatish (Skype), kompyuterga masofadan kirish (TeamViewer) yoki onlayn o'yin kabi tengdoshlar orasidagi ilovalar uchun muhimdir.

Turli xil xususiy tarmoqlardagi qurilmalar o'rtasida tengdoshga ulanishni o'rnatishning eng samarali usullaridan biri teshik ochish deb ataladi. Ushbu usul ko'pincha UDP protokoliga asoslangan ilovalarda qo'llaniladi.

Ammo agar sizning ilovangiz ma'lumotlarning kafolatlangan yetkazib berilishini talab qilsa, masalan, siz kompyuterlar o'rtasida fayllarni uzatsangiz, UDP dan foydalanish TCP dan farqli ravishda UDP kafolatlangan yetkazib berish protokoli emasligi va paketlarni tartibda yetkazib berishni ta'minlamasligi sababli ko'p qiyinchiliklarga duch keladi. protokol.

Bunday holda, kafolatlangan paket yetkazib berishni ta'minlash uchun zarur funksionallikni ta'minlaydigan va UDP orqali ishlaydigan dastur qatlami protokolini amalga oshirish talab etiladi.

Darhol ta'kidlashni istardimki, turli xil xususiy tarmoqlardagi tugunlar o'rtasida TCP ulanishlarini o'rnatish uchun TCP teshiklarini ochish texnikasi mavjud, ammo ko'plab NAT qurilmalari uni qo'llab-quvvatlamasligi sababli, odatda ulanishning asosiy usuli sifatida qaralmaydi. bunday tugunlar.

Ushbu maqolaning qolgan qismida men faqat kafolatlangan etkazib berish protokolini amalga oshirishga e'tibor qarataman. UDP teshik ochish texnikasini amalga oshirish keyingi maqolalarda tasvirlanadi.

Protokol talablari

  1. Ijobiy qayta aloqa mexanizmi (ijobiy tasdiq deb ataladigan) orqali amalga oshiriladigan ishonchli paket yetkazib berish.
  2. Katta ma'lumotlarni samarali uzatish zarurati, ya'ni. protokol paketlarni keraksiz uzatishdan qochishi kerak
  3. Yetkazib berishni tasdiqlash mexanizmini bekor qilish mumkin bo'lishi kerak ("sof" UDP protokoli sifatida ishlash qobiliyati)
  4. Har bir xabarni tasdiqlash bilan buyruq rejimini amalga oshirish imkoniyati
  5. Protokol orqali ma'lumotlarni uzatishning asosiy birligi xabar bo'lishi kerak

Ushbu talablar asosan maqolada tasvirlangan Ishonchli ma'lumotlar protokoli talablariga mos keladi RF 908 и RF 1151, va men ushbu protokolni ishlab chiqishda ushbu standartlarga tayandim.

Ushbu talablarni tushunish uchun ikkita tarmoq tugunlari o'rtasida TCP va UDP protokollari yordamida ma'lumotlarni uzatish vaqtini ko'rib chiqaylik. Ikkala holatda ham bitta paket yo'qoladi.
TCP orqali interaktiv bo'lmagan ma'lumotlarni uzatish:.Net uchun ishonchli Udp protokolini amalga oshirish

Diagrammadan ko'rinib turibdiki, paket yo'qolgan taqdirda TCP yo'qolgan paketni aniqlaydi va yo'qolgan segment raqamini so'rab jo'natuvchiga xabar beradi.
UDP protokoli orqali ma'lumotlarni uzatish:.Net uchun ishonchli Udp protokolini amalga oshirish

UDP yo'qotishlarni aniqlash choralarini ko'rmaydi. UDP protokolidagi uzatish xatolarini nazorat qilish to'liq dasturning javobgarligi.

TCP protokolidagi xatolarni aniqlash yakuniy tugun bilan ulanishni o'rnatish, ushbu ulanish holatini saqlash, har bir paket sarlavhasida yuborilgan baytlar sonini ko'rsatish va tasdiqlash raqamidan foydalangan holda kvitansiyalarni xabardor qilish orqali erishiladi.

Bundan tashqari, unumdorlikni yaxshilash uchun (ya'ni, tasdiqnoma olmasdan bir nechta segmentni yuborish) TCP protokoli uzatish oynasi deb ataladigan oynadan foydalanadi - segment jo'natuvchisi olishni kutayotgan ma'lumotlar baytlari soni.

TCP protokoli haqida ko'proq ma'lumot olish uchun qarang RF 793, UDP dan RF 768qaerda, aslida, ular belgilangan.

Yuqoridagilardan ko'rinib turibdiki, UDP (keyingi o'rinlarda deb yuritiladi) orqali ishonchli xabar yetkazib berish protokolini yaratish uchun. Ishonchli UDP), TCP ga o'xshash ma'lumotlarni uzatish mexanizmlarini amalga oshirish talab qilinadi. Aynan:

  • ulanish holatini saqlang
  • segment raqamlashdan foydalaning
  • maxsus tasdiqlash paketlaridan foydalaning
  • protokol o'tkazuvchanligini oshirish uchun soddalashtirilgan oyna mexanizmidan foydalaning

Bundan tashqari, sizga kerak:

  • ulanish uchun resurslarni ajratish uchun xabarning boshlanishini bildiradi
  • qabul qilingan xabarni yuqori oqim ilovasiga uzatish va protokol resurslarini chiqarish uchun xabar tugashini bildiradi
  • ulanishga xos protokolga etkazib berishni tasdiqlash mexanizmini "sof" UDP sifatida ishlashi uchun o'chirishga ruxsat bering

Ishonchli UDP sarlavhasi

Eslatib o'tamiz, UDP datagramma IP-datagrammasiga kiritilgan. Ishonchli UDP paketi UDP datagrammasiga mos ravishda "o'ralgan".
Ishonchli UDP sarlavhasi inkapsulyatsiyasi:.Net uchun ishonchli Udp protokolini amalga oshirish

Ishonchli UDP sarlavhasining tuzilishi juda oddiy:

.Net uchun ishonchli Udp protokolini amalga oshirish

  • Bayroqlar - paketlarni boshqarish bayroqlari
  • MessageType - ma'lum xabarlarga obuna bo'lish uchun yuqori oqim ilovalari tomonidan ishlatiladigan xabar turi
  • TransmissionId - uzatish raqami, qabul qiluvchining manzili va porti bilan birgalikda ulanishni noyob tarzda aniqlaydi.
  • PacketNumber - paket raqami
  • Variantlar - qo'shimcha protokol variantlari. Birinchi paketga kelsak, u xabar hajmini ko'rsatish uchun ishlatiladi

Bayroqlar quyidagicha:

  • FirstPacket - xabarning birinchi paketi
  • NoAsk - xabar tasdiqlash mexanizmini yoqishni talab qilmaydi
  • LastPacket - xabarning oxirgi paketi
  • RequestForPacket - tasdiqlash paketi yoki yo'qolgan paket uchun so'rov

Protokolning umumiy tamoyillari

Ishonchli UDP ikkita tugun o'rtasida kafolatlangan xabar uzatishga qaratilganligi sababli, u boshqa tomon bilan aloqa o'rnatish imkoniyatiga ega bo'lishi kerak. Ulanishni o'rnatish uchun jo'natuvchi FirstPacket bayrog'i bilan paketni yuboradi, unga javob ulanish o'rnatilganligini bildiradi. Barcha javob paketlari yoki boshqacha qilib aytganda, tasdiqlash paketlari, har doim PacketNumber maydonining qiymatini muvaffaqiyatli qabul qilingan paketlarning eng katta PacketNumber qiymatidan biriga o'rnatadi. Birinchi yuborilgan paket uchun Options maydoni xabarning o'lchamidir.

Xuddi shunday mexanizm ulanishni to'xtatish uchun ishlatiladi. LastPacket bayrog'i xabarning oxirgi paketiga o'rnatiladi. Javob paketida oxirgi paketning raqami + 1 ko'rsatilgan, bu qabul qiluvchi tomon uchun xabarning muvaffaqiyatli yetkazib berilishini anglatadi.
Ulanishni o'rnatish va tugatish diagrammasi:.Net uchun ishonchli Udp protokolini amalga oshirish

Ulanish o'rnatilganda ma'lumotlarni uzatish boshlanadi. Ma'lumotlar paketlar bloklarida uzatiladi. Har bir blok, oxirgisidan tashqari, belgilangan miqdordagi paketlarni o'z ichiga oladi. Bu qabul qilish/uzatuvchi oyna hajmiga teng. Ma'lumotlarning oxirgi blokida kamroq paketlar bo'lishi mumkin. Har bir blokni yuborgandan so'ng, jo'natuvchi tomon etkazib berishni tasdiqlash yoki yo'qolgan paketlarni qayta yetkazib berish so'rovini kutadi va javoblarni olish uchun qabul qilish/uzatish oynasini ochiq qoldiradi. Blokni yetkazib berishni tasdiqlashni olgandan so'ng, qabul qilish/uzatish oynasi siljiydi va keyingi ma'lumotlar bloki yuboriladi.

Qabul qiluvchi tomon paketlarni oladi. Har bir paket uzatish oynasiga to'g'ri kelishini tekshirish uchun tekshiriladi. Oynaga tushmaydigan paketlar va dublikatlar filtrlanadi. Chunki Agar oynaning o'lchami sobit bo'lsa va qabul qiluvchi va jo'natuvchi uchun bir xil bo'lsa, paketlar bloki yo'qolmagan holda etkazib berilganda, oyna keyingi ma'lumotlar blokining paketlarini qabul qilish uchun siljiydi va etkazib berishni tasdiqlaydi. yuborilgan. Agar oyna ish taymerida belgilangan muddatda to'ldirilmasa, u holda qaysi paketlar yetkazib berilmaganligi tekshiriladi va qayta yetkazib berish uchun so'rovlar yuboriladi.
Qayta uzatish diagrammasi:.Net uchun ishonchli Udp protokolini amalga oshirish

Taymerlar va protokol taymerlari

Ulanishni o'rnatib bo'lmaydigan bir nechta sabablar mavjud. Misol uchun, agar qabul qiluvchi tomon oflayn bo'lsa. Bunday holda, ulanishni o'rnatishga urinayotganda, ulanish vaqt tugashi bilan yopiladi. Ishonchli UDP ilovasi vaqt tugashini belgilash uchun ikkita taymerdan foydalanadi. Birinchisi, ishchi taymer masofaviy xostdan javob kutish uchun ishlatiladi. Agar u jo'natuvchi tomondan ishga tushsa, oxirgi yuborilgan paket qayta yuboriladi. Agar taymer qabul qiluvchida tugasa, yo'qolgan paketlar tekshiriladi va qayta yetkazib berish uchun so'rovlar yuboriladi.

Ikkinchi taymer tugunlar o'rtasida aloqa yo'q bo'lganda ulanishni yopish uchun kerak. Yuboruvchi tomon uchun u ish taymerining muddati tugagandan so'ng darhol boshlanadi va masofaviy tugundan javob kutadi. Belgilangan muddat ichida javob bo'lmasa, ulanish to'xtatiladi va resurslar chiqariladi. Qabul qiluvchi tomon uchun ulanishni yopish taymeri ish taymerining muddati ikki marta tugaganidan keyin ishga tushiriladi. Bu tasdiqlash paketini yo'qotishdan sug'urta qilish uchun kerak. Taymer muddati tugagach, ulanish ham tugatiladi va resurslar chiqariladi.

Ishonchli UDP uzatish holati diagrammasi

Protokol tamoyillari cheklangan holat mashinasida amalga oshiriladi, uning har bir holati paketlarni qayta ishlashning ma'lum bir mantiqiyligi uchun javobgardir.
Ishonchli UDP holati diagrammasi:

.Net uchun ishonchli Udp protokolini amalga oshirish

yopiq - aslida holat emas, bu avtomat uchun boshlang'ich va yakuniy nuqta. Davlat uchun yopiq uzatishni boshqarish bloki qabul qilinadi, u asinxron UDP serverini amalga oshirib, paketlarni tegishli ulanishlarga yo'naltiradi va holatni qayta ishlashni boshlaydi.

FirstPacketSending – xabar yuborilganda chiquvchi ulanishning dastlabki holati.

Bu holatda oddiy xabarlar uchun birinchi paket yuboriladi. Tasdiqlanmagan xabarlar uchun bu butun xabar yuboriladigan yagona holat.

SendingCycle – xabar paketlarini uzatish uchun asosiy holat.

Davlatdan unga o'tish FirstPacketSending xabarning birinchi paketi yuborilgandan keyin amalga oshiriladi. Aynan shu holatda qayta uzatish uchun barcha tasdiqlar va so'rovlar keladi. Undan chiqish ikki holatda mumkin - xabar muvaffaqiyatli yetkazib berilganda yoki vaqt tugashi bilan.

Birinchi paket qabul qilindi – xabarni qabul qiluvchining dastlabki holati.

U uzatish boshlanishining to'g'riligini tekshiradi, kerakli tuzilmalarni yaratadi va birinchi paketni olganligi to'g'risida tasdiqnoma yuboradi.

Bitta paketdan iborat bo'lgan va etkazib berishni tasdiqlovchi hujjatdan foydalanmasdan yuborilgan xabar uchun bu yagona holat. Bunday xabarni qayta ishlagandan so'ng, ulanish yopiladi.

O'rnatish – xabar paketlarini qabul qilishning asosiy holati.

U paketlarni vaqtincha saqlashga yozadi, paketlar yo‘qolganligini tekshiradi, paketlar bloki va butun xabarni yetkazib berish uchun bildirishnomalarni yuboradi va yo‘qolgan paketlarni qayta yetkazib berish uchun so‘rovlarni yuboradi. Agar butun xabar muvaffaqiyatli qabul qilingan bo'lsa, ulanish holatiga o'tadi Tugallandi, aks holda, kutish vaqti tugaydi.

Tugallandi – butun xabar muvaffaqiyatli qabul qilingan taqdirda ulanishni yopish.

Ushbu holat xabarni yig'ish uchun va jo'natuvchiga jo'natuvchiga yo'lda xabarni etkazib berishni tasdiqlash yo'qolgan holatlar uchun zarurdir. Bu holatdan kutish vaqti tugadi, lekin ulanish muvaffaqiyatli yopilgan deb hisoblanadi.

Kodga chuqurroq. uzatishni boshqarish bloki

Ishonchli UDP ning asosiy elementlaridan biri uzatishni boshqarish blokidir. Ushbu blokning vazifasi joriy ulanishlar va yordamchi elementlarni saqlash, kiruvchi paketlarni mos keladigan ulanishlarga taqsimlash, ulanishga paketlarni yuborish interfeysini ta'minlash va API protokolini amalga oshirishdir. Transmissiyani boshqarish bloki paketlarni UDP qatlamidan oladi va ularni qayta ishlash uchun davlat mashinasiga yuboradi. Paketlarni qabul qilish uchun u asinxron UDP serverini amalga oshiradi.
ReliableUdpConnectionControlBlock sinfining ba'zi a'zolari:

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

Asinxron UDP serverini amalga oshirish:

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

Har bir xabarni uzatish uchun ulanish haqidagi ma'lumotlarni o'z ichiga olgan tuzilma yaratiladi. Bunday tuzilma deyiladi ulanish yozuvi.
ReliableUdpConnectionRecord sinfining ba'zi a'zolari:

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

Kodga chuqurroq. davlatlar

Davlatlar ishonchli UDP protokolining davlat mashinasini amalga oshiradilar, bu erda paketlarni asosiy qayta ishlash amalga oshiriladi. ReliableUdpState mavhum klassi holat uchun interfeysni taqdim etadi:

.Net uchun ishonchli Udp protokolini amalga oshirish

Protokolning butun mantig'i yuqorida keltirilgan sinflar tomonidan, masalan, ulanish yozuvidan ReliableUdp sarlavhasini yaratish kabi statik usullarni ta'minlovchi yordamchi sinf tomonidan amalga oshiriladi.

Keyinchalik, protokolning asosiy algoritmlarini aniqlaydigan interfeys usullarini amalga oshirishni batafsil ko'rib chiqamiz.

DisposeByTimeout usuli

DisposeByTimeout usuli vaqt tugashidan keyin ulanish resurslarini chiqarish va xabarni muvaffaqiyatli/muvaffaqiyatsiz yetkazib berish haqida signal berish uchun javobgardir.
ReliableUdpState.DisposeByTimeout:

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

Bu faqat shtatda bekor qilingan Tugallandi.
Completed.DisposeByTimeout:

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

ProcessPackets usuli

ProcessPackets usuli paket yoki paketlarni qo'shimcha qayta ishlash uchun javobgardir. To'g'ridan-to'g'ri yoki paketli kutish taymer orqali chaqiriladi.

Holatida O'rnatish usul bekor qilingan va yo'qolgan paketlarni tekshirish va holatga o'tish uchun javobgardir Tugallandi, oxirgi paketni olgan va muvaffaqiyatli tekshiruvdan o'tgan taqdirda
Assembling.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);
  }
}

Holatida SendingCycle bu usul faqat taymerda chaqiriladi va oxirgi xabarni qayta yuborish, shuningdek ulanishni yopish taymerini yoqish uchun javobgardir.
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);
}

Holatida Tugallandi usul ishlaydigan taymerni to'xtatadi va xabarni abonentlarga yuboradi.
Bajarildi.ProcessPackets:

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

ReceivePacket usuli

Holatida Birinchi paket qabul qilindi Usulning asosiy vazifasi - birinchi xabar paketi haqiqatda interfeysga kelgan-kelmaganligini aniqlash, shuningdek, bitta paketdan iborat xabarni yig'ish.
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);
  }
}

Holatida SendingCycle bu usul yetkazib berishni tasdiqlash va qayta yuborish so'rovlarini qabul qilish uchun bekor qilingan.
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));
}

Holatida O'rnatish ReceivePacket usulida kiruvchi paketlardan xabar yig'ishning asosiy ishi amalga oshiriladi.
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);
  }
}

Holatida Tugallandi usulning yagona vazifasi - xabarni muvaffaqiyatli yetkazib berishni qayta tan olish.
Bajarildi.Paketni qabul qilish:

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

Paketni yuborish usuli

Holatida FirstPacketSending bu usul ma'lumotlarning birinchi paketini yoki agar xabar yetkazib berishni tasdiqlashni talab qilmasa, butun xabarni yuboradi.
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);
}

Holatida SendingCycle bu usulda paketlar bloki yuboriladi.
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 );
  }
}

Kodga chuqurroq. Aloqalarni yaratish va o'rnatish

Endi biz asosiy holatlarni va holatlarni boshqarishda qo‘llaniladigan usullarni ko‘rib chiqdik, keling, protokol qanday ishlashiga bir necha misollarni biroz batafsilroq ko‘rib chiqaylik.
Oddiy sharoitlarda ma'lumotlarni uzatish diagrammasi:.Net uchun ishonchli Udp protokolini amalga oshirish

Yaratishni batafsil ko'rib chiqing ulanish yozuvi birinchi paketni ulash va yuborish uchun. O'tkazish har doim xabar yuborish API-ni chaqiradigan dastur tomonidan amalga oshiriladi. Keyinchalik, uzatishni boshqarish blokining StartTransmission usuli chaqiriladi, bu yangi xabar uchun ma'lumotlarni uzatishni boshlaydi.
Chiquvchi ulanishni yaratish:

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

Birinchi paketni yuborish (FirstPacketSending holati):

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

Birinchi paketni yuborgandan so'ng, jo'natuvchi davlatga kiradi SendingCycle – paketni yetkazib berish tasdiqlanishini kuting.
Qabul qiluvchi tomon EndReceive usulidan foydalanib, yuborilgan paketni oladi, yangisini yaratadi ulanish yozuvi va bu paketni qayta ishlash uchun davlatning ReceivePacket usuliga oldindan ajratilgan sarlavha bilan uzatadi Birinchi paket qabul qilindi
Qabul qiluvchi tomonda ulanishni yaratish:

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

Birinchi paketni qabul qilish va tasdiqni yuborish (FirstPacketReceived holati):

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

Kodga chuqurroq. Vaqt tugashi bilan ulanishni yopish

Vaqt tugashi bilan ishlash Ishonchli UDP ning muhim qismidir. Oraliq tugun ishlamay qolgan va ikkala yo'nalishda ham ma'lumotlarni yetkazib berish imkonsiz bo'lgan misolni ko'rib chiqing.
Vaqt tugashi bilan ulanishni yopish diagrammasi:.Net uchun ishonchli Udp protokolini amalga oshirish

Diagrammadan ko'rinib turibdiki, jo'natuvchining ish taymeri paketlar blokini yuborgandan so'ng darhol boshlanadi. Bu holatning SendPacket usulida sodir bo'ladi SendingCycle.
Ish taymerini yoqish (SendingCycle holati):

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

Taymer muddatlari ulanish yaratilganda o'rnatiladi. Standart ShortTimerPeriod 5 soniya. Misolda, u 1,5 soniyaga o'rnatiladi.

Kiruvchi ulanish uchun taymer oxirgi kiruvchi ma'lumotlar paketini olgandan keyin ishga tushadi, bu holatning ReceivePacket usulida sodir bo'ladi. O'rnatish
Ish taymerini yoqish (yig'ish holati):

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

Ish taymerini kutish vaqtida kiruvchi ulanishga boshqa paketlar kelmadi. Taymer o'chdi va ProcessPackets usulini chaqirdi, bu erda yo'qolgan paketlar topildi va birinchi marta qayta yetkazib berish so'rovlari yuborildi.
Qayta yetkazib berish so'rovlarini yuborish (yig'ish holati):

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 o'zgaruvchisi o'rnatilgan haqiqiy. Ushbu o'zgaruvchi ish taymerini qayta ishga tushirish uchun javobgardir.

Yuboruvchi tomonida ish taymeri ham ishga tushiriladi va oxirgi yuborilgan paket qayta yuboriladi.
Ulanishni yopish taymerini yoqish (SendingCycle holati):

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

Shundan so'ng, chiquvchi ulanishda ulanishni yopish taymeri boshlanadi.
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);
}

Ulanishni yopish taymerining kutish vaqti sukut bo'yicha 30 soniya.

Qisqa vaqtdan so'ng, qabul qiluvchi tomonidagi ish taymeri yana yonadi, so'rovlar yana yuboriladi, shundan so'ng kiruvchi ulanish uchun ulanishni yopish taymerini ishga tushiradi.

Yopiq taymerlar ishga tushganda, ikkala ulanish yozuvlarining barcha resurslari chiqariladi. Yuboruvchi yuqori oqim ilovasiga yetkazib berishda xatolik haqida xabar beradi (Ishonchli UDP API-ga qarang).
Ulanish yozuvlari resurslarini chiqarish:

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

Kodga chuqurroq. Ma'lumotlar uzatishni tiklash

Paket yo'qolgan taqdirda ma'lumotlarni uzatishni tiklash diagrammasi:.Net uchun ishonchli Udp protokolini amalga oshirish

Taymer tugashi bilan ulanishni yopishda allaqachon muhokama qilinganidek, ish taymerining muddati tugagach, qabul qiluvchi yo'qolgan paketlarni tekshiradi. Paket yo'qolgan taqdirda, qabul qiluvchiga etib bormagan paketlar sonining ro'yxati tuziladi. Bu raqamlar ma'lum bir ulanishning LostPackets massiviga kiritiladi va qayta yetkazib berish uchun so'rovlar yuboriladi.
Paketlarni qayta etkazib berish uchun so'rovlarni yuborish (yig'ish holati):

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

Yuboruvchi qayta yetkazib berish so'rovini qabul qiladi va etishmayotgan paketlarni jo'natadi. Shuni ta'kidlash kerakki, hozirda jo'natuvchi ulanishni yopish taymerini allaqachon ishga tushirgan va so'rov qabul qilinganda u qayta tiklanadi.
Yo'qotilgan paketlarni qayta yuborish (SendingCycle holati):

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

Qayta yuborilgan paket (diagrammada №3 paket) kiruvchi ulanish tomonidan qabul qilinadi. Qabul qilish oynasi to'lganligini va normal ma'lumotlarni uzatish tiklanganligini tekshirish uchun tekshiriladi.
Qabul qilish oynasida urishlarni tekshirish (yig'ish holati):

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

Ishonchli UDP API

Ma'lumotlarni uzatish protokoli bilan o'zaro ishlash uchun ochiq Ishonchli Udp klassi mavjud bo'lib, u uzatishni boshqarish bloki ustidagi o'ram hisoblanadi. Mana, sinfning eng muhim a'zolari:

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

Xabarlar obuna orqali qabul qilinadi. Qayta qo'ng'iroq qilish usuli uchun delegat imzosi:

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

Xabar:

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

Muayyan xabar turiga va/yoki ma'lum bir jo'natuvchiga obuna bo'lish uchun ikkita ixtiyoriy parametr ishlatiladi: ReliableUdpMessageTypes messageType va IPEndPoint ipEndPoint.

Xabar turlari:

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

Xabar asinxron tarzda yuboriladi; buning uchun protokol asinxron dasturlash modelini amalga oshiradi:

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

Xabarni yuborish natijasi rost bo'ladi - agar xabar qabul qiluvchiga muvaffaqiyatli etib borgan bo'lsa va noto'g'ri bo'lsa - ulanish vaqt tugashi bilan yopilgan bo'lsa:

public bool EndSendMessage(IAsyncResult asyncResult)

xulosa

Ushbu maqolada ko'p narsa tasvirlanmagan. Mavzuni moslashtirish mexanizmlari, istisno va xatolarni qayta ishlash, asinxron xabar yuborish usullarini amalga oshirish. Ammo protokolning o'zagi, paketlarni qayta ishlash, ulanishni o'rnatish va taym-autlarni qayta ishlash mantig'ining tavsifi siz uchun tushunarli bo'lishi kerak.

Ishonchli yetkazib berish protokolining ko'rsatilgan versiyasi ilgari belgilangan talablarga javob beradigan darajada mustahkam va moslashuvchan. Ammo shuni qo'shimcha qilmoqchimanki, tasvirlangan dasturni yaxshilash mumkin. Masalan, o'tkazuvchanlikni oshirish va taymer davrlarini dinamik ravishda o'zgartirish uchun protokolga surma oynasi va RTT kabi mexanizmlar qo'shilishi mumkin, shuningdek ulanish tugunlari o'rtasida MTU ni aniqlash mexanizmini amalga oshirish foydali bo'ladi (lekin faqat katta xabarlar yuborilsa) .

E'tiboringiz uchun rahmat, fikr va mulohazalaringizni kutaman.

PS Tafsilotlarga qiziqqanlar yoki shunchaki protokolni sinab ko'rmoqchi bo'lganlar uchun GitHube-dagi loyihaga havola:
Ishonchli UDP loyihasi

Foydali havolalar va maqolalar

  1. TCP protokoli spetsifikatsiyasi: inzgliz tilida и rus tilida
  2. UDP protokoli spetsifikatsiyasi: inzgliz tilida и rus tilida
  3. RUDP protokolining muhokamasi: qoralama-ietf-sigtran-ishonchli-udp-00
  4. Ishonchli ma'lumotlar protokoli: RF 908 и RF 1151
  5. UDP orqali yetkazib berishni tasdiqlashning oddiy amalga oshirilishi: .NET va UDP bilan tarmog'ingizni to'liq nazorat qiling
  6. NAT o'tish mexanizmlarini tavsiflovchi maqola: Tarmoq manzili tarjimonlari bo'ylab tengdoshlar bilan aloqa
  7. Asinxron dasturlash modelini amalga oshirish: CLR asinxron dasturlash modelini joriy qilish и IAsyncResult dizayn namunasini qanday amalga oshirish kerak
  8. Asinxron dasturlash modelini vazifaga asoslangan asinxron naqshga o'tkazish (TAPda APM):
    TPL va an'anaviy .NET asinxron dasturlash
    Boshqa asinxron naqshlar va turlar bilan o'zaro ishlash

Yangilash: rahmat mayorovp и sidristij interfeysga vazifa qo'shish g'oyasi uchun. Kutubxonaning eski operatsion tizimlar bilan mosligi buzilmaydi, chunki 4-ramka XP va 2003 serverlarini qo'llab-quvvatlaydi.

Manba: www.habr.com

a Izoh qo'shish