Implementatie van het Reliable Udp-protocol voor .Net

Het internet is al lang geleden veranderd. Een van de belangrijkste protocollen van internet - UDP wordt door toepassingen niet alleen gebruikt om datagrammen en uitzendingen te leveren, maar ook om "peer-to-peer"-verbindingen tussen netwerkknooppunten te bieden. Vanwege het eenvoudige ontwerp heeft dit protocol veel voorheen ongeplande toepassingen, maar de tekortkomingen van het protocol, zoals het ontbreken van gegarandeerde levering, zijn nergens verdwenen. Dit artikel beschrijft de implementatie van het gegarandeerde leveringsprotocol via UDP.
Inhoud:Toegang
Protocolvereisten
Betrouwbare UDP-header
Algemene principes van het protocol
Time-outs en protocoltimers
Betrouwbaar UDP-transmissiestatusdiagram
Dieper in de code. transmissie regeleenheid
Dieper in de code. staten

Dieper in de code. Verbindingen maken en tot stand brengen
Dieper in de code. De verbinding sluiten bij time-out
Dieper in de code. Gegevensoverdracht herstellen
Betrouwbare UDP-API
Conclusie
Handige links en artikelen

Toegang

De oorspronkelijke architectuur van internet ging uit van een homogene adresruimte waarin elk knooppunt een globaal en uniek IP-adres had en rechtstreeks kon communiceren met andere knooppunten. Nu heeft internet in feite een andere architectuur - een gebied met globale IP-adressen en veel gebieden met privé-adressen verborgen achter NAT-apparaten.In deze architectuur kunnen alleen apparaten in de globale adresruimte gemakkelijk communiceren met iedereen op het netwerk omdat ze een uniek, wereldwijd routeerbaar IP-adres hebben. Een knooppunt op een particulier netwerk kan verbinding maken met andere knooppunten op hetzelfde netwerk en kan ook verbinding maken met andere bekende knooppunten in de globale adresruimte. Deze interactie wordt grotendeels bereikt dankzij het netwerkadresvertalingsmechanisme. NAT-apparaten, zoals Wi-Fi-routers, maken speciale vertaaltabelvermeldingen voor uitgaande verbindingen en wijzigen IP-adressen en poortnummers in pakketten. Hierdoor zijn uitgaande verbindingen van het particuliere netwerk naar hosts in de globale adresruimte mogelijk. Maar tegelijkertijd blokkeren NAT-apparaten meestal al het inkomende verkeer, tenzij er aparte regels voor inkomende verbindingen worden ingesteld.

Deze architectuur van internet is correct genoeg voor client-servercommunicatie, waarbij clients zich in privénetwerken kunnen bevinden en servers een globaal adres hebben. Maar het creëert moeilijkheden voor de directe verbinding van twee knooppunten ertussen verscheidene particuliere netwerken. Een directe verbinding tussen twee knooppunten is belangrijk voor peer-to-peer-toepassingen zoals spraakoverdracht (Skype), toegang op afstand tot een computer (TeamViewer) of online gaming.

Een van de meest effectieve methoden om een ​​peer-to-peer-verbinding tot stand te brengen tussen apparaten op verschillende particuliere netwerken, wordt perforatie genoemd. Deze techniek wordt het meest gebruikt bij toepassingen die gebaseerd zijn op het UDP-protocol.

Maar als uw toepassing een gegarandeerde levering van gegevens nodig heeft, bijvoorbeeld als u bestanden overdraagt ​​tussen computers, dan zal het gebruik van UDP veel problemen opleveren vanwege het feit dat UDP geen gegarandeerd leveringsprotocol is en geen pakketbezorging op volgorde biedt, in tegenstelling tot TCP protocol.

In dit geval is het vereist om een ​​toepassingslaagprotocol te implementeren dat de nodige functionaliteit biedt en werkt via UDP om gegarandeerde pakketbezorging te garanderen.

Ik wil meteen opmerken dat er een TCP-perforatietechniek is voor het tot stand brengen van TCP-verbindingen tussen knooppunten in verschillende particuliere netwerken, maar vanwege het gebrek aan ondersteuning ervoor door veel NAT-apparaten, wordt dit meestal niet beschouwd als de belangrijkste manier om verbinding te maken zulke knooppunten.

Voor de rest van dit artikel zal ik me alleen concentreren op de implementatie van het gegarandeerde leveringsprotocol. De implementatie van de UDP-perforatietechniek wordt in de volgende artikelen beschreven.

Protocolvereisten

  1. Betrouwbare pakketbezorging geïmplementeerd via een positief feedbackmechanisme (de zogenaamde positieve bevestiging)
  2. De behoefte aan een efficiënte overdracht van big data, d.w.z. het protocol moet onnodige pakketdoorgifte vermijden
  3. Het moet mogelijk zijn om het bezorgingsbevestigingsmechanisme te annuleren (de mogelijkheid om te functioneren als een "puur" UDP-protocol)
  4. Mogelijkheid om commandomodus te implementeren, met bevestiging van elk bericht
  5. De basiseenheid van gegevensoverdracht via het protocol moet een bericht zijn

Deze vereisten vallen grotendeels samen met de vereisten van het Reliable Data Protocol beschreven in rfc908 и rfc1151, en ik vertrouwde op die normen bij het ontwikkelen van dit protocol.

Laten we, om deze vereisten te begrijpen, kijken naar de timing van gegevensoverdracht tussen twee netwerkknooppunten met behulp van de TCP- en UDP-protocollen. Laten we in beide gevallen een pakketje kwijt zijn.
Overdracht van niet-interactieve gegevens via TCP:Implementatie van het Reliable Udp-protocol voor .Net

Zoals u in het diagram kunt zien, zal TCP in geval van pakketverlies het verloren pakket detecteren en dit aan de afzender rapporteren door naar het nummer van het verloren segment te vragen.
Gegevensoverdracht via UDP-protocol:Implementatie van het Reliable Udp-protocol voor .Net

UDP neemt geen stappen voor verliesdetectie. Controle van transmissiefouten in het UDP-protocol is volledig de verantwoordelijkheid van de applicatie.

Foutdetectie in het TCP-protocol wordt bereikt door een verbinding tot stand te brengen met een eindknooppunt, de status van die verbinding op te slaan, het aantal bytes aan te geven dat in elke pakketkop is verzonden en ontvangstmeldingen te melden met behulp van een bevestigingsnummer.

Om de prestaties te verbeteren (d.w.z. meer dan één segment verzenden zonder een bevestiging te ontvangen), gebruikt het TCP-protocol bovendien het zogenaamde transmissievenster - het aantal bytes aan gegevens dat de afzender van het segment verwacht te ontvangen.

Zie voor meer informatie over het TCP-protocol rfc793, van UDP naar rfc768waar ze in feite zijn gedefinieerd.

Uit het bovenstaande blijkt duidelijk dat om een ​​betrouwbaar protocol voor het afleveren van berichten over UDP (hierna aangeduid als Betrouwbare UDP), is het vereist om mechanismen voor gegevensoverdracht te implementeren die vergelijkbaar zijn met die van TCP. Namelijk:

  • verbindingsstatus opslaan
  • segmentnummering gebruiken
  • gebruik speciale bevestigingspakketten
  • gebruik een vereenvoudigd venstermechanisme om de protocoldoorvoer te verhogen

Daarnaast heb je nodig:

  • signaleren het begin van een bericht, om middelen toe te wijzen voor de verbinding
  • signaleren het einde van een bericht, om het ontvangen bericht door te geven aan de stroomopwaartse applicatie en protocolbronnen vrij te geven
  • toestaan ​​dat het verbindingsspecifieke protocol het bezorgingsbevestigingsmechanisme uitschakelt om te functioneren als "pure" UDP

Betrouwbare UDP-header

Bedenk dat een UDP-datagram is ingekapseld in een IP-datagram. Het Reliable UDP-pakket wordt op de juiste manier "verpakt" in een UDP-datagram.
Betrouwbare inkapseling van UDP-headers:Implementatie van het Reliable Udp-protocol voor .Net

De structuur van de Reliable UDP-header is vrij eenvoudig:

Implementatie van het Reliable Udp-protocol voor .Net

  • Vlaggen - pakketcontrolevlaggen
  • MessageType - berichttype dat wordt gebruikt door upstream-toepassingen om zich te abonneren op specifieke berichten
  • TransmissionId - het nummer van de verzending, samen met het adres en de poort van de ontvanger, geeft een unieke identificatie van de verbinding
  • Pakketnummer - pakketnummer
  • Opties - aanvullende protocolopties. In het geval van het eerste pakket wordt het gebruikt om de grootte van het bericht aan te geven

Vlaggen zijn als volgt:

  • FirstPacket - het eerste pakket van het bericht
  • NoAsk - voor het bericht hoeft geen bevestigingsmechanisme te zijn ingeschakeld
  • LastPacket - het laatste pakket van het bericht
  • RequestForPacket - bevestigingspakket of verzoek om een ​​verloren pakket

Algemene principes van het protocol

Omdat Reliable UDP gericht is op gegarandeerde berichtoverdracht tussen twee knooppunten, moet het een verbinding met de andere kant tot stand kunnen brengen. Om een ​​verbinding tot stand te brengen, stuurt de afzender een pakket met de FirstPacket-vlag, het antwoord hierop betekent dat de verbinding tot stand is gebracht. Alle antwoordpakketten, of met andere woorden bevestigingspakketten, stellen de waarde van het veld PacketNumber altijd in op één meer dan de grootste PacketNumber-waarde van succesvol ontvangen pakketten. Het veld Opties voor het eerste verzonden pakket is de grootte van het bericht.

Een soortgelijk mechanisme wordt gebruikt om een ​​verbinding te verbreken. De LastPacket-vlag wordt ingesteld op het laatste pakket van het bericht. In het antwoordpakket wordt het nummer van het laatste pakket + 1 aangegeven, wat voor de ontvangende kant betekent dat het bericht succesvol is afgeleverd.
Verbindingsopbouw- en beëindigingsschema:Implementatie van het Reliable Udp-protocol voor .Net

Wanneer de verbinding tot stand is gebracht, begint de gegevensoverdracht. Gegevens worden verzonden in pakketblokken. Elk blok, behalve het laatste, bevat een vast aantal pakketten. Het is gelijk aan de venstergrootte voor ontvangen/verzenden. Het laatste gegevensblok bevat mogelijk minder pakketten. Na het verzenden van elk blok wacht de verzendende partij op een ontvangstbevestiging of een verzoek om verloren pakketten opnieuw af te leveren, waarbij het venster voor ontvangen/verzenden open blijft om antwoorden te ontvangen. Na bevestiging van de levering van het blok, verschuift het ontvangst-/verzendvenster en wordt het volgende gegevensblok verzonden.

De ontvangende kant ontvangt de pakketten. Elk pakket wordt gecontroleerd om te zien of het binnen het transmissievenster valt. Pakketten en duplicaten die niet in het venster vallen, worden eruit gefilterd. Omdat Als de grootte van het venster vast is en hetzelfde is voor de ontvanger en de afzender, dan wordt, in het geval dat een blok pakketten zonder verlies wordt afgeleverd, het venster verschoven om pakketten van het volgende gegevensblok te ontvangen en wordt er een ontvangstbevestiging gegeven. verstuurd. Als het venster niet binnen de door de werktimer ingestelde periode vol raakt, wordt gecontroleerd welke pakketten niet zijn afgeleverd en worden verzoeken tot herbezorging verzonden.
Doorgiftediagram:Implementatie van het Reliable Udp-protocol voor .Net

Time-outs en protocoltimers

Er zijn verschillende redenen waarom er geen verbinding tot stand kan worden gebracht. Bijvoorbeeld als de ontvangende partij offline is. In dit geval wordt de verbinding verbroken door een time-out wanneer u probeert een verbinding tot stand te brengen. De Reliable UDP-implementatie gebruikt twee timers om time-outs in te stellen. De eerste, de werkende timer, wordt gebruikt om te wachten op een reactie van de externe host. Als het aan de kant van de afzender wordt geactiveerd, wordt het laatst verzonden pakket opnieuw verzonden. Als de timer bij de ontvanger afloopt, wordt er een controle op verloren pakketten uitgevoerd en worden verzoeken om herbezorging verzonden.

De tweede timer is nodig om de verbinding te verbreken in het geval van een gebrek aan communicatie tussen de knooppunten. Voor de zenderzijde start het onmiddellijk nadat de werktimer is verstreken en wacht het op een reactie van het externe knooppunt. Als er binnen de opgegeven periode geen reactie komt, wordt de verbinding verbroken en worden bronnen vrijgegeven. Voor de ontvangende kant wordt de timer voor het sluiten van de verbinding gestart nadat de werktimer twee keer is verlopen. Dit is nodig om het verlies van het bevestigingspakket te voorkomen. Wanneer de timer afloopt, wordt ook de verbinding verbroken en worden bronnen vrijgegeven.

Betrouwbaar UDP-transmissiestatusdiagram

De principes van het protocol zijn geïmplementeerd in een eindige toestandsmachine, waarvan elke toestand verantwoordelijk is voor een bepaalde logica van pakketverwerking.
Betrouwbaar UDP-statusdiagram:

Implementatie van het Reliable Udp-protocol voor .Net

Gesloten - is niet echt een toestand, het is een begin- en eindpunt voor de automaat. Voor staat Gesloten er wordt een transmissiecontroleblok ontvangen dat, door een asynchrone UDP-server te implementeren, pakketten doorstuurt naar de juiste verbindingen en de statusverwerking start.

FirstPacketSending – de begintoestand waarin de uitgaande verbinding zich bevindt op het moment dat het bericht wordt verzonden.

In deze toestand wordt het eerste pakket voor normale berichten verzonden. Voor berichten zonder verzendbevestiging is dit de enige status waarin het volledige bericht wordt verzonden.

VerzendenCycle – grondtoestand voor de verzending van berichtpakketten.

Overgang naar het van de staat FirstPacketSending uitgevoerd nadat het eerste pakket van het bericht is verzonden. In deze staat komen alle bevestigingen en verzoeken om heruitzendingen. Afsluiten is in twee gevallen mogelijk - in het geval van een succesvolle bezorging van het bericht of door een time-out.

Eerste pakket ontvangen – de beginstatus voor de ontvanger van het bericht.

Het controleert de juistheid van het begin van de verzending, creëert de nodige structuren en stuurt een ontvangstbevestiging van het eerste pakket.

Voor een bericht dat uit één pakket bestaat en is verzonden zonder bewijs van aflevering, is dit de enige status. Na verwerking van zo'n bericht wordt de verbinding verbroken.

Het assembleren – basisstatus voor het ontvangen van berichtpakketten.

Het schrijft pakketten naar tijdelijke opslag, controleert op pakketverlies, verzendt bevestigingen voor de bezorging van een blok pakketten en het volledige bericht, en verzendt verzoeken om verloren pakketten opnieuw af te leveren. Bij succesvolle ontvangst van het gehele bericht gaat de verbinding in de staat Voltooid, anders wordt er een time-out afgesloten.

Voltooid – verbreken van de verbinding bij succesvolle ontvangst van het gehele bericht.

Deze status is nodig voor het samenstellen van het bericht en voor het geval dat de ontvangstbevestiging van het bericht verloren is gegaan op weg naar de afzender. Deze status wordt beëindigd door een time-out, maar de verbinding wordt als succesvol gesloten beschouwd.

Dieper in de code. transmissie regeleenheid

Een van de belangrijkste elementen van Reliable UDP is het transmissiecontroleblok. De taak van dit blok is om huidige verbindingen en hulpelementen op te slaan, inkomende pakketten naar de overeenkomstige verbindingen te distribueren, een interface te bieden voor het verzenden van pakketten naar een verbinding en de protocol-API te implementeren. Het transmissiecontroleblok ontvangt pakketten van de UDP-laag en stuurt ze door naar de toestandsmachine voor verwerking. Om pakketten te ontvangen, implementeert het een asynchrone UDP-server.
Enkele leden van de 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;    	
  //...
}

Implementatie van asynchrone UDP-server:

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

Voor elke berichtoverdracht wordt een structuur gemaakt die informatie bevat over de verbinding. Zo'n constructie heet verbindingsrecord.
Enkele leden van de 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;
  //...
}

Dieper in de code. staten

Staten implementeren de statusmachine van het Reliable UDP-protocol, waar de hoofdverwerking van pakketten plaatsvindt. De abstracte klasse ReliableUdpState biedt een interface voor de status:

Implementatie van het Reliable Udp-protocol voor .Net

De volledige logica van het protocol wordt geïmplementeerd door de hierboven gepresenteerde klassen, samen met een hulpklasse die statische methoden biedt, zoals bijvoorbeeld het samenstellen van de ReliableUdp-header uit het verbindingsrecord.

Vervolgens zullen we in detail ingaan op de implementatie van de interfacemethoden die de basisalgoritmen van het protocol bepalen.

DisposeByTimeout-methode

De DisposeByTimeout-methode is verantwoordelijk voor het vrijgeven van verbindingsresources na een time-out en voor het signaleren van geslaagde/mislukte bezorging van berichten.
BetrouwbareUdpState.DisposeByTimeout:

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

Het is alleen opgeheven in de staat Voltooid.
Voltooid.DisposeByTimeout:

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

ProcessPackets-methode

De methode ProcessPackets is verantwoordelijk voor de aanvullende verwerking van een pakket of pakketten. Rechtstreeks gebeld of via een wachttimer voor pakketten.

In staat tot Het assembleren de methode wordt overschreven en is verantwoordelijk voor het controleren op verloren pakketten en het overgaan naar de status Voltooid, in het geval van het ontvangen van het laatste pakket en het slagen voor een succesvolle controle
Assembleren.ProcesPakketten:

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

In staat tot VerzendenCycle deze methode wordt alleen aangeroepen op een timer en is verantwoordelijk voor het opnieuw verzenden van het laatste bericht en het inschakelen van de timer voor het sluiten van de verbinding.
VerzendenCycle.ProcessPackets:

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

In staat tot Voltooid de methode stopt de lopende timer en stuurt het bericht naar de abonnees.
Voltooid.Procespakketten:

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

ReceivePacket-methode

In staat tot Eerste pakket ontvangen de belangrijkste taak van de methode is om te bepalen of het eerste berichtenpakket daadwerkelijk bij de interface is aangekomen, en ook om een ​​bericht te verzamelen dat uit een enkel pakket bestaat.
FirstPacketOntvangen.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);
  }
}

In staat tot VerzendenCycle deze methode wordt overschreven om ontvangstbevestigingen en verzoeken tot herverzending te accepteren.
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));
}

In staat tot Het assembleren bij de ReceivePacket-methode vindt het belangrijkste werk plaats van het samenstellen van een bericht uit inkomende pakketten.
Assembleren.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);
  }
}

In staat tot Voltooid de enige taak van de methode is het verzenden van een herbevestiging van de succesvolle aflevering van het bericht.
Voltooid.Ontvangpakket:

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

Pakketmethode verzenden

In staat tot FirstPacketSending deze methode verzendt het eerste gegevenspakket, of als het bericht geen ontvangstbevestiging vereist, het volledige bericht.
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);
}

In staat tot VerzendenCycle bij deze methode wordt een blok pakketten verzonden.
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 de code. Verbindingen maken en tot stand brengen

Nu we de basisstatussen en de methoden die worden gebruikt om met statussen om te gaan hebben gezien, laten we een paar voorbeelden van hoe het protocol werkt wat gedetailleerder bekijken.
Datatransmissieschema onder normale omstandigheden:Implementatie van het Reliable Udp-protocol voor .Net

Overweeg in detail de creatie verbindingsrecord om verbinding te maken en het eerste pakket te verzenden. De overdracht wordt altijd geïnitieerd door de applicatie die de API voor het verzenden van berichten aanroept. Vervolgens wordt de StartTransmission-methode van het transmissiebesturingsblok aangeroepen, waarmee de verzending van gegevens voor het nieuwe bericht wordt gestart.
Een uitgaande verbinding maken:

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

Het eerste pakket verzenden (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);
}

Na het verzenden van het eerste pakket komt de afzender in de staat VerzendenCycle – wacht op bevestiging van pakketbezorging.
De ontvangende kant, met behulp van de EndReceive-methode, ontvangt het verzonden pakket, maakt een nieuw verbindingsrecord en geeft dit pakket, met een vooraf geparseerde header, door aan de ReceivePacket-methode van de staat voor verwerking Eerste pakket ontvangen
Een verbinding maken aan de ontvangende 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);
}

Het eerste pakket ontvangen en een bevestiging verzenden (status FirstPacketReceived):

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 de code. De verbinding sluiten bij time-out

Time-outafhandeling is een belangrijk onderdeel van Reliable UDP. Overweeg een voorbeeld waarin een tussenliggend knooppunt faalde en gegevenslevering in beide richtingen onmogelijk werd.
Schema voor het sluiten van een verbinding door time-out:Implementatie van het Reliable Udp-protocol voor .Net

Zoals te zien is in het diagram, begint de werktimer van de afzender onmiddellijk na het verzenden van een blok pakketten. Dit gebeurt in de methode SendPacket van de staat VerzendenCycle.
De werktimer inschakelen (SendingCycle-status):

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

De timerperioden worden ingesteld wanneer de verbinding tot stand wordt gebracht. De standaard ShortTimerPeriod is 5 seconden. In het voorbeeld is deze ingesteld op 1,5 seconden.

Voor een inkomende verbinding start de timer na ontvangst van het laatste inkomende datapakket, dit gebeurt in de ReceivePacket methode van de staat Het assembleren
De werktimer inschakelen (montagestatus):

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

Er zijn geen pakketten meer aangekomen op de inkomende verbinding in afwachting van de werkende timer. De timer ging af en riep de ProcessPackets-methode aan, waar de verloren pakketten werden gevonden en voor de eerste keer herbezorgverzoeken werden verzonden.
Herleveringsverzoeken verzenden (montagestatus):

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 variabele TimerSecondTry is ingesteld op waar. Deze variabele is verantwoordelijk voor het herstarten van de werktimer.

Aan de kant van de afzender wordt ook de werktimer geactiveerd en wordt het laatst verzonden pakket opnieuw verzonden.
Timer voor het sluiten van verbindingen inschakelen (Status SendingCycle):

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

Daarna start de timer voor het sluiten van de verbinding in de uitgaande verbinding.
BetrouwbareUdpState.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 time-outperiode van de timer voor het sluiten van de verbinding is standaard 30 seconden.

Na een korte tijd wordt de werkende timer aan de kant van de ontvanger opnieuw geactiveerd, verzoeken worden opnieuw verzonden, waarna de timer voor het sluiten van de verbinding start voor de inkomende verbinding

Wanneer de sluittimers worden geactiveerd, worden alle bronnen van beide verbindingsrecords vrijgegeven. De afzender meldt de bezorgingsfout aan de stroomopwaartse toepassing (zie Betrouwbare UDP-API).
Bronnen voor verbindingsrecords vrijgeven:

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 de code. Gegevensoverdracht herstellen

Hersteldiagram gegevensoverdracht in geval van pakketverlies:Implementatie van het Reliable Udp-protocol voor .Net

Zoals al besproken bij het sluiten van de verbinding bij time-out, zal de ontvanger controleren op verloren pakketten wanneer de werktimer afloopt. In geval van pakketverlies wordt een lijst samengesteld met het aantal pakketten dat de ontvanger niet heeft bereikt. Deze nummers worden ingevoerd in de LostPackets-array van een specifieke verbinding en verzoeken om herbezorging worden verzonden.
Verzoeken verzenden om pakketten opnieuw af te leveren (montagestatus):

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 afzender accepteert het verzoek tot herbezorging en stuurt de ontbrekende pakketten. Het is vermeldenswaard dat op dit moment de afzender de timer voor het sluiten van de verbinding al heeft gestart en, wanneer een verzoek wordt ontvangen, deze wordt gereset.
Verloren pakketten opnieuw verzenden (SendingCycle-status):

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

Het opnieuw verzonden pakket (pakket #3 in het diagram) wordt ontvangen door de inkomende verbinding. Er wordt gecontroleerd of het ontvangstvenster vol is en de normale gegevensoverdracht wordt hersteld.
Controleren op treffers in het ontvangstvenster (montagestatus):

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

Betrouwbare UDP-API

Om te communiceren met het gegevensoverdrachtsprotocol, is er een open Reliable Udp-klasse, die een wrapper is over het overdrachtsbesturingsblok. Dit zijn de belangrijkste leden van de 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()    
}

Berichten worden per abonnement ontvangen. Handtekening delegeren voor de callback-methode:

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

Bericht:

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

Om u te abonneren op een specifiek berichttype en/of een specifieke afzender, worden twee optionele parameters gebruikt: ReliableUdpMessageTypes messageType en IPEndPoint ipEndPoint.

Soorten berichten:

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

Het bericht wordt asynchroon verzonden; hiervoor implementeert het protocol een asynchroon programmeermodel:

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

Het resultaat van het verzenden van een bericht is waar - als het bericht de ontvanger heeft bereikt en onwaar - als de verbinding is verbroken door een time-out:

public bool EndSendMessage(IAsyncResult asyncResult)

Conclusie

Veel is in dit artikel niet beschreven. Mechanismen voor het matchen van threads, afhandeling van uitzonderingen en fouten, implementatie van asynchrone methoden voor het verzenden van berichten. Maar de kern van het protocol, de beschrijving van de logica voor het verwerken van pakketten, het tot stand brengen van een verbinding en het afhandelen van time-outs, moet u duidelijk zijn.

De gedemonstreerde versie van het betrouwbare leveringsprotocol is robuust en flexibel genoeg om aan de eerder gedefinieerde eisen te voldoen. Maar ik wil hieraan toevoegen dat de beschreven implementatie kan worden verbeterd. Om bijvoorbeeld de doorvoer te verhogen en timerperioden dynamisch te wijzigen, kunnen mechanismen zoals glijdend venster en RTT aan het protocol worden toegevoegd. Het zal ook nuttig zijn om een ​​mechanisme te implementeren voor het bepalen van de MTU tussen verbindingsknooppunten (maar alleen als er grote berichten worden verzonden ).

Dank u voor uw aandacht, ik kijk uit naar uw opmerkingen en opmerkingen.

PS Voor degenen die geïnteresseerd zijn in de details of gewoon het protocol willen testen, de link naar het project op GitHube:
Betrouwbaar UDP-project

Handige links en artikelen

  1. Specificatie TCP-protocol: на английском и in het Russisch
  2. Specificatie UDP-protocol: на английском и in het Russisch
  3. Bespreking van het RUDP-protocol: concept-ietf-sigtran-betrouwbaar-udp-00
  4. Betrouwbaar gegevensprotocol: rfc908 и rfc1151
  5. Een eenvoudige implementatie van afleverbevestiging via UDP: Neem totale controle over uw netwerk met .NET en UDP
  6. Artikel waarin NAT-traversal-mechanismen worden beschreven: Peer-to-peer-communicatie via netwerkadresvertalers
  7. Implementatie van het asynchrone programmeermodel: Implementatie van het CLR-model voor asynchroon programmeren и Het IAsyncResult-ontwerppatroon implementeren
  8. Het asynchrone programmeermodel overzetten naar het taakgebaseerde asynchrone patroon (APM in TAP):
    TPL en traditionele .NET asynchrone programmering
    Interoperabiliteit met andere asynchrone patronen en typen

Bijwerken: bedankt burgemeester ovp и sidristij voor het idee om een ​​taak toe te voegen aan de interface. De compatibiliteit van de bibliotheek met oude besturingssystemen wordt niet geschonden, omdat Het 4e framework ondersteunt zowel XP als 2003 server.

Bron: www.habr.com

Voeg een reactie