Utfiering fan it Reliable Udp-protokol foar .Net

It ynternet is lang feroare. Ien fan 'e wichtichste ynternetprotokollen, UDP wurdt brûkt troch applikaasjes net allinich om datagrammen en útstjoeringen te leverjen, mar ek om peer-to-peer-ferbiningen te leverjen tusken netwurkknooppunten. Troch it ienfâldige ûntwerp hat dit protokol in protte earder net plande gebrûk; de tekortkomingen fan it protokol, lykas it gebrek oan garandearre levering, binne lykwols net ferdwûn. Dit artikel beskriuwt in ymplemintaasje fan it Guaranteed Delivery Protocol oer UDP.
Ynhâld:Ynlieding
Protokol easken
Betrouwbare UDP-header
Algemiene prinsipes fan it protokol
Protokol timeouts en timers
Betrouber UDP Transmission State Diagram
Djipper yn 'e koade. Transmission kontrôle ienheid
Djipper yn 'e koade. Steaten

Djipper yn 'e koade. It oanmeitsjen en oprjochtsjen fan ferbinings
Djipper yn 'e koade. In ferbining slute fanwege time-out
Djipper yn 'e koade. Data oerdracht herstel
Betroubere UDP API
konklúzje
Nuttige keppelings en artikels

Ynlieding

De orizjinele arsjitektuer fan it ynternet foarsjoen in unifoarme adresromte wêryn elke knooppunt in wrâldwiid en unyk IP-adres hie en direkt mei oare knooppunten koe kommunisearje. No hat it ynternet, yn feite, in oare arsjitektuer - ien gebiet fan wrâldwide IP-adressen en in protte gebieten mei priveeadressen ferburgen efter NAT-apparaten.Yn dizze arsjitektuer kinne allinich apparaten yn 'e globale adresromte maklik kommunisearje mei elkenien op it netwurk, om't se in unyk, globaal routabel IP-adres hawwe. In knooppunt op in privee netwurk kin ferbine mei oare knopen op itselde netwurk, en ek ferbine mei oare, bekende knopen yn 'e globale adresromte. Dizze ynteraksje wurdt berikt foar in grut part troch it netwurk adres oersetting meganisme. NAT-apparaten, lykas Wi-Fi-routers, meitsje spesjale yngongen foar oersettingtabel foar útgeande ferbiningen en wizigje IP-adressen en poartenûmers yn pakketten. Hjirmei kinne útgeande ferbiningen makke wurde fan in privee netwurk nei hosts yn 'e globale adresromte. Mar tagelyk blokkearje NAT-apparaten normaal alle ynkommende ferkear, útsein as aparte regels ynsteld binne foar ynkommende ferbiningen.

Dizze ynternetarsjitektuer is genôch korrekt foar ynteraksje tusken kliïnten en tsjinners, as kliïnten kinne lizze op partikuliere netwurken, en tsjinners hawwe in globaal adres. Mar it makket swierrichheden foar direkt ferbinen twa knopen tusken ferskate privee netwurken. Direkte ferbining fan twa knooppunten is wichtich foar peer-to-peer-applikaasjes lykas stimferstjoering (Skype), kompjûter tagong op ôfstân (TeamViewer), of online spultsjes.

Ien fan 'e meast effektive metoaden foar it oprjochtsjen fan peer-to-peer-ferbiningen tusken apparaten dy't op ferskate partikuliere netwurken lizze, wurdt "gatpunching" neamd. Dizze technyk wurdt meast brûkt mei applikaasjes basearre op it UDP-protokol.

Mar as jo applikaasje garandearre levering fan gegevens fereasket, bygelyks jo bestannen oerdrage tusken kompjûters, dan sil it brûken fan UDP in protte swierrichheden presintearje fanwege it feit dat UDP gjin garandearre leveringsprotokol is en net soarget foar de levering fan pakketten yn oarder, yn tsjinstelling ta it TCP-protokol.

Yn dit gefal, om garandearre levering fan pakketten te garandearjen, is it nedich om in protokol op applikaasjenivo te ymplementearjen dat de nedige funksjonaliteit leveret en boppe-op UDP wurket.

Ik wol fuortdaliks opmerke dat d'r in TCP-gatpunchtechnyk is foar it oprjochtsjen fan TCP-ferbiningen tusken knooppunten op ferskate privee netwurken, mar troch it gebrek oan stipe foar it troch in protte NAT-apparaten, wurdt it normaal net beskôge as de wichtichste metoade fan ferbinen sokke knopen.

Letter yn dit artikel sil ik allinich de ymplemintaasje fan it garandearre leveringsprotokol beskôgje. De ymplemintaasje fan 'e UDP-gatpunchtechnyk sil wurde beskreaun yn' e folgjende artikels.

Protokol easken

  1. Betrouwbare pakketlevering ymplementearre fia in positive feedbackmeganisme (saneamde positive erkenning)
  2. De needsaak foar effisjinte oerdracht fan grutte gegevens, d.w.s. it protokol moat ûnnedige pakketferstjoerings foarkomme
  3. It moat mooglik wêze om it leveringsbefêstigingsmeganisme te oerskriuwen (de mooglikheid om te funksjonearjen as in "suver" UDP-protokol)
  4. Mooglikheid om kommando-modus út te fieren, mei befêstiging fan elk berjocht
  5. De basisienheid fan gegevensoerdracht ûnder it protokol moat in berjocht wêze

Dizze easken falle foar in grut part oerien mei de easken foar it Reliable Data Protocol beskreaun yn rfc 908 и rfc 1151, en ik fertroude op dizze noarmen foar it ûntwikkeljen fan dit protokol.

Om dizze easken te begripen, litte wy sjen nei de timingdiagrammen fan gegevensoerdracht tusken twa netwurkknooppunten mei de TCP- en UDP-protokollen. Lit ús yn beide gefallen ien pakket ferlieze.
It ferstjoeren fan net-ynteraktive gegevens oer TCP:Utfiering fan it Reliable Udp-protokol foar .Net

Lykas jo kinne sjen út it diagram, yn gefal fan pakketferlies, sil TCP it ferlerne pakket ûntdekke en it rapportearje oan de stjoerder troch it oanfreegjen fan it ferlerne segmintnûmer.
Gegevensferfier fia UDP-protokol:Utfiering fan it Reliable Udp-protokol foar .Net

UDP nimt gjin stappen om ferliezen te detektearjen. Kontrôle fan oerdrachtflaters yn it UDP-protokol is folslein de ferantwurdlikens fan 'e applikaasje.

Flaterdeteksje yn it TCP-protokol wurdt berikt troch it oprjochtsjen fan in ferbining mei in einknooppunt, it bewarjen fan de steat fan dizze ferbining, it oanjaan fan it oantal ferstjoerde bytes yn elke pakketkop, en it ynformearjen fan ûntfangsten mei in befêstigingsnûmer.

Derneist, om prestaasjes te ferbetterjen (dus mear as ien segmint ferstjoere sûnder in erkenning te ûntfangen), brûkt it TCP-protokol in saneamde transmissiefinster - it oantal bytes fan gegevens dat de stjoerder fan it segmint ferwachtet te ûntfangen.

Mear details oer it TCP-protokol kinne fûn wurde yn rfc 793, mei UDP yn rfc 768, dêr't se binne, yn feite, definiearre.

Ut it boppesteande is it dúdlik dat om in betrouber protokol foar berjochtlevering te meitsjen oer UDP (hjirnei sille wy neame Betrouwbare UDP), is it ferplichte om meganismen foar oerdracht fan gegevens te ymplementearjen fergelykber mei TCP. Nammentlik:

  • bewarje ferbining steat
  • brûke segment nûmering
  • brûk spesjale befêstigingspakketten
  • brûke in ferienfâldige finster meganisme te fergrutsjen protokol trochstreaming

Derneist, fereaske:

  • sinjalearje it begjin fan in berjocht om boarnen foar de ferbining te allocearjen
  • sinjalearje it ein fan in berjocht, om it ûntfongen berjocht troch te jaan oan in streamôfwerts applikaasje en protokolboarnen frij te meitsjen
  • tastean in ferbining spesifyk protokol te skeakeljen it leveringserkenningsmeganisme om te funksjonearjen as "suvere" UDP

Betrouwbare UDP-header

Tink derom dat in UDP-datagram is ynkapsele yn in IP-datagram. It betroubere UDP-pakket is dêrtroch "ferpakt" yn in UDP-datagram.
Betrouwbare UDP-header-ynkapseling:Utfiering fan it Reliable Udp-protokol foar .Net

De betroubere UDP-headerstruktuer is frij ienfâldich:

Utfiering fan it Reliable Udp-protokol foar .Net

  • Flaggen - flaggen foar pakketkontrôle
  • MessageType - berjochttype, brûkt troch streamop-applikaasjes om te abonnearjen op spesifike berjochten
  • TransmissionId - transmissienûmer, tegearre mei it adres en de poarte fan 'e ûntfanger, identifisearret de ferbining unyk
  • PacketNumber - pakketnûmer
  • Opsjes - ekstra protokolopsjes. Yn it gefal fan it earste pakket wurdt it brûkt om de berjochtgrutte oan te jaan

De flaggen binne as folget:

  • FirstPacket - it earste pakket fan it berjocht
  • NoAsk - it berjocht fereasket net dat it befêstigingsmeganisme ynskeakele is
  • LastPacket - lêste berjochtpakket
  • RequestForPacket - befêstigingspakket of fersyk foar in ferlern pakket

Algemiene prinsipes fan it protokol

Sûnt Reliable UDP is rjochte op it garandearjen fan de oerdracht fan in berjocht tusken twa knooppunten, moat it in ferbining kinne meitsje mei de oare kant. Om in ferbining te meitsjen, stjoert de stjoerende kant in pakket mei de FirstPacket-flagge, it antwurd dêrop sil betsjutte dat de ferbining is oprjochte. Alle antwurdpakketten, of, mei oare wurden, befêstigingspakketten, set de wearde fan it PacketNumber-fjild altyd yn op ien grutter dan de grutste PacketNumber-wearde fan suksesfol oankommen pakketten. It fjild Opsjes foar it earste ferstjoerde pakket registrearret de berjochtgrutte.

In ferlykbere meganisme wurdt brûkt om de ferbining te foltôgjen. De flagge fan LastPacket is ynsteld yn it lêste berjochtpakket. It antwurdpakket jout it nûmer fan it lêste pakket + 1 oan, wat foar de ûntfangende kant in suksesfolle levering fan it berjocht betsjut.
Diagram fan ferbining oprjochting en beëiniging:Utfiering fan it Reliable Udp-protokol foar .Net

As de ferbining is oprjochte, begjint gegevensferfier. Gegevens wurde oerdroegen yn blokken fan pakketten. Elk blok, útsein de lêste, befettet in fêst oantal pakketten. It is gelyk oan de grutte fan it ûntfangst-/ferstjoerfinster. It lêste gegevensblok kin minder pakketten hawwe. Nei it ferstjoeren fan elk blok wachtet de ferstjoerende kant op befêstiging fan levering, of in fersyk foar werlevering fan ferlerne pakketten, wêrtroch it ûntfangst-/ferstjoerfinster iepen is om antwurden te ûntfangen. Nei ûntfangst fan befêstiging fan blok levering, de ûntfangst / oerdracht finster ferskowings en it folgjende blok fan gegevens wurdt ferstjoerd.

De ûntfangende kant ûntfangt de pakketten. Elk pakket wurdt kontrolearre om te sjen oft it binnen it oerdrachtfinster falt. Pakketten en duplikaten dy't net yn it finster falle wurde elimineare. Omdat Sûnt de grutte fan it finster is fêst en itselde foar de ûntfanger en de stjoerder, dan yn it gefal fan levering fan in blok fan pakketten sûnder ferlies, it finster wurdt ferskood om te ûntfangen pakketten fan it folgjende gegevens blok en in levering befêstiging wurdt ferstjoerd . As it finster net foltôge binnen de perioade ynsteld troch de wurktimer, sil in kontrôle lansearre wurde om te sjen hokker pakketten net levere binne en fersiken foar werlevering wurde ferstjoerd.
Retransmission diagram:Utfiering fan it Reliable Udp-protokol foar .Net

Protokol timeouts en timers

D'r binne ferskate redenen wêrom't in ferbining net kin wurde oprjochte. Bygelyks, as de ûntfangende partij offline is. Yn dit gefal, as jo besykje in ferbining te meitsjen, sil de ferbining sletten wurde fanwege in time-out. De betroubere UDP-ymplemintaasje brûkt twa timers om timeouts yn te stellen. De earste, de wurkjende timer, wurdt brûkt om te wachtsjen op in antwurd fan 'e host op ôfstân. As it oan 'e stjoerende kant wurdt trigger, dan wurdt it lêste pakket ferstjoerd opnij ferstjoerd. As de timer wurdt aktivearre by de ûntfanger, dan wurdt in kontrôle útfierd foar ferlerne pakketten en fersiken foar opnij levering wurde ferstjoerd.

De twadde timer is nedich om de ferbining te sluten as der gjin kommunikaasje is tusken de knopen. Foar de stjoerende kant begjint it fuortendaliks nei't de wurktimer ferrint, en wachtet op in antwurd fan 'e knooppunt op ôfstân. As d'r gjin antwurd binnen de opjûne perioade is, wurdt de ferbining beëinige en wurde boarnen frijjûn. Foar de ûntfanger kant begjint de ferbining slute timer neidat de wurk timer fjoer twa kear. Dit is nedich om te fersekerjen tsjin ferlies fan it befestigingspakket. As de timer ûntspringt, wurdt de ferbining ek beëinige en wurde boarnen frijjûn.

Betrouber UDP Transmission State Diagram

De bestjoeringsprinsipes fan it protokol wurde ymplementearre yn in finite state masine, wêrfan elke steat ferantwurdlik is foar in spesifike pakketferwurkingslogika.
Betrouber UDP-statediagram:

Utfiering fan it Reliable Udp-protokol foar .Net

Sletten - is net eins in steat, it is it begjin- en einpunt foar de masine. Foar de steat Sletten in oerdracht kontrôle blok wurdt ûntfongen, dy't, it útfieren fan in asynchronous UDP tsjinner, omlaat pakketten nei de passende ferbinings en begjint steat ferwurkjen.

FirstPacketSending - de begjinstân wêryn de útgeande ferbining is by it ferstjoeren fan in berjocht.

Yn dizze steat wurdt it earste pakket foar reguliere berjochten ferstjoerd. Foar berjochten sûnder befêstiging fan ferstjoeren, dit is de ienige steat - it hiele berjocht wurdt ferstjoerd yn it.

SendingCycle - basis tastân foar it ferstjoeren fan berjochtpakketten.

Oergong nei it fan 'e steat FirstPacketSending útfierd nei it ferstjoeren fan it earste berjochtpakket. It is yn dizze steat dat alle erkenningen en oanfragen foar retransmissions komme. It útgean is yn twa gefallen mooglik - yn gefal fan suksesfolle levering fan it berjocht of fanwege in time-out.

FirstPacketReceived - inisjele steat foar de berjochtûntfanger.

It kontrolearret de krektens fan 'e start fan' e oerdracht, makket de nedige struktueren, en stjoert in befêstiging fan ûntfangst fan it earste pakket.

Foar in berjocht besteande út in inkele pakket en ferstjoerd sûnder gebrûk fan in levering befêstiging, dit is de ienige steat. Nei it ferwurkjen fan sa'n berjocht wurdt de ferbining sluten.

Wetter - Agrarwetter - basis tastân foar it ûntfangen fan berjochtpakketten.

It skriuwt pakketten nei tydlike opslach, kontrolearret foar pakketferlies, stjoert befêstigingen fan levering fan in blok fan pakketten en it hiele berjocht, en stjoert fersiken foar werlevering fan ferlerne pakketten. As it hiele berjocht mei súkses ûntfongen is, giet de ferbining yn 'e Klear, oars komt der in time-outútgong op.

Klear - de ferbining slute as it heule berjocht mei sukses ûntfongen is.

Dizze steat is nedich foar berjocht gearstalling en foar it gefal as befêstiging fan berjocht levering waard ferlern op 'e wei nei de stjoerder. Dizze steat wurdt útgien troch in time-out, mar de ferbining wurdt beskôge as suksesfol sluten.

Djipper yn 'e koade. Transmission kontrôle ienheid

Ien fan 'e wichtichste eleminten fan Reliable UDP is it transmissiekontrôleblok. It doel fan dit blok is om aktuele ferbiningen en help-eleminten op te slaan, ynkommende pakketten te fersprieden nei de passende ferbiningen, in ynterface te leverjen foar it ferstjoeren fan pakketten nei de ferbining, en it protokol API útfiere. It transmissiekontrôleblok ûntfangt pakketten fan 'e UDP-laach en ferwiist se nei de steatmasjine foar ferwurking. Om pakketten te ûntfangen, ymplementearret it in asynchrone UDP-tsjinner.
Guon leden fan 'e klasse ReliableUdpConnectionControlBlock:

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

Implementaasje fan in asynchrone UDP-tsjinner:

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

Foar elke berjochtoerdracht wurdt in struktuer makke mei ferbiningynformaasje. Dizze struktuer wurdt neamd ferbining rekord.
Guon leden fan 'e klasse ReliableUdpConnectionRecord:

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

Djipper yn 'e koade. Steaten

Steaten implementearje de steatmasine fan it Reliable UDP-protokol, wêryn de haadferwurking fan pakketten foarkomt. De abstrakte klasse ReliableUdpState biedt in ynterface foar steat:

Utfiering fan it Reliable Udp-protokol foar .Net

De folsleine logika fan it protokol wurdt ymplementearre troch de hjirboppe presintearre klassen, tegearre mei in helpklasse dy't statyske metoaden leveret, lykas bygelyks it bouwen fan de ReliableUdp-header út it ferbiningsrecord.

Dêrnei sille wy yn detail de ymplemintaasje fan ynterfacemetoaden beskôgje dy't de basisalgoritmen fan it protokol definiearje.

DisposeByTimeout Metoade

De metoade DisposeByTimeout is ferantwurdlik foar it frijjaan fan ferbiningsboarnen as in time-out ferrint en foar it sinjalearjen fan suksesfol/mislearre berjochtlevering.
ReliableUdpState.DisposeByTimeout:

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

It wurdt allinnich oerskreaun yn 'e steat Klear.
Completed.DisposeByTimeout:

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

ProcessPackets metoade

De metoade ProcessPackets is ferantwurdlik foar ekstra ferwurking fan it pakket of pakketten. Rop direkt of fia in pakketwachttimer.

Able Wetter - Agrarwetter de metoade wurdt oerskreaun en is ferantwurdlik foar it kontrolearjen fan ferlerne pakketten en oergong nei steat Klear, yn gefal fan ûntfangst fan it lêste pakket en it trochjaan fan de kontrôle mei súkses
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);
  }
}

Able SendingCycle dizze metoade wurdt neamd allinnich op in timer, en is ferantwurdlik foar it opnij ferstjoeren fan it lêste berjocht, likegoed as foar it starten fan de ferbining sluten timer.
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);
}

Able Klear De metoade stopt de rinnende timer en stjoert in berjocht nei abonnees.
Completed.ProcessPackets:

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

ReceivePacket metoade

Able FirstPacketReceived De wichtichste taak fan 'e metoade is om te bepalen oft it earste berjochtpakket wirklik op' e ynterface kaam, en ek in berjocht te sammeljen dat bestiet út ien pakket.
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);
  }
}

Able SendingCycle dizze metoade wurdt oerskreaun te akseptearjen levering befêstigings en retransmission fersiken.
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));
}

Able Wetter - Agrarwetter yn 'e ReceivePacket-metoade bart it haadwurk fan it gearstallen fan it berjocht fan ynkommende pakketten.
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);
  }
}

Able Klear De ienige taak fan 'e metoade is it stjoeren fan in werhelle befêstiging fan suksesfolle levering fan it berjocht.
Completed.ReceivePacket:

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

SendPacket metoade

Able FirstPacketSending Dizze metoade stjoert it earste pakket mei gegevens, of, as it berjocht gjin leveringsbefêstiging fereasket, it hiele berjocht.
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);
}

Able SendingCycle yn dizze metoade wurdt in blok pakketten ferstjoerd.
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 );
  }
}

Djipper yn 'e koade. It oanmeitsjen en oprjochtsjen fan ferbinings

No't wy bekend binne mei de basissteaten en metoaden dy't brûkt wurde om steaten te ferwurkjen, kinne wy ​​​​yn in bytsje mear detail sjen nei ferskate foarbylden fan 'e wurking fan it protokol.
Data oerdracht diagram ûnder normale omstannichheden:Utfiering fan it Reliable Udp-protokol foar .Net

Litte wy de skepping in tichterby besjen ferbining rekord om it earste pakket te ferbinen en te ferstjoeren. De oerdracht wurdt altyd inisjearre troch de applikaasje dy't de API-metoade ropt foar it ferstjoeren fan it berjocht. Folgjende wurdt de StartTransmission-metoade fan it transmissiekontrôleblok brûkt, en begjint gegevensferfier foar it nije berjocht.
In útgeande ferbining oanmeitsje:

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

It earste pakket ferstjoere (FirstPacketSending-status):

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

Nei it ferstjoeren fan it earste pakket komt de stjoerder de steat yn SendingCycle - wachtsje op befêstiging fan pakket levering.
De ûntfangende kant, mei de EndReceive-metoade, akseptearret it ferstjoerde pakket en makket in nij ferbining rekord en jout dit pakket, mei in foarôf parsearde koptekst, troch nei de steat ReceivePacket metoade foar ferwurking FirstPacketReceived
In ferbining oanmeitsje oan 'e ûntfangende kant:

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

It earste pakket ûntfange en in erkenning ferstjoere (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);
  }
}

Djipper yn 'e koade. In ferbining slute fanwege time-out

It behanneljen fan timeouts is in wichtich ûnderdiel fan Reliable UDP. Beskôgje in foarbyld wêryn in tuskenknooppunt mislearret en gegevenslevering yn beide rjochtingen ûnmooglik wurdt.
Diagram fan it sluten fan in ferbining troch time-out:Utfiering fan it Reliable Udp-protokol foar .Net

Lykas út it diagram te sjen is, begjint de wurktimer fan de stjoerder fuort nei it ferstjoeren fan in blok pakketten. Dit bart yn 'e SendPacket-metoade fan' e steat SendingCycle.
De wurktimer ynskeakelje (SendingCycle-status):

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

Timer perioaden wurde ynsteld as de ferbining wurdt makke. Standert is ShortTimerPeriod 5 sekonden. Yn it foarbyld is it ynsteld op 1,5 sekonden.

Foar in ynkommende ferbining begjint de timer nei ûntfangst fan it lêste ûntfongen gegevenspakket; dit bart yn 'e ReceivePacket-metoade fan' e steat Wetter - Agrarwetter
It ynskeakeljen fan de wurktimer (assemblagestatus):

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

De ynkommende ferbining krige net mear pakketten yn 'e wachttiid foar de wurkjende timer. De timer ûntsloech en neamde de ProcessPackets-metoade, dy't ferlerne pakketten ûntdutsen en foar it earst fersiken foar ferstjoeren stjoerde.
It ferstjoeren fan fersiken foar werlevering (Assembly state):

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

De fariabele TimerSecondTry is ynsteld op wier. Dizze fariabele is ferantwurdlik foar it werstarten fan de wurktimer.

Oan 'e kant fan' e stjoerder wurdt ek in wurkjende timer aktivearre en it lêste ferstjoerde pakket wurdt opnij ferstjoerd.
Ynskeakelje de ferbining slute timer (SendingCycle steat):

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

Dêrnei wurdt de ferbining slute timer yn 'e útgeande ferbining begon.
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);
}

De standert timeout perioade foar de ferbining slute timer is 30 sekonden.

Nei in koarte tiid wurdt de wurkjende timer oan 'e ûntfangende kant wer ynskeakele, fersiken wurde wer ferstjoerd, wêrnei't de ferbining slute timer foar de ynkommende ferbining begjint

As de slutingstimers fjoer, wurde alle boarnen fan beide ferbiningsrecords frijjûn. De stjoerder rapporteart it mislearjen fan levering oan de streamop-applikaasje (sjoch API Reliable UDP).
Ferbining record boarnen frijjaan:

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

Djipper yn 'e koade. Data oerdracht herstel

Diagram fan herstel fan gegevensferfier yn gefal fan pakketferlies:Utfiering fan it Reliable Udp-protokol foar .Net

Lykas al besprutsen by it sluten fan in ferbining troch time-out, as de wurktimer ferrint, sil de ûntfanger kontrolearje op ferlerne pakketten. As der pakketferlies binne, wurdt in list gearstald mei de oantallen pakketten dy't de ûntfanger net berikten. Dizze nûmers wurde ynfierd yn 'e LostPackets-array fan in spesifike ferbining en fersiken foar opnij levering wurde ferstjoerd.
Fersiken ferstjoere om pakketten opnij te leverjen (tastân gearstalle):

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

De ôfstjoerder sil it fersyk foar ferstjoeren akseptearje en de ûntbrekkende pakketten opnij ferstjoere. It is de muoite wurdich opskriuwen dat op dit stuit de stjoerder hat al begûn in ferbining sluten timer en, op ûntfangst fan in fersyk, it wurdt reset.
Ferlearde pakketten opnij ferstjoere (Staat SendingCycle):

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

It resent pakket (pakket #3 yn it diagram) wurdt ûntfongen troch de ynkommende ferbining. In kontrôle wurdt makke om te soargjen dat it ûntfangstfinster fol is en normale gegevensferfier is weromset.
Kontrolearje op hits yn it ûntfangstfinster (tastân gearstalle):

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

Betroubere UDP API

Om ynteraksje mei de gegevens oerdracht protokol, der is in iepen klasse Reliable Udp, dat is in wrapper oer de oerdracht kontrôle blok. Hjir binne de wichtichste klasse leden:

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

Berjochten ûntfange wurdt útfierd troch abonnemint. Hantekening delegearje foar werombelmetoade:

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

Berjocht:

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

Om te abonnearjen op in spesifyk berjochttype en/of in spesifike stjoerder, wurde twa opsjonele parameters brûkt: ReliableUdpMessageTypes messageType en IPEndPoint ipEndPoint.

Berjochttypen:

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

It berjocht wurdt asyngroan ferstjoerd; foar dit doel implementeart it protokol in asynchrone programmearringsmodel:

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

It resultaat fan it ferstjoeren fan in berjocht sil wier wêze - as it berjocht mei súkses de ûntfanger berikt en falsk - as de ferbining sletten waard fanwegen in time-out:

public bool EndSendMessage(IAsyncResult asyncResult)

konklúzje

In protte waard net beskreaun binnen it ramt fan dit artikel. Draadkoördinaasjemeganismen, útsûndering en flaterhanneling, ymplemintaasje fan metoaden foar it ferstjoeren fan asynchrone berjochten. Mar de kearn fan it protokol, de beskriuwing fan 'e logika foar it ferwurkjen fan pakketten, it oprjochtsjen fan in ferbining en it útwurkjen fan timeouts, moat dúdliker wurde foar jo.

De oantoand ferzje fan it betroubere leveringsprotokol is frij robúst en fleksibel, en foldocht oan de earder definieare easken. Mar ik wol taheakje dat de beskreaune útfiering ferbettere wurde kin. Bygelyks, om trochput te fergrutsjen en dynamysk te feroarjen fan timerperioden, kinne jo meganismen tafoegje lykas sliding finster en RTT oan it protokol; it sil ek nuttich wêze om in meganisme te ymplementearjen foar it bepalen fan de MTU tusken ferbiningsknooppunten (mar allinich yn it gefal fan ferstjoeren grutte berjochten).

Tankewol foar jo oandacht, ik sjoch út nei jo opmerkings en opmerkings.

PS Foar dyjingen dy't ynteressearre binne yn details of gewoan it protokol wolle testen, hjir is de keppeling nei it projekt op GitHube:
Betrouber UDP-projekt

Nuttige keppelings en artikels

  1. TCP-protokol spesifikaasje: yn it Ingelsk и yn it Russysk
  2. UDP protokol spesifikaasje: yn it Ingelsk и yn it Russysk
  3. Diskusje oer it RUDP-protokol: draft-ietf-sigtran-reliable-udp-00
  4. Betrouber gegevensprotokol: rfc 908 и rfc 1151
  5. In ienfâldige ymplemintaasje fan leveringsbefêstiging oer UDP: Nim totale kontrôle fan jo netwurken mei .NET en UDP
  6. In artikel dat meganismen beskriuwt foar it oerwinnen fan NAT's: Peer-to-peer-kommunikaasje oer netwurkadresoersetters
  7. Implementaasje fan it asynchrone programmearring model: It útfieren fan it CLR Asynchronous Programming Model и Hoe kinne jo it IAsyncResult-ûntwerppatroan ymplementearje
  8. It asynchrone programmearringmodel ferpleatse nei it taakbasearre asynchrone patroan (APM nei TAP):
    TPL en tradisjoneel .NET Asynchronous Programming
    Ynterop mei oare asynchrone patroanen en soarten

Update: Tankewol boargemaster и sidristij foar it idee om in taak ta te foegjen oan 'e ynterface. De kompatibiliteit fan de bibleteek mei âldere bestjoeringssystemen wurdt net beynfloede, om't It 4e ramt stipet sawol XP- as 2003-tsjinner.

Boarne: www.habr.com

Add a comment