Implementering van die Reliable Udp-protokol vir .Net

Die internet het lank gelede verander. Een van die hoofprotokolle van die internet - UDP word deur toepassings gebruik om nie net datagramme en uitsendings te lewer nie, maar ook om "peer-to-peer"-verbindings tussen netwerknodusse te verskaf. As gevolg van sy eenvoudige ontwerp, het hierdie protokol baie voorheen onbeplande gebruike, maar die tekortkominge van die protokol, soos die gebrek aan gewaarborgde aflewering, het nêrens verdwyn nie. Hierdie artikel beskryf die implementering van die gewaarborgde afleweringsprotokol oor UDP.
Inhoud:Entry
Protokolvereistes
Betroubare UDP-kopskrif
Algemene beginsels van die protokol
Time-outs en protokol timers
Betroubare UDP-transmissietoestanddiagram
Dieper in die kode. transmissie beheer eenheid
Dieper in die kode. state

Dieper in die kode. Skep en vestiging van verbindings
Dieper in die kode. Sluit die verbinding met uitteltyd
Dieper in die kode. Herstel tans data-oordrag
Betroubare UDP API
Gevolgtrekking
Nuttige skakels en artikels

Entry

Die oorspronklike argitektuur van die internet het 'n homogene adresruimte aanvaar waarin elke nodus 'n globale en unieke IP-adres gehad het en direk met ander nodusse kon kommunikeer. Nou het die internet in werklikheid 'n ander argitektuur - een gebied van wêreldwye IP-adresse en baie gebiede met private adresse versteek agter NAT-toestelle.In hierdie argitektuur kan slegs toestelle in die globale adresruimte maklik met enigiemand op die netwerk kommunikeer omdat hulle 'n unieke, wêreldwyd herleibare IP-adres het. 'n Nodus op 'n private netwerk kan aan ander nodusse op dieselfde netwerk koppel, en kan ook aan ander bekende nodusse in die globale adresruimte koppel. Hierdie interaksie word grootliks bereik as gevolg van die netwerkadresvertalingsmeganisme. NAT-toestelle, soos Wi-Fi-roeteerders, skep spesiale vertaaltabelinskrywings vir uitgaande verbindings en verander IP-adresse en poortnommers in pakkies. Dit laat uitgaande verbindings vanaf die private netwerk na gashere in die globale adresruimte toe. Maar terselfdertyd blokkeer NAT-toestelle gewoonlik alle inkomende verkeer, tensy aparte reëls vir inkomende verbindings gestel word.

Hierdie argitektuur van die internet is korrek genoeg vir kliënt-bediener kommunikasie, waar kliënte in private netwerke kan wees, en bedieners het 'n globale adres. Maar dit skep probleme vir die direkte verbinding van twee nodusse tussen verskillende private netwerke. 'n Direkte verbinding tussen twee nodusse is belangrik vir eweknie-toepassings soos stemoordrag (Skype), verkryging van afstandtoegang tot 'n rekenaar (TeamViewer), of aanlyn speletjies.

Een van die doeltreffendste metodes om 'n eweknie-verbinding tussen toestelle op verskillende private netwerke te vestig, word gatpons genoem. Hierdie tegniek word die meeste gebruik met toepassings gebaseer op die UDP-protokol.

Maar as u toepassing gewaarborgde aflewering van data benodig, byvoorbeeld, dra u lêers tussen rekenaars oor, dan sal die gebruik van UDP baie probleme hê as gevolg van die feit dat UDP nie 'n gewaarborgde afleweringsprotokol is nie en nie pakketaflewering in volgorde verskaf nie, anders as die TCP protokol.

In hierdie geval, om gewaarborgde pakkie aflewering te verseker, is dit nodig om 'n toepassingslaagprotokol te implementeer wat die nodige funksionaliteit verskaf en oor UDP werk.

Ek wil dadelik daarop let dat daar 'n TCP-gatponstegniek is om TCP-verbindings tussen nodusse in verskillende private netwerke te vestig, maar weens die gebrek aan ondersteuning daarvoor deur baie NAT-toestelle, word dit gewoonlik nie beskou as die hoof manier om aan te sluit nie. sulke nodusse.

Vir die res van hierdie artikel sal ek slegs fokus op die implementering van die gewaarborgde afleweringsprotokol. Die implementering van die UDP-gatponstegniek sal in die volgende artikels beskryf word.

Protokolvereistes

  1. Betroubare pakkie aflewering geïmplementeer deur 'n positiewe terugvoermeganisme (die sogenaamde positiewe erkenning)
  2. Die behoefte aan doeltreffende oordrag van groot data, m.a.w. die protokol moet onnodige pakkie-oordrag vermy
  3. Dit behoort moontlik te wees om die afleweringsbevestigingsmeganisme te kanselleer (die vermoë om as 'n "suiwer" UDP-protokol te funksioneer)
  4. Vermoë om opdragmodus te implementeer, met bevestiging van elke boodskap
  5. Die basiese eenheid van data-oordrag oor die protokol moet 'n boodskap wees

Hierdie vereistes stem grootliks ooreen met die vereistes vir betroubare dataprotokol wat in beskryf word rfc908 и rfc1151, en ek het op daardie standaarde staatgemaak toe ek hierdie protokol ontwikkel het.

Om hierdie vereistes te verstaan, kom ons kyk na die tydsberekening van data-oordrag tussen twee netwerknodusse deur die TCP- en UDP-protokolle te gebruik. Laat ons in beide gevalle een pakkie verloor.
Oordrag van nie-interaktiewe data oor TCP:Implementering van die Reliable Udp-protokol vir .Net

Soos u uit die diagram kan sien, sal TCP in die geval van pakkieverlies die verlore pakkie opspoor en dit aan die sender rapporteer deur die nommer van die verlore segment te vra.
Data-oordrag via UDP-protokol:Implementering van die Reliable Udp-protokol vir .Net

UDP neem geen verliesopsporingstappe nie. Beheer van transmissiefoute in die UDP-protokol is geheel en al die verantwoordelikheid van die toepassing.

Foutdetectie in die TCP-protokol word verkry deur 'n verbinding met 'n eindnodus te vestig, die toestand van daardie verbinding te stoor, die aantal grepe aan te dui wat in elke pakkieopskrif gestuur is, en kwitansies in kennis te stel deur 'n erkenningnommer te gebruik.

Daarbenewens, om werkverrigting te verbeter (d.w.s. stuur meer as een segment sonder om 'n erkenning te ontvang), gebruik die TCP-protokol die sogenaamde transmissievenster - die aantal grepe data wat die sender van die segment verwag om te ontvang.

Vir meer inligting oor die TCP-protokol, sien rfc793, van UDP na rfc768waar hulle in werklikheid gedefinieer word.

Uit bogenoemde is dit duidelik dat om 'n betroubare boodskapafleweringsprotokol oor UDP te skep (hierna verwys as Betroubare UDP), is dit nodig om data-oordragmeganismes soortgelyk aan TCP te implementeer. Naamlik:

  • stoor verbindingstoestand
  • gebruik segmentnommering
  • gebruik spesiale bevestigingspakkette
  • gebruik 'n vereenvoudigde venstermeganisme om protokoldeurvloei te verhoog

Daarbenewens benodig jy:

  • dui die begin van 'n boodskap aan, om hulpbronne vir die verbinding toe te wys
  • die einde van 'n boodskap aandui, om die ontvangde boodskap na die stroomoptoepassing deur te gee en protokolhulpbronne vry te stel
  • laat die verbindingspesifieke protokol toe om die afleweringsbevestigingsmeganisme te deaktiveer om as "suiwer" UDP te funksioneer

Betroubare UDP-kopskrif

Onthou dat 'n UDP-datagram in 'n IP-datagram ingekapsuleer is. Die betroubare UDP-pakkie is toepaslik "toegedraai" in 'n UDP-datagram.
Betroubare UDP-kopinkapseling:Implementering van die Reliable Udp-protokol vir .Net

Die struktuur van die betroubare UDP-kopskrif is redelik eenvoudig:

Implementering van die Reliable Udp-protokol vir .Net

  • Vlae - pakketbeheervlae
  • Boodskaptipe - boodskaptipe wat deur stroomoptoepassings gebruik word om op spesifieke boodskappe in te teken
  • TransmissionId - die nommer van die oordrag, tesame met die adres en poort van die ontvanger, identifiseer die verbinding uniek
  • Pakkienommer - pakkienommer
  • Opsies - addisionele protokol opsies. In die geval van die eerste pakkie word dit gebruik om die grootte van die boodskap aan te dui

Vlae is soos volg:

  • FirstPacket - die eerste pakkie van die boodskap
  • NoAsk - die boodskap vereis nie dat 'n erkenningsmeganisme geaktiveer word nie
  • LastPacket - die laaste pakkie van die boodskap
  • RequestForPacket - bevestigingspakkie of versoek vir 'n verlore pakkie

Algemene beginsels van die protokol

Aangesien Betroubare UDP gefokus is op gewaarborgde boodskapoordrag tussen twee nodusse, moet dit 'n verbinding met die ander kant kan vestig. Om 'n verbinding te bewerkstellig, stuur die sender 'n pakkie met die FirstPacket-vlag, die reaksie daarop sal beteken dat die verbinding tot stand gebring is. Alle antwoordpakkies, of, met ander woorde, erkenningspakkies, stel altyd die waarde van die Pakketnommer-veld op een meer as die grootste Pakketnommer-waarde van pakkies wat suksesvol ontvang is. Die Opsies-veld vir die eerste pakkie wat gestuur is, is die grootte van die boodskap.

'n Soortgelyke meganisme word gebruik om 'n verbinding te beëindig. Die LastPacket-vlag is op die laaste pakkie van die boodskap gestel. In die antwoordpakkie word die nommer van die laaste pakkie + 1 aangedui, wat vir die ontvangskant suksesvolle aflewering van die boodskap beteken.
Verbinding vestiging en beëindiging diagram:Implementering van die Reliable Udp-protokol vir .Net

Wanneer die verbinding tot stand gebring is, begin data-oordrag. Data word in blokke pakkies oorgedra. Elke blok, behalwe die laaste een, bevat 'n vaste aantal pakkies. Dit is gelyk aan die ontvang-/stuurvenstergrootte. Die laaste blok data kan minder pakkies hê. Nadat elke blok gestuur is, wag die stuurkant vir 'n afleweringsbevestiging of 'n versoek om verlore pakkies weer af te lewer, wat die ontvang-/versendingvenster oop laat om antwoorde te ontvang. Na ontvangs van bevestiging van blokaflewering, skuif die ontvang-/versendingvenster en die volgende blok data word gestuur.

Die ontvangerkant ontvang die pakkies. Elke pakkie word nagegaan om te sien of dit binne die transmissievenster val. Pakkies en duplikate wat nie in die venster val nie, word uitgefiltreer. Omdat As die grootte van die venster vas is en dieselfde is vir die ontvanger en die sender, dan in die geval van 'n blok pakkies wat sonder verlies afgelewer word, word die venster geskuif om pakkies van die volgende blok data te ontvang en 'n afleweringsbevestiging is gestuur. Indien die venster nie vol raak binne die tydperk wat deur die werktydteller gestel is nie, sal 'n kontrole begin word op watter pakkies nie afgelewer is nie en versoeke vir heraflewering sal gestuur word.
Heruitsendingdiagram:Implementering van die Reliable Udp-protokol vir .Net

Time-outs en protokol timers

Daar is verskeie redes waarom 'n verbinding nie bewerkstellig kan word nie. Byvoorbeeld, as die ontvangende party vanlyn is. In hierdie geval, wanneer u probeer om 'n verbinding tot stand te bring, sal die verbinding gesluit word deur uitteltyd. Die betroubare UDP-implementering gebruik twee tydtellers om uitteltyd in te stel. Die eerste, die werkende timer, word gebruik om te wag vir 'n reaksie van die afgeleë gasheer. As dit aan die senderkant brand, word die laaste gestuurde pakkie weer gestuur. As die tydhouer by die ontvanger verval, word 'n tjek vir verlore pakkies uitgevoer en versoeke vir heraflewering word gestuur.

Die tweede timer is nodig om die verbinding te sluit in die geval van 'n gebrek aan kommunikasie tussen die nodusse. Vir die senderkant begin dit onmiddellik nadat die werktydteller verstryk het, en wag vir 'n reaksie van die afgeleë nodus. As daar geen reaksie binne die gespesifiseerde tydperk is nie, word die verbinding beëindig en hulpbronne word vrygestel. Vir die ontvangerkant word die verbindingtoeskakelaar begin nadat die werktydteller twee keer verval het. Dit is nodig om te verseker teen die verlies van die bevestigingspakkie. Wanneer die tydhouer verstryk, word die verbinding ook beëindig en hulpbronne word vrygestel.

Betroubare UDP-transmissietoestanddiagram

Die beginsels van die protokol word in 'n eindige toestand masjien geïmplementeer, waarvan elke toestand verantwoordelik is vir 'n sekere logika van pakketverwerking.
Betroubare UDP-staatdiagram:

Implementering van die Reliable Udp-protokol vir .Net

Gesluit - is nie regtig 'n toestand nie, dit is 'n begin- en eindpunt vir die outomaat. Vir staat Gesluit 'n transmissiebeheerblok word ontvang, wat, met die implementering van 'n asinchrone UDP-bediener, pakkies aanstuur na die toepaslike verbindings en toestandverwerking begin.

FirstPacketSending – die aanvanklike toestand waarin die uitgaande verbinding is wanneer die boodskap gestuur word.

In hierdie toestand word die eerste pakkie vir normale boodskappe gestuur. Vir boodskappe sonder stuurbevestiging is dit die enigste toestand waar die hele boodskap gestuur word.

Stuursiklus – grondtoestand vir die oordrag van boodskappakkies.

Oorgang na dit van die staat FirstPacketSending uitgevoer nadat die eerste pakkie van die boodskap gestuur is. Dit is in hierdie toestand dat alle erkennings en versoeke vir heruitsendings kom. Dit is in twee gevalle moontlik om daaruit te gaan - in die geval van suksesvolle aflewering van die boodskap of deur uitteltyd.

Eerste Pakkie ontvang – die aanvanklike toestand vir die ontvanger van die boodskap.

Dit kontroleer die korrektheid van die begin van die oordrag, skep die nodige strukture en stuur 'n erkenning van ontvangs van die eerste pakkie.

Vir 'n boodskap wat uit 'n enkele pakkie bestaan ​​en gestuur is sonder om bewys van aflewering te gebruik, is dit die enigste toestand. Nadat so 'n boodskap verwerk is, word die verbinding gesluit.

samestelling – basiese toestand vir die ontvangs van boodskappakkies.

Dit skryf pakkies na tydelike berging, kontroleer vir pakkieverlies, stuur erkennings vir die aflewering van 'n blok pakkies en die hele boodskap, en stuur versoeke vir heraflewering van verlore pakkies. In die geval van suksesvolle ontvangs van die hele boodskap, gaan die verbinding na die staat Voltooide, anders gaan 'n timeout uit.

Voltooide – die sluiting van die verbinding in geval van suksesvolle ontvangs van die hele boodskap.

Hierdie toestand is nodig vir die samestelling van die boodskap en vir die geval wanneer die afleweringsbevestiging van die boodskap verlore gegaan het op pad na die sender. Hierdie toestand word met 'n uitteltyd verlaat, maar die verbinding word as suksesvol gesluit beskou.

Dieper in die kode. transmissie beheer eenheid

Een van die sleutelelemente van Reliable UDP is die transmissiebeheerblok. Die taak van hierdie blok is om huidige verbindings en hulpelemente te stoor, inkomende pakkies na die ooreenstemmende verbindings te versprei, 'n koppelvlak te verskaf om pakkies na 'n verbinding te stuur, en die protokol API te implementeer. Die transmissiebeheerblok ontvang pakkies vanaf die UDP-laag en stuur dit aan na die staatmasjien vir verwerking. Om pakkies te ontvang, implementeer dit 'n asynchrone UDP-bediener.
Sommige lede van die ReliableUdpConnectionControlBlock-klas:

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

Implementering van asynchrone UDP-bediener:

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

Vir elke boodskapoordrag word 'n struktuur geskep wat inligting oor die verbinding bevat. So 'n struktuur word genoem verbinding rekord.
Sommige lede van die ReliableUdpConnectionRecord-klas:

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

Dieper in die kode. state

State implementeer die staatsmasjien van die Reliable UDP-protokol, waar die hoofverwerking van pakkies plaasvind. Die abstrakte klas ReliableUdpState bied 'n koppelvlak vir die staat:

Implementering van die Reliable Udp-protokol vir .Net

Die hele logika van die protokol word geïmplementeer deur die klasse wat hierbo aangebied word, tesame met 'n hulpklas wat statiese metodes verskaf, soos byvoorbeeld die bou van die ReliableUdp-kopskrif vanaf die verbindingsrekord.

Vervolgens sal ons die implementering van die koppelvlakmetodes wat die basiese algoritmes van die protokol bepaal, in detail oorweeg.

DisposeByTimeout metode

Die DisposeByTimeout-metode is verantwoordelik vir die vrystelling van verbindingshulpbronne na 'n uitteltyd en om suksesvolle/onsuksesvolle boodskaplewering aan te dui.
ReliableUdpState.DisposeByTimeout:

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

Dit word net in die staat oorheers Voltooide.
Completed.DisposeByTimeout:

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

ProcessPackets Metode

Die ProcessPackets-metode is verantwoordelik vir bykomende verwerking van 'n pakket of pakkette. Gebel direk of via 'n pakkie wag timer.

Bekwaam samestelling die metode word oorheers en is verantwoordelik vir die kontrolering van verlore pakkies en die oorgang na die staat Voltooide, in die geval van ontvangs van die laaste pakkie en slaag 'n suksesvolle tjek
Assembling.ProsesPakkette:

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

Bekwaam Stuursiklus hierdie metode word slegs op 'n tydhouer genoem, en is verantwoordelik vir die herstuur van die laaste boodskap, sowel as die aktivering van die verbinding-toeskakelaar.
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);
}

Bekwaam Voltooide die metode stop die lopende timer en stuur die boodskap aan die intekenare.
Voltooi. Prosespakkies:

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

ReceivePacket metode

Bekwaam Eerste Pakkie ontvang die hooftaak van die metode is om te bepaal of die eerste boodskappakkie werklik by die koppelvlak aangekom het, en ook om 'n boodskap te versamel wat uit 'n enkele pakkie bestaan.
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);
  }
}

Bekwaam Stuursiklus hierdie metode word oorheers om afleweringserkennings en heruitsendingversoeke te aanvaar.
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));
}

Bekwaam samestelling in die ReceivePacket-metode vind die hoofwerk van die samestelling van 'n boodskap uit inkomende pakkies plaas.
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);
  }
}

Bekwaam Voltooide die enigste taak van die metode is om 'n her-erkenning van die suksesvolle aflewering van die boodskap te stuur.
Voltooi. Ontvang Pakket:

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

Stuur pakkie metode

Bekwaam FirstPacketSending hierdie metode stuur die eerste pakkie data, of, as die boodskap nie afleweringsbevestiging vereis nie, die hele boodskap.
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);
}

Bekwaam Stuursiklus in hierdie metode word 'n blok pakkies gestuur.
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 );
  }
}

Dieper in die kode. Skep en vestiging van verbindings

Noudat ons die basiese toestande en die metodes wat gebruik word om state te hanteer gesien het, laat ons 'n paar voorbeelde van hoe die protokol werk in 'n bietjie meer detail uiteensit.
Data-oordragdiagram onder normale toestande:Implementering van die Reliable Udp-protokol vir .Net

Oorweeg die skepping in detail verbinding rekord om te koppel en die eerste pakkie te stuur. Die oordrag word altyd geïnisieer deur die toepassing wat die stuurboodskap-API oproep. Vervolgens word die StartTransmission-metode van die transmissiebeheerblok opgeroep, wat die oordrag van data vir die nuwe boodskap begin.
Skep 'n uitgaande verbinding:

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

Stuur die eerste pakkie (FirstPacketSending-toestand):

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

Nadat die eerste pakkie gestuur is, gaan die sender die staat binne Stuursiklus – wag vir bevestiging van pakketaflewering.
Die ontvangkant, met behulp van die EndReceive-metode, ontvang die gestuurde pakkie, skep 'n nuwe verbinding rekord en gee hierdie pakkie, met 'n vooraf geparseerde kopskrif, na die ReceivePacket-metode van die staat vir verwerking Eerste Pakkie ontvang
Skep 'n verbinding aan die ontvangkant:

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

Ontvang die eerste pakkie en stuur 'n erkenning (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);
  }
}

Dieper in die kode. Sluit die verbinding met uitteltyd

Time-out hantering is 'n belangrike deel van Reliable UDP. Beskou 'n voorbeeld waarin 'n intermediêre nodus misluk het en datalewering in beide rigtings onmoontlik geword het.
Diagram vir die sluiting van 'n verbinding deur uitteltyd:Implementering van die Reliable Udp-protokol vir .Net

Soos uit die diagram gesien kan word, begin die sender se werktydhouer onmiddellik nadat 'n blok pakkies gestuur is. Dit gebeur in die SendPacket-metode van die staat Stuursiklus.
Aktiveer die werktydteller (SendingCycle-toestand):

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

Die timerperiodes word gestel wanneer die verbinding geskep word. Die verstek ShortTimerPeriod is 5 sekondes. In die voorbeeld is dit gestel op 1,5 sekondes.

Vir 'n inkomende verbinding begin die timer na ontvangs van die laaste inkomende datapakkie, dit gebeur in die ReceivePacket-metode van die staat samestelling
Aktiveer die werktydteller (samestellingstatus):

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

Geen pakkies het meer op die inkomende verbinding aangekom terwyl daar vir die werkende tydteller gewag is nie. Die tydhouer het afgegaan en die ProcessPackets-metode genoem, waar die verlore pakkies gevind is en herafleweringsversoeke vir die eerste keer gestuur is.
Stuur herafleweringsversoeke (samestellingstaat):

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

Die TimerSecondTry-veranderlike is ingestel op waar. Hierdie veranderlike is verantwoordelik vir die herbegin van die werktydteller.

Aan die sender se kant word die werkende timer ook geaktiveer en die laaste gestuurde pakkie word weer gestuur.
Aktiveer verbinding-toetydhouer (SendingCycle-toestand):

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

Daarna begin die verbinding-toeskakelaar in die uitgaande verbinding.
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);
}

Die tydsverlooptydperk vir verbindingssluiting is by verstek 30 sekondes.

Na 'n kort tydjie brand die werkende timer aan die ontvanger se kant weer, versoeke word weer gestuur, waarna die verbinding sluit timer begin vir die inkomende verbinding

Wanneer die aftellers brand, word alle hulpbronne van beide verbindingrekords vrygestel. Die sender rapporteer die afleweringsmislukking aan die stroomop-toepassing (sien Betroubare UDP API).
Maak verbindingsrekordhulpbronne vry:

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

Dieper in die kode. Herstel tans data-oordrag

Data-oordragherwinningsdiagram in geval van pakkieverlies:Implementering van die Reliable Udp-protokol vir .Net

Soos reeds bespreek in die sluiting van die verbinding met time-out, sal die ontvanger kyk vir verlore pakkies wanneer die werktydhouer verstryk. In die geval van pakkieverlies, sal 'n lys van die aantal pakkies wat nie die ontvanger bereik het nie, saamgestel word. Hierdie nommers word in die LostPackets-skikking van 'n spesifieke verbinding ingevoer, en versoeke vir heraflewering word gestuur.
Stuur versoeke om pakkette weer af te lewer (samestellingstaat):

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

Die sender sal die herafleweringsversoek aanvaar en die ontbrekende pakkies stuur. Dit is die moeite werd om daarop te let dat die sender op hierdie oomblik reeds die verbindingstoeskakelaar begin het en wanneer 'n versoek ontvang word, word dit teruggestel.
Herstuur verlore pakkies (SendingCycle-toestand):

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

Die pakkie wat weer gestuur is (pakkie #3 in die diagram) word deur die inkomende verbinding ontvang. 'n Kontrole word gedoen om te sien of die ontvangvenster vol is en normale data-oordrag is herstel.
Kyk vir treffers in die ontvangvenster (samestellingstaat):

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

Betroubare UDP API

Om met die data-oordragprotokol te kommunikeer, is daar 'n oop Betroubare Udp-klas, wat 'n omhulsel oor die oordragbeheerblok is. Hier is die belangrikste lede van die klas:

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

Boodskappe word per intekening ontvang. Delegeer handtekening vir die terugbelmetode:

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

Om in te teken op 'n spesifieke boodskaptipe en/of 'n spesifieke sender, word twee opsionele parameters gebruik: ReliableUdpMessageTypes messageType en IPEndPoint ipEndPoint.

Boodskaptipes:

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

Die boodskap word asinchroon gestuur; hiervoor implementeer die protokol 'n asynchrone programmeringsmodel:

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

Die resultaat van die stuur van 'n boodskap sal waar wees - as die boodskap die ontvanger suksesvol bereik het en vals - as die verbinding deur uitteltyd gesluit is:

public bool EndSendMessage(IAsyncResult asyncResult)

Gevolgtrekking

Baie is nie in hierdie artikel beskryf nie. Draadpassingsmeganismes, uitsonderings- en fouthantering, implementering van asinchroniese boodskapstuurmetodes. Maar die kern van die protokol, die beskrywing van die logika vir die verwerking van pakkies, die vestiging van 'n verbinding en die hantering van time-outs, behoort vir jou duidelik te wees.

Die gedemonstreerde weergawe van die betroubare afleweringsprotokol is robuust en buigsaam genoeg om aan die voorheen gedefinieerde vereistes te voldoen. Maar ek wil byvoeg dat die beskryfde implementering verbeter kan word. Byvoorbeeld, om deurset te verhoog en timerperiodes dinamies te verander, kan meganismes soos skuifvenster en RTT by die protokol gevoeg word, dit sal ook nuttig wees om 'n meganisme te implementeer vir die bepaling van MTU tussen verbindingsnodusse (maar slegs as groot boodskappe gestuur word) .

Dankie vir jou aandag, ek sien uit na jou kommentaar en kommentaar.

NS Vir diegene wat belangstel in die besonderhede of net die protokol wil toets, die skakel na die projek op GitHube:
Betroubare UDP-projek

Nuttige skakels en artikels

  1. TCP protokol spesifikasie: in Engels и in Russies
  2. UDP protokol spesifikasie: in Engels и in Russies
  3. Bespreking van die RUDP-protokol: konsep-ietf-sigtran-betroubare-udp-00
  4. Betroubare dataprotokol: rfc908 и rfc1151
  5. 'n Eenvoudige implementering van afleweringsbevestiging oor UDP: Neem totale beheer van jou netwerk met .NET en UDP
  6. Artikel wat NAT-deurkruismeganismes beskryf: Eweknie-kommunikasie oor netwerkadresvertalers
  7. Implementering van die asynchrone programmeringsmodel: Implementering van die CLR-asinchrone programmeringsmodel и Hoe om die IAsyncResult-ontwerppatroon te implementeer
  8. Oordrag van die asynchrone programmeringsmodel na die taakgebaseerde asinchrone patroon (APM in TAP):
    TPL en Tradisionele .NET Asynchrone Programmering
    Interopereer met ander asinchrone patrone en tipes

Update: Dankie mayorovp и sidristij vir die idee om 'n taak by die koppelvlak te voeg. Die verenigbaarheid van die biblioteek met ou bedryfstelsels word nie geskend nie, want Die 4de raamwerk ondersteun beide XP- en 2003-bediener.

Bron: will.com

Voeg 'n opmerking