Implementering af Reliable Udp protokollen til .Net

Internettet har længe ændret sig. En af de grundlæggende protokoller på internettet – UDP – anvendes ikke kun af applikationer til levering af datagrammer og broadcasting, men også til at sikre "peer-to-peer" forbindelser mellem netværksnoder. På grund af dets enkle struktur er der opstået mange uventede anvendelser af denne protokol, men ulemperne ved protokollen, såsom manglende garanti for levering, er stadig til stede. Denne artikel beskriver implementeringen af en protokol for garanteret levering over UDP.
Indhold:Introduktion
Krav til protokollen
Overskrift for Reliable UDP
Generelle principper for protokollens funktion
Timeouts og timere for protokollen
Tilstandsgraf for transmission af Reliable UDP
Dybdegående i koden. Kontrollaget for transmission
Dybdegående i koden. Tilstande

Dybdegående i koden. Oprettelse og etablering af forbindelser
Dybdegående i koden. Lukning af forbindelse ved timeout
Dybdegående i koden. Gendannelse af datatransmission
API Reliable UDP
Konklusion
Nyttige links og artikler

Introduktion

Den oprindelige arkitektur af internettet antog et homogent adresserum, hvor hver node havde en global og unik IP-adresse, og kunne kommunikere direkte med andre noder. I dag har internettet imidlertid en anden arkitektur – én zone for globale IP-adresser og mange zoner med private adresser, skjult bag NAT-enheder.I denne arkitektur kan kun enheder, der befinder sig i det globale adresseområde, nemt interagere med andre i nettet, da de har en unik, globalt routable IP-adresse. En node, der befinder sig i et privat netværk, kan forbinde sig med andre noder i samme netværk samt forbindes med andre kendte noder i det globale adresseområde. Denne interaktion opnås i høj grad takket være mekanismen til oversættelse af netværksadresser. NAT-enheder, såsom Wi-Fi-routere, opretter specielle optegnelser i oversættelsestabellerne for udgående forbindelser og ændrer IP-adresserne og portnumrene i pakkerne. Dette gør det muligt at etablere en udgående forbindelse fra det private netværk til noder i det globale adresseområde. Men samtidig blokerer NAT-enheder normalt al indgående trafik, medmindre der er opsat specifikke regler for indgående forbindelser.

Denne arkitektur af internettet er ret passende til klient-server-interaktion, hvor klienter kan befinde sig i private netværk, mens serverne har en global adresse. Men den skaber vanskeligheder for direkte forbindelse mellem to noder i forskellige private netværk. forskellige private netværk. Direkte forbindelser mellem to noder er vigtige for peer-to-peer-applikationer, såsom stemmeoverførsel (Skype), fjernadgang til computeren (TeamViewer) eller online-spil.

En af de mest effektive metoder til at etablere peer-to-peer-forbindelser mellem enheder i forskellige private netværk kaldes 'hole punching'. Denne teknik bruges oftest med applikationer baseret på UDP-protokollen.

Men hvis din applikation kræver garanteret levering af data, for eksempel når du overfører filer mellem computere, vil der opstå mange udfordringer ved brug af UDP, da UDP ikke er en protokol til garanteret levering og ikke sikrer, at pakkerne leveres i rækkefølge, i modsætning til TCP-protokollen.

I dette tilfælde kræves det at implementere en applikationsprotokol, der sikrer nødvendig funktionalitet og fungerer oven på UDP for at sikre garanteret levering af pakker.

Jeg vil straks bemærke, at der findes en teknik kaldet TCP hole punching til etablering af TCP-forbindelser mellem noder i forskellige private netværk, men på grund af manglende support fra mange NAT-enheder betragtes den sædvanligvis ikke som den primære metode til at forbinde sådanne noder.

I denne artikel vil jeg kun fokusere på implementeringen af et protokol for garanteret levering. Implementeringen af UDP hole punching-teknikken vil blive beskrevet i følgende artikler.

Krav til protokollen

  1. Sikker levering af pakker, der er realiseret gennem en positiv feedback-mekanisme (den såkaldte positive acknowledgment)
  2. Der er behov for effektiv transmission af store data, dvs. protokollen bør undgå unødvendige retransmissioner af pakker.
  3. Der skal være mulighed for at annullere bekræftelsesmekanismen (mulighed for at fungere som en "ren" UDP-protokol).
  4. Mulighed for at implementere kommandotilstand med bekræftelse af hver meddelelse.
  5. Den grundlæggende enhed for dataoverførsel via protokollen skal være en meddelelse.

Disse krav stemmer i høj grad overens med kravene til Reliable Data Protocol, der er beskrevet i rfc 908 og rfc 1151, og jeg har baseret mig på disse standarder ved udviklingen af denne protokol.

For at forstå disse krav, lad os se på tidsdiagrammerne for dataoverførsel mellem to noder i netværket via TCP- og UDP-protokoller. Lad os antage, at vi i begge tilfælde har mistet en pakke.
Overførsel af ikke-interaktive data via TCP:Implementering af Reliable Udp protokollen til .Net

Som det fremgår af diagrammet, vil TCP opdage den mistede pakke i tilfælde af pakkeudfald og informere afsenderen om dette ved at anmode om nummeret på den mistede segment.
Dataoverførsel via UDP-protokollen:Implementering af Reliable Udp protokollen til .Net

UDP tager ingen skridt til at opdage tab. Fejlkontrol i UDP-protokollen er fuldstændig overladt til applikationen.

Fejlregistrering i TCP-protokollen opnås gennem oprettelsen af forbindelse til slutpunktet, opretholdelse af tilstanden for denne forbindelse, angivelse af nummeret på de sendte byte i hver pakkehoved og meddelelser om modtagelse ved hjælp af bekræftelsesnummeret 'acknowledge number'.

Derudover anvender TCP-protokollen for at forbedre ydelsen (dvs. at sende mere end ét segment uden at modtage bekræftelse) det såkaldte transmissionsvindue — antallet af byte data, som segmentets afsender forventer at modtage.

Du kan læse mere detaljeret om TCP-protokollen i rfc 793, med UDP i rfc 768, hvor de faktisk er defineret.

Som det er beskrevet ovenfor, er det klart, at for at skabe en pålidelig protokol til levering af meddelelser over UDP (som vi herefter vil kalde Reliable UDP), er det nødvendigt at implementere mekanismer til datatransmission, der ligner dem fra TCP. Det vil sige:

  • bevare forbindelsens tilstand
  • bruge segmentnumre
  • benytte specielle bekræftelsespakker
  • anvende en forenklet vinduesmecanisme for at øge protokollens båndbredde

Derudover er det nødvendigt:

  • at signalere begyndelsen af meddelelsen for at afsætte ressourcer til forbindelsen
  • at signalere slutningen af meddelelsen for at overføre den modtagne meddelelse til den højere applikation og frigive protokollens ressourcer
  • at tillade protokollen at deaktivere bekræftelsesmekanismen for specifikke forbindelser, så den kan fungere som «ren» UDP

Overskrift for Reliable UDP

Lad os huske, at UDP-datagrammet er indkapslet i IP-datagrammet. Reliable UDP-pakken 'pakkes' derfor ind i UDP-datagrammet.
Indkapsling af Reliable UDP-headeren:Implementering af Reliable Udp protokollen til .Net

Strukturen af Reliable UDP-headeren er ret enkel:

Implementering af Reliable Udp protokollen til .Net

  • Flags – kontrolflag for pakken
  • MessageType – meddelelsestypen, der bruges af højere applikationer til at abonnere på bestemte meddelelser
  • TransmissionId — overførselsnummer, der sammen med adressen og porten til modtageren entydigt identificerer forbindelsen
  • PacketNumber – pakke nummer
  • Options – yderligere protokolmuligheder. I tilfælde af den første pakke bruges det til at angive størrelsen på meddelelsen

Flagene kan være som følger:

  • FirstPacket — den første pakke i meddelelsen
  • NoAsk — meddelelsen kræver ikke aktivering af bekræftelsesmekanismen
  • LastPacket — den sidste pakke i meddelelsen
  • RequestForPacket — bekræftelsespakke eller anmodning om en mistet pakke

Generelle principper for protokollens funktion

Da Reliable UDP er centreret omkring garanteret levering af meddelelser mellem to noder, skal den kunne etablere forbindelse til den anden part. For at etablere forbindelsen sender afsendersiden en pakke med flaget FirstPacket, hvorefter svaret vil indikere, at forbindelsen er etableret. Alle svarpakker, eller med andre ord bekræftelsespakker, angiver altid værdien af PacketNumber-feltet til en mere end den største værdi af PacketNumber blandt de succesfuldt modtagne pakker. I Options-feltet for den første sendte pakke indtastes størrelsen på meddelelsen.

For completing the connection, a similar mechanism is used. In the last message packet, a LastPacket flag is set. In the response packet, the number of the last packet + 1 is indicated, which for the receiving side means successful delivery of the message.
Diagram of connection establishment and termination:Implementering af Reliable Udp protokollen til .Net

When the connection is established, data transmission begins. Data is transmitted in blocks of packets. Each block, except for the last one, contains a fixed number of packets. This number is equal to the size of the reception/transmission window. The last data block may contain fewer packets. After sending each block, the sender waits for an acknowledgment of delivery or a request for retransmission of lost packets, leaving the reception/transmission window open for receiving responses. After receiving the acknowledgment of the delivery of the block, the reception/transmission window shifts and the next block of data is sent.

The receiving side accepts the packets. Each packet is checked against the transmission window. Packets that do not fall within the window and duplicates are discarded. Since the size of the window is strictly fixed and the same for both the receiver and the sender, in the case of a successful delivery of the packet block, the window shifts to receive packets from the next data block, and an acknowledgment of delivery is sent. If the window is not filled within the period set by the working timer, a check will be initiated to determine which packets were not delivered, and requests for retransmission will be sent.
Diagram of retransmission:Implementering af Reliable Udp protokollen til .Net

Timeouts og timere for protokollen

There are several reasons why a connection cannot be established. For example, if the receiving side is offline. In this case, when trying to establish a connection, it will be closed due to a timeout. In the implementation of Reliable UDP, two timers are used for setting timeouts. The first, the working timer, is used to wait for a response from the remote host. If it triggers on the sender side, the last sent packet is retransmitted. If the timer triggers on the receiver side, a check for lost packets is performed, and requests for retransmission are sent.

Den anden timer er nødvendig for at lukke forbindelsen, hvis der ikke er noget signal mellem noderne. For afsenderens side starter den straks efter at arbejds- timeren udløser, og venter på svar fra den fjernværende node. Hvis der ikke er noget svar inden for den fastsatte periode, afsluttes forbindelsen, og ressourcerne frigives. For modtagerens side starter forbindelsens lukketimer efter at arbejds-timeren udløser to gange. Dette er nødvendigt for at forsikre mod tab af bekræftelsespakken. Når timeren udløber, afsluttes forbindelsen også, og ressourcerne frigives.

Tilstandsgraf for transmission af Reliable UDP

Protokollens driftsprincipper er implementeret i en endelig automat, hvor hver tilstand har ansvaret for en bestemt logik ved behandling af pakker.
Z-diagram for Reliable UDP:

Implementering af Reliable Udp protokollen til .Net

Lukket – er faktisk ikke en tilstand, men et start- og slutpunkt for automaten. 'Tilstanden Lukket tager højde for kontrolblokken for transmissionen, som, når den implementerer en asynkron UDP-server, omdirigerer pakker til de relevante forbindelser og starter behandlingen af tilstandene.

FørstePakkesender – starttilstand, hvor den udgående forbindelse befinder sig, når der sendes en besked.

I denne tilstand sendes den første pakke for normale beskeder. For beskeder uden bekræftelse på sending, er dette den eneste tilstand - her sendes hele beskeden.

Sendecyklus – hovedtilstanden for transmission af transmissionspakker.

Overgangen til denne tilstand fra 'sendt' sker efter at den første pakke er sendt. Det er netop til denne tilstand, at alle bekræftelser og anmodninger om genoverførsler kommer. Udgange fra denne tilstand kan ske under to omstændigheder - i tilfælde af en vellykket levering af beskeden eller ved timeout. FørstePakkesender FørstePakkeModtaget

– starttilstand for modtageren af beskeden. Her kontrolleres rigtigheden af begyndelsen af transmissionen, de nødvendige strukturer oprettes, og der sendes en bekræftelse på modtagelsen af den første pakke.

For en besked bestående af en enkelt pakke og sendt uden brug af leveringsbekræftelse - er dette den eneste tilstand. Efter behandlingen af sådanne beskeder lukkes forbindelsen.

Sammenstilling

– hovedtilstand for modtagelse af pakkerne i beskeden. – den grundlæggende tilstand for modtagelse af meddelelsespakker.

Det registrerer pakker i et midlertidigt lager, tjekker for tab af pakker, sender bekræftelser om levering af pakke-blokke og meddelelser i sin helhed, og sender anmodninger om genlevering af tabte pakker. I tilfælde af vellykket modtagelse af hele meddelelsen – går forbindelsen til tilstanden Completed, ellers udføres en timeout-udgang.

Completed – lukning af forbindelsen i tilfælde af vellykket modtagelse af hele meddelelsen.

Denne tilstand er nødvendig for at samle meddelelsen og i tilfælde af at bekræftelsen om meddelelselevering blev tabt undervejs til afsenderen. Udgange fra denne tilstand sker ved timeout, men forbindelsen betragtes som vellykket lukket.

Dybdegående i koden. Kontrollaget for transmission

En af de centrale elementer i Reliable UDP – transmission control block. Opgaven for denne blok er at opbevare nuværende forbindelser og hjælpeelementer, fordele modtagne pakker til de relevante forbindelser, tilbyde et interface til at sende pakker til forbindelsen og implementere protokolens API. Transmission control block modtager pakker fra UDP-niveauet og videresender dem til behandling i en endelig automat. For at modtage pakker er der implementeret en asynkron UDP-server i den.
Nogle medlemmer af klassen ReliableUdpConnectionControlBlock:

internal class ReliableUdpConnectionControlBlock : IDisposable
{
  // byte-array til angivet nøgle. Bruges til at samle indkommende meddelelser    
  public ConcurrentDictionary<Tuple<EndPoint, Int32>, byte[]> IncomingStreams { get; private set;}
  // byte-array til angivet nøgle. Bruges til at sende udgående meddelelser.
  public ConcurrentDictionary<Tuple<EndPoint, Int32>, byte[]> OutcomingStreams { get; private set; }
  // connection record for the specified key.
  private readonly ConcurrentDictionary<Tuple<EndPoint, Int32>, ReliableUdpConnectionRecord> m_listOfHandlers;
  // liste over abonnenter til meddelelser.
  private readonly List<ReliableUdpSubscribeObject> m_subscribers;    
  // lokal sokkel    
  private Socket m_socketIn;
  // port til indkommende meddelelser
  private int m_port;
  // lokal IP-adresse
  private IPAddress m_ipAddress;    
  // lokal slutpunkt    
  public IPEndPoint LocalEndpoint { get; private set; }    
  // samling af forudinitialiserede
  // tilstande for den endelige automat
  public StatesCollection States { get; private set; }
  // tilfældig talgenerator. Bruges til at generere TransmissionId
  private readonly RNGCryptoServiceProvider m_randomCrypto;    	
  //...
}

Implementering af en asynkron UDP-server:

private void Receive()
{
  EndPoint connectedClient = new IPEndPoint(IPAddress.Any, 0);
  // Opretter en ny buffer til hver socket.BeginReceiveFrom 
  byte[] buffer = new byte[DefaultMaxPacketSize + ReliableUdpHeader.Length];
  // Sender bufferen som parameter til den asynkrone metode
  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);
  // Pakken er modtaget, klar til at modtage den næste
  Receive();
  // Da den enkleste måde at løse bufferproblemet på - få en reference til det
  // fra IAsyncResult.AsyncState
  byte[] bytes = ((byte[]) ar.AsyncState).Slice(0, bytesRead);
  // Modtager headeren af pakken
  ReliableUdpHeader header;
  if (!ReliableUdpStateTools.ReadReliableUdpHeader(bytes, out header))
  {          
    // Ugyldig pakke modtaget - kaster den væk
    return;
  }
  // Konstruerer en nøgle til bestemmelse af connection record’en for pakken
  Tuple key = new Tuple(connectedClient, header.TransmissionId);
  // Henter eksisterende connection record eller opretter en ny
  ReliableUdpConnectionRecord record = m_listOfHandlers.GetOrAdd(key, new ReliableUdpConnectionRecord(key, this, header.ReliableUdpMessageType));
  // Starter pakken til behandling i tilstandsmaskinen
  record.State.ReceivePacket(record, header, bytes);
}

For hver meddelelse, der sendes, oprettes en struktur, som indeholder oplysninger om forbindelsen. Denne struktur kaldes connection record.
Nogle medlemmer af klassen ReliableUdpConnectionRecord:

intern klasse ReliableUdpConnectionRecord : IDisposable
{    
  // byte array med besked    
  public byte[] IncomingStream { get; set; }
  // reference til tilstanden af den endelige automaton    
  public ReliableUdpState State { get; set; }    
  // et par, der entydigt definerer connection record
  // i transmissionskontrolblokken     
  public Tuple Key { get; private set;}
  // nedre grænse for modtagevinduet    
  public int WindowLowerBound;
  // størrelse af transmissionsvinduet
  public readonly int WindowSize;     
  // pakkenummer til afsendelse
  public int SndNext;
  // antal pakker til afsendelse
  public int NumberOfPackets;
  // transmissionsnummer (det er den anden del af Tuple)
  // hvert besked har sin egen	
  public readonly Int32 TransmissionId;
  // fjern IP endpoint – modtageren af beskeden
  public readonly IPEndPoint RemoteClient;
  // pakkestørrelse for at undgå fragmentering på IP niveau
  // må ikke overstige MTU – (IP.Header + UDP.Header + ReliableUDP.Header)
  public readonly int BufferSize;
  // transmissionskontrolblok
  public readonly ReliableUdpConnectionControlBlock Tcb;
  // indkapsler resultaterne af den asynkrone operation for BeginSendMessage/EndSendMessage
  public readonly AsyncResultSendMessage AsyncResult;
  // ikke sende bekræftelsespakker
  public bool IsNoAnswerNeeded;
  // det sidste korrekt modtagne pakke (sættes altid til det højest nummer)
  public int RcvCurrent;
  // array med numrene på tabte pakker
  public int[] LostPackets { get; private set; }
  // er den sidste pakke modtaget? Bruges som bool.
  public int IsLastPacketReceived = 0;
  //...
}

Dybdegående i koden. Tilstande

Tilstande implementerer den endelige automaton af Reliable UDP-protokollen, hvor den primære behandling af pakker finder sted. Den abstrakte klasse ReliableUdpState giver et interface til tilstanden:

Implementering af Reliable Udp protokollen til .Net

Al logik for protokolens funktionalitet implementeres af de ovenfor præsenterede klasser, sammen med en hjælpekklasse, der tilbyder statiske metoder, såsom opbygning af headeren til ReliableUdp fra connection record.

Metoderne i interfacet, der definerer de grundlæggende algoritmer for protokolens funktionalitet, vil nu blive gennemgået i detaljer.

Metode DisposeByTimeout

Metoden DisposeByTimeout har ansvaret for at frigøre ressourcerne for forbindelsen efter udløb af timeout og for at signalere om succesfuld/ikke-succesfuld levering af beskeden.
ReliableUdpState.DisposeByTimeout:

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

Den er kun overridden i tilstanden Completed.
Completed.DisposeByTimeout:

beskyttet override void DisposeByTimeout(object record)
{
  ReliableUdpConnectionRecord connectionRecord = (ReliableUdpConnectionRecord) record;
  // bekræfter modtagelse af meddelelse
  SetAsCompleted(connectionRecord);
}

Metode ProcessPackets

Metoden ProcessPackets håndterer den ekstra behandling af en eller flere pakker. Den kaldes direkte, eller via en timeout timer.

I tilstand – hovedtilstand for modtagelse af pakkerne i beskeden. metoden er blevet overskrevet og håndterer kontrollen af tabte pakker og overgangen til tilstand Completed, i tilfælde af modtagelse af den sidste pakke og bestået kontrol
Assembling.ProcessPackets:

public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
  if (connectionRecord.IsDone != 0)
    return;
  if (!ReliableUdpStateTools.CheckForNoPacketLoss(connectionRecord, connectionRecord.IsLastPacketReceived != 0))
  {
    // der er tabte pakker, sender anmodninger om dem
    foreach (int seqNum in connectionRecord.LostPackets)
    {
      if (seqNum != 0)
      {
        ReliableUdpStateTools.SendAskForLostPacket(connectionRecord, seqNum);
      }
    }
    // sætter timeren for anden gang, for at forsøge at sende igen
    if (!connectionRecord.TimerSecondTry)
    {
      connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
      connectionRecord.TimerSecondTry = true;
      return;
    }
    // hvis efter to forsøg med WaitForPacketTimer 
    // vi ikke har fået pakker - starter vi timeren for at lukke forbindelsen
    StartCloseWaitTimer(connectionRecord);
  }
  else if (connectionRecord.IsLastPacketReceived != 0)
  // bestået kontrol 
  {
    // sender bekræftelse på modtagelse af datablok
    ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
    connectionRecord.State = connectionRecord.Tcb.States.Completed;
    connectionRecord.State.ProcessPackets(connectionRecord);
    // i stedet for øjeblikkelig implementering af ressourcer
    // starter vi en timer, i tilfælde af at
    // hvis sidste ack ikke når frem til afsenderen og han anmoder om det igen.
    // ved timerens udløb - implementerer vi ressourcer
    // i tilstanden Completed er timerens metode overskrevet
    StartCloseWaitTimer(connectionRecord);
  }
  // dette er tilfældet, hvor ack for blokken af pakker er gået tabt
  else
  {
    if (!connectionRecord.TimerSecondTry)
    {
      ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
      connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
      connectionRecord.TimerSecondTry = true;
      return;
    }
    // starter timeren for at lukke forbindelsen
    StartCloseWaitTimer(connectionRecord);
  }
}

I tilstand Sendecyklus denne metode kaldes kun ved timeren, og håndterer genudsendelse af den seneste meddelelse, samt aktivering af lukketimer.
SendingCycle.ProcessPackets:

public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
  if (connectionRecord.IsDone != 0)
    return;        
  // sender den seneste pakke igen 
  // (i tilfælde af genskabelse af forbindelsen vil modtagerknuden sende anmodningerne igen, som ikke nåede frem til den)        
  ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.RetransmissionCreateUdpPayload(connectionRecord, connectionRecord.SndNext - 1));
  // aktiverer CloseWait timeren – for at vente på genskabelse af forbindelsen eller dens afslutning
  StartCloseWaitTimer(connectionRecord);
}

I tilstand Completed metoden stopper arbejdstimeren og videresender beskeden til abonnenterne.
Completed.ProcessPackets:

public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
  if (connectionRecord.WaitForPacketsTimer != null)
    connectionRecord.WaitForPacketsTimer.Dispose();
  // samler besked og videresender den til abonnenterne
  ReliableUdpStateTools.CreateMessageFromMemoryStream(connectionRecord);
}

Metode ReceivePacket

I tilstand – starttilstand for modtageren af beskeden. Hovedformålet med metoden er at bestemme, om den første pakke af meddelelsen faktisk er nået til grænsefladen, samt at samle meddelelsen bestående af en enkelt pakke.
FirstPacketReceived.ReceivePacket:

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  if (!header.Flags.HasFlag(ReliableUdpHeaderFlags.FirstPacket))
    return; // vi drop the packet
  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))
    {
      // send acknowledgment packet
      ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
    }
    SetAsCompleted(connectionRecord);
    return;
  }
  // by design all packet numbers start from 0;
  if (header.PacketNumber != 0)
    return;
  ReliableUdpStateTools.InitIncomingBytesStorage(connectionRecord, header);
  ReliableUdpStateTools.WritePacketData(connectionRecord, header, payload);
  // calculate the number of packets that should arrive
  connectionRecord.NumberOfPackets = (int)Math.Ceiling((double)((double)connectionRecord.IncomingStream.Length / (double)connectionRecord.BufferSize));
  // record the number of the last received packet (0)
  connectionRecord.RcvCurrent = header.PacketNumber;
  // then shifted the reception window by 1
  connectionRecord.WindowLowerBound++;
  // switch state
  connectionRecord.State = connectionRecord.Tcb.States.Assembling;
  // if acknowledgment mechanism is not required
  // start a timer that will release all structures
  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);
  }
}

I tilstand Sendecyklus Denne metode er overtaget for at modtage bekræftelser for levering og anmodninger om genoverførsel.
SendingCycle.ReceivePacket:

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  if (connectionRecord.IsDone != 0)
    return;
  if (!header.Flags.HasFlag(ReliableUdpHeaderFlags.RequestForPacket))
    return;
  // beregner den endelige grænse for vinduet
  // grænsen for vinduet + 1 bruges til at få bekræftelser på levering
  int windowHighestBound = Math.Min((connectionRecord.WindowLowerBound + connectionRecord.WindowSize), (connectionRecord.NumberOfPackets));
  // kontrol for at være inden for vinduet
  if (header.PacketNumber  windowHighestBound)
    return;
  connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
  if (connectionRecord.CloseWaitTimer != null)
    connectionRecord.CloseWaitTimer.Change(-1, -1);
  // tjek for den sidste pakke:
  if (header.PacketNumber == connectionRecord.NumberOfPackets)
  {
    // transmissionen er afsluttet
    Interlocked.Increment(ref connectionRecord.IsDone);
    SetAsCompleted(connectionRecord);
    return;
  }
  // dette er et svar på den første pakke med bekræftelse
  if ((header.Flags.HasFlag(ReliableUdpHeaderFlags.FirstPacket) && header.PacketNumber == 1))
  {
    // uden vinduesforskydning
    SendPacket(connectionRecord);
  }
  // der er kommet en bekræftelse på modtagelsen af data blokkene
  else if (header.PacketNumber == windowHighestBound)
  {
    // forskydning af modtage/afsendelsesvinduet
    connectionRecord.WindowLowerBound += connectionRecord.WindowSize;
    // nulstiller transmissions kontrolarrayet
    connectionRecord.WindowControlArray.Nullify();
    // sender blokken af pakker
    SendPacket(connectionRecord);
  }
  // dette er en anmodning om genoverførsel - sender den krævede pakke
  else
    ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.RetransmissionCreateUdpPayload(connectionRecord, header.PacketNumber));
}

I tilstand – hovedtilstand for modtagelse af pakkerne i beskeden. I metoden ReceivePacket udføres det hovedsagelige arbejde med at samle meddelelsen fra de indkommende pakker.
Assembling.ReceivePacket:

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  if (connectionRecord.IsDone != 0)
    return;
  // behandling af pakker uden bekræftelsesmekanisme
  if (header.Flags.HasFlag(ReliableUdpHeaderFlags.NoAsk))
  {
    // nulstil timer
    connectionRecord.CloseWaitTimer.Change(connectionRecord.LongTimerPeriod, -1);
    // gem data
    ReliableUdpStateTools.WritePacketData(connectionRecord, header, payload);
    // hvis vi modtog en pakke med den sidste flag - afslut
    if (header.Flags.HasFlag(ReliableUdpHeaderFlags.LastPacket))
    {
      connectionRecord.State = connectionRecord.Tcb.States.Completed;
      connectionRecord.State.ProcessPackets(connectionRecord);
    }
    return;
  }        
  // beregning af den endelige vinduesgrænse
  int windowHighestBound = Math.Min((connectionRecord.WindowLowerBound + connectionRecord.WindowSize - 1), (connectionRecord.NumberOfPackets - 1));
  // aflæggelse af pakker, der ikke falder inden for vinduet
  if (header.PacketNumber  (windowHighestBound))
    return;
  // aflæggelse af duplikater
  if (connectionRecord.WindowControlArray.Contains(header.PacketNumber))
    return;
  // gem data 
  ReliableUdpStateTools.WritePacketData(connectionRecord, header, payload);
  // øg pakke tæller        
  connectionRecord.PacketCounter++;
  // gem den aktuelle pakke nummer i vindueskontrol arrayet        
  connectionRecord.WindowControlArray[header.PacketNumber - connectionRecord.WindowLowerBound] = header.PacketNumber;
  // sæt den største ankomne pakke        
  if (header.PacketNumber > connectionRecord.RcvCurrent)
    connectionRecord.RcvCurrent = header.PacketNumber;
  // genstart timerne        
  connectionRecord.TimerSecondTry = false;
  connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
  if (connectionRecord.CloseWaitTimer != null)
    connectionRecord.CloseWaitTimer.Change(-1, -1);
  // hvis den sidste pakke er ankommet
  if (header.Flags.HasFlag(ReliableUdpHeaderFlags.LastPacket))
  {
    Interlocked.Increment(ref connectionRecord.IsLastPacketReceived);
  }
  // hvis vi har modtaget alle pakker i vinduet, nulstill tælleren
  // og send en bekræftelsespakke
  else if (connectionRecord.PacketCounter == connectionRecord.WindowSize)
  {
    // nulstill tælleren.      
    connectionRecord.PacketCounter = 0;
    // vi har skubbet sendervindue
    connectionRecord.WindowLowerBound += connectionRecord.WindowSize;
    // nulstilling af overføringskontrol array
    connectionRecord.WindowControlArray.Nullify();
    ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
  }
  // hvis den sidste pakke allerede er modtaget        
  if (Thread.VolatileRead(ref connectionRecord.IsLastPacketReceived) != 0)
  {
    // kontroller pakker          
    ProcessPackets(connectionRecord);
  }
}

I tilstand Completed Den eneste opgave for metoden er at sende en gentaget bekræftelse på den succesfulde levering af meddelelsen.
Completed.ReceivePacket:

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  \/\/ genudsend sidste pakke, fordi
  \/\/ den sidste ack ikke nåede senderen
  if (header.Flags.HasFlag(ReliableUdpHeaderFlags.LastPacket))
  {
    ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
  }
}

Metode SendPacket

I tilstand FørstePakkesender denne metode sender det første datapakke, eller, hvis beskeden ikke kræver leveringsbekræftelse - hele beskeden.
FirstPacketSending.SendPacket:

public override void SendPacket(ReliableUdpConnectionRecord connectionRecord)
{
  connectionRecord.PacketCounter = 0;
  connectionRecord.SndNext = 0;
  connectionRecord.WindowLowerBound = 0;       
  \/\/ hvis bekræftelse ikke er nødvendig - sender vi alle pakker
  \/\/ og frigiver ressourcer
  if (connectionRecord.IsNoAnswerNeeded)
  {
    \/\/ Her sendes As Is
    do
    {
      ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.CreateUdpPayload(connectionRecord, ReliableUdpStateTools.CreateReliableUdpHeader(connectionRecord)));
      connectionRecord.SndNext++;
    } while (connectionRecord.SndNext < connectionRecord.NumberOfPackets);
    SetAsCompleted(connectionRecord);
    return;
  }
  \/\/ vi opretter pakkeheader og sender den 
  ReliableUdpHeader header = ReliableUdpStateTools.CreateReliableUdpHeader(connectionRecord);
  ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.CreateUdpPayload(connectionRecord, header));
  \/\/ øger tælleren
  connectionRecord.SndNext++;
  \/\/ skubber vinduet frem
  connectionRecord.WindowLowerBound++;
  connectionRecord.State = connectionRecord.Tcb.States.SendingCycle;
  \/\/ Starter timeren
  connectionRecord.WaitForPacketsTimer = new Timer(CheckByTimer, connectionRecord, connectionRecord.ShortTimerPeriod, -1);
}

I tilstand Sendecyklus i denne metode sendes en blok af pakker.
SendingCycle.SendPacket:

public override void SendPacket(ReliableUdpConnectionRecord connectionRecord)
{      
  \/\/ sender blokken af pakker      
  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++;
  }
  \/\/ ved store vinduer genstarter vi timeren efter sending
  connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
  if (connectionRecord.CloseWaitTimer != null)
  {
    connectionRecord.CloseWaitTimer.Change(-1, -1);
  }
}

Dybdegående i koden. Oprettelse og etablering af forbindelser

Nu hvor vi har fået kendskab til de grundlæggende tilstande og metoder, der anvendes til håndtering af tilstande, kan vi se lidt nærmere på nogle eksempler på protokollens funktion.
Dataoverførselsdiagram under normale forhold:Implementering af Reliable Udp protokollen til .Net

Lad os se nærmere på oprettelsen connection record For connection and sending the first packet. The initiator of the transfer is always the application that calls the API method for sending the message. Next, the StartTransmission method of the transmission control block is engaged, initiating the data transfer for the new message.
Creating an outgoing connection:

private void StartTransmission(ReliableUdpMessage reliableUdpMessage, EndPoint endPoint, AsyncResultSendMessage asyncResult)
{
  if (m_isListenerStarted == 0)
  {
    if (this.LocalEndpoint == null)
    {
      throw new ArgumentNullException( "", "Du skal bruge en konstruktor med parametre eller starte lytteren før du sender beskeden" );
    }
    // starting the processing of incoming packets
    StartListener(LocalEndpoint);
  }
  // creating a key for the dictionary based on EndPoint and ReliableUdpHeader.TransmissionId
  byte[] transmissionId = new byte[4];
  // creating a random number transmissionId
  m_randomCrypto.GetBytes(transmissionId);
  Tuple key = new Tuple(endPoint, BitConverter.ToInt32(transmissionId, 0));
  // creating a new entry for the connection and checking, 
  // if such a number already exists in our dictionaries
  if (!m_listOfHandlers.TryAdd(key, new ReliableUdpConnectionRecord(key, this, reliableUdpMessage, asyncResult)))
  {
    // if it exists – generate a random number again
    m_randomCrypto.GetBytes(transmissionId);
    key = new Tuple(endPoint, BitConverter.ToInt32(transmissionId, 0));
    if (!m_listOfHandlers.TryAdd(key, new ReliableUdpConnectionRecord(key, this, reliableUdpMessage, asyncResult)))
      // if it fails again – generate an exception
      throw new ArgumentException("Parret TransmissionId & EndPoint findes allerede i ordbogen");
  }
  // initiated the state for processing
  m_listOfHandlers[key].State.SendPacket(m_listOfHandlers[key]);
}

Sending the first packet (state FirstPacketSending):

public override void SendPacket(ReliableUdpConnectionRecord connectionRecord)
{
  connectionRecord.PacketCounter = 0;
  connectionRecord.SndNext = 0;
  connectionRecord.WindowLowerBound = 0;
  // ... 
  // creating the packet header and sending it
  ReliableUdpHeader header = ReliableUdpStateTools.CreateReliableUdpHeader(connectionRecord);
  ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.CreateUdpPayload(connectionRecord, header));
  // increment the counter
  connectionRecord.SndNext++;
  // shift the window
  connectionRecord.WindowLowerBound++;
  // transition to state SendingCycle
  connectionRecord.State = connectionRecord.Tcb.States.SendingCycle;
  // starting the timer
  connectionRecord.WaitForPacketsTimer = new Timer(CheckByTimer, connectionRecord, connectionRecord.ShortTimerPeriod, -1);
}

After sending the first packet, the sender transitions to the state Sendecyklus – waiting for confirmation of packet delivery.
The receiving side, using the EndReceive method, accepts the sent packet, creates a new connection record and passes this packet, with the previously parsed header, to the ReceivePacket processing method. – starttilstand for modtageren af beskeden.
Oprettelse af forbindelse på modtagerens side:

private void EndReceive(IAsyncResult ar)
{
  	// ...
  	// pakken er modtaget
  	// parser headeren på pakken
  ReliableUdpHeader header;
  if (!ReliableUdpStateTools.ReadReliableUdpHeader(bytes, out header))
  {
    	// modtaget en ugyldig pakke - forkast den
    return;
  }
  	// konstruere nøgle for at bestemme connection record’en for pakken
  Tuple key = new Tuple(connectedClient, header.TransmissionId);
  	// få eksisterende connection record eller opret en ny
  ReliableUdpConnectionRecord record = m_listOfHandlers.GetOrAdd(key, new ReliableUdpConnectionRecord(key, this, header.ReliableUdpMessageType));
  	// starter pakken i behandling i den endelige automata
  record.State.ReceivePacket(record, header, bytes);
}

Modtagelse af den første pakke og sending af bekræftelse (tilstand FirstPacketReceived):

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  if (!header.Flags.HasFlag(ReliableUdpHeaderFlags.FirstPacket))
    	// forkast pakken
    return;
  	// ...
  	// ifølge design starter alle packet numbers fra 0;
  if (header.PacketNumber != 0)
    return;
  	// initialiserer array til at gemme dele af meddelelsen
  ReliableUdpStateTools.InitIncomingBytesStorage(connectionRecord, header);
  	// skriver data pakken til array
  ReliableUdpStateTools.WritePacketData(connectionRecord, header, payload);
  	// tæller antallet af pakker, der skal komme
  connectionRecord.NumberOfPackets = (int)Math.Ceiling((double)((double)connectionRecord.IncomingStream.Length / (double)connectionRecord.BufferSize));
  	// skriver nummeret på den sidste modtagne pakke (0)
  connectionRecord.RcvCurrent = header.PacketNumber;
  	// efterfølgende flytter vi modtagelsesvinduet op med 1
  connectionRecord.WindowLowerBound++;
  	// skifter tilstand
  connectionRecord.State = connectionRecord.Tcb.States.Assembling;
  if (/*hvis bekræftelsesmekanisme ikke er påkrævet*/)
  	// ...
  else
  {
    	// sender bekræftelse
    ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
    connectionRecord.WaitForPacketsTimer = new Timer(CheckByTimer, connectionRecord, connectionRecord.ShortTimerPeriod, -1);
  }
}

Dybdegående i koden. Lukning af forbindelse ved timeout

Håndtering af timeouts er en vigtig del af Reliable UDP. Lad os se på et eksempel, hvor der skete et nedbrud på en mellemstation, og datalevering i begge retninger blev umulig.
Diagram over lukning af forbindelse ved timeout:Implementering af Reliable Udp protokollen til .Net

Som det ses i diagrammet, aktiveres arbejdskimmeren hos afsenderen straks efter afsendelse af blokken af pakker. Dette sker i SendPacket-metoden i tilstanden Sendecyklus.
Aktivering af arbejdskimmeren (tilstand SendingCycle):

public override void SendPacket(ReliableUdpConnectionRecord connectionRecord)
{      
  	// sender en blok af pakker   
  	// ...   
  	// genstarter timeren efter afsendelse
  connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
  if (connectionRecord.CloseWaitTimer != null)
    connectionRecord.CloseWaitTimer.Change(-1, -1);
}

Tidsperioderne for timeren fastsættes, når forbindelsen oprettes. Som standard er ShortTimerPeriod 5 sekunder. I dette eksempel er den indstillet til 1,5 sekunder.

For en indgående forbindelse starter timeren, når den sidste modtagne datapakke er kommet, dette sker i metoden ReceivePacket tilstanden. – hovedtilstand for modtagelse af pakkerne i beskeden.
Aktivering af arbejds timeren (tilstand Assembling):

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  // ... 
  // genstarter timerne        
  connectionRecord.TimerSecondTry = false;
  connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
  if (connectionRecord.CloseWaitTimer != null)
    connectionRecord.CloseWaitTimer.Change(-1, -1);
  // ...
}

I den indgående forbindelse kom der ikke flere pakker i løbet af ventetiden for arbejds timeren. Timeren blev udløst og kaldte metoden ProcessPackets, hvori der blev opdaget tabte pakker og der blev første gang sendt anmodninger om genlevering.
Udsendelse af anmodninger om genlevering (tilstand Assembling):

public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
  // ...        
  if (/*kontrol for tabte pakker */)
  {
    // sender anmodninger om genlevering
    // indstiller timeren for anden gang, til genforsøg ved overførsel
    if (!connectionRecord.TimerSecondTry)
    {
      connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
      connectionRecord.TimerSecondTry = true;
      return;
    }
    // hvis det ikke lykkedes at få pakker efter to forsøg på WaitForPacketTimer 
    // starter vi timeren for at afslutte forbindelsen
    StartCloseWaitTimer(connectionRecord);
  }
  else if (/*den sidste pakke er modtaget og succesfuld kontrol */)
  {
    // ...
    StartCloseWaitTimer(connectionRecord);
  }
  // hvis ack på pakkegruppen blev tabt
  else
  { 
    if (!connectionRecord.TimerSecondTry)
    {
      // sender ack igen
      connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
      connectionRecord.TimerSecondTry = true;
      return;
    }
    // starter timeren for at afslutte forbindelsen
    StartCloseWaitTimer(connectionRecord);
  }
}

Variablen TimerSecondTry blev indstillet til true. Denne variabel styrer genstarten af arbejds timeren.

På afsenderens side aktiveres arbejds timeren også, og den sidste sendte pakke sendes igen.
Aktivering af timeren til afbrydelse af forbindelsen (tilstand SendingCycle):

public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
  // ...        
  // sender den sidste pakke igen 
  // ...        
  // aktiverer CloseWait timeren – for at vente på genoprettelse af forbindelsen eller dens afslutning
  StartCloseWaitTimer(connectionRecord);
}

Når en udgående forbindelse er etableret, startes en timer til lukning af forbindelsen.
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);
}

Standard tidsperioden for lukningstimeren er 30 sekunder.

Efter kort tid udløses arbejdstimeren på modtagerens side igen, hvilket resulterer i, at forespørgsler sendes igen, hvorefter lukningstimeren for indgående forbindelse aktiveres.

Når lukningstimerne udløber, frigives alle ressourcer fra begge forbindelsesposter. Afsenderen rapporterer om fejl ved leveringen til den overordnede applikation (se API Reliable UDP).
Frigivelse af ressourcer fra forbindelsesposten:

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

Dybdegående i koden. Gendannelse af datatransmission

Diagram for genoprettelse af dataoverføring ved pakke tab:Implementering af Reliable Udp protokollen til .Net

Som allerede nævnt i forbindelse med lukningen af forbindelsen på timeout, vil der ved udløbet af arbejdstimeren på modtagerens side blive udført en kontrol for tabte pakker. I tilfælde af tab vil der blive oprettet en liste over pakker, der ikke er nået frem til modtageren. Disse nummererede pakker føjes til arrayet LostPackets for den specifikke forbindelse, og der sendes forespørgsler om genlevering.
Afsendelse af forespørgsler om genlevering af pakker (tilstand Assembling):

public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
  //...
  if (!ReliableUdpStateTools.CheckForNoPacketLoss(connectionRecord, connectionRecord.IsLastPacketReceived != 0))
  {
    // der er tabte pakker, så vi sender forespørgsler på dem
    foreach (int seqNum in connectionRecord.LostPackets)
    {
      if (seqNum != 0)
      {
        ReliableUdpStateTools.SendAskForLostPacket(connectionRecord, seqNum);
      }
    }
    // ...
  }
}

Afsenderen vil modtage forespørgslen om genlevering og sende de manglende pakker. Det skal bemærkes, at lukningstimeren allerede er startet hos afsenderen, og at den nulstilles ved modtagelse af forespørgslen.
Genafsendelse af tabte pakker (tilstand SendingCycle):

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  // ...
  connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
  // nulstiller forbindelsens lukketimer 
  if (connectionRecord.CloseWaitTimer != null)
    connectionRecord.CloseWaitTimer.Change(-1, -1);
  // ...
  // dette er en anmodning om retransmission – sender den krævede pakke
  else
    ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.RetransmissionCreateUdpPayload(connectionRecord, header.PacketNumber));
}

Den retransmitterede pakke (packet#3 i diagrammet) modtages af den indkommende forbindelse. Der udføres en kontrol af modtagervinduet, og den normale dataoverførsel genoptages.
Kontrol for at se, om den er i modtagervinduet (tilstand Assembling):

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  // ...
  // øger pakke tælleren
  connectionRecord.PacketCounter++;
  // registrerer det aktuelle pakke nummer i kontrolarrayet
  connectionRecord.WindowControlArray[header.PacketNumber - connectionRecord.WindowLowerBound] = header.PacketNumber;
  // sætter den største modtagne pakke
  if (header.PacketNumber > connectionRecord.RcvCurrent)
    connectionRecord.RcvCurrent = header.PacketNumber;
  // genstarter timerne
  connectionRecord.TimerSecondTry = false;
  connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
  if (connectionRecord.CloseWaitTimer != null)
    connectionRecord.CloseWaitTimer.Change(-1, -1);
  // ...
  // hvis vi har modtaget alle pakker i vinduet, nulstiller tælleren
  // og sender bekræftelsespakken
  else if (connectionRecord.PacketCounter == connectionRecord.WindowSize)
  {
    // nulstiller tælleren.
    connectionRecord.PacketCounter = 0;
    // flyttede transmissionsvinduet
    connectionRecord.WindowLowerBound += connectionRecord.WindowSize;
    // nulstil kontrolarrayet
    connectionRecord.WindowControlArray.Nullify();
    ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
  }
  // ...
}

API Reliable UDP

Der er en offentlig klasse Reliable Udp til interaktion med dataoverførselsprotokollen, som er en wrapper over transmissionskontrolblokken. Her er de vigtigste medlemmer af klassen:

offentlig lukket klasse ReliableUdp : IDisposable
{
  // henter den lokale endepunkt
  offentlig IPEndPoint LocalEndpoint    
  // opretter en instans af ReliableUdp og starter
  // lytter efter indkommende pakker på den angivne IP-adresse
  // og port. Værdi 0 for port betyder brug af
  // dynamisk tildelt port
  offentlig ReliableUdp(IPAddress localAddress, int port = 0) 
  // abonnement på at modtage indkommende meddelelser
  offentlig ReliableUdpSubscribeObject SubscribeOnMessages(ReliableUdpMessageCallback callback, ReliableUdpMessageTypes messageType = ReliableUdpMessageTypes.Any, IPEndPoint ipEndPoint = null)    
  // afmeld modtagelse af meddelelser
  offentlig void Unsubscribe(ReliableUdpSubscribeObject subscribeObject)
  // asynkront sende meddelelse 
  // Bemærk: kompatibilitet med XP og Server 2003 opretholdes, da .NET Framework 4.0 anvendes
  offentlig Task SendMessageAsync(ReliableUdpMessage reliableUdpMessage, IPEndPoint remoteEndPoint, CancellationToken cToken)
  // begynde asynkron sending af meddelelse
  offentlig IAsyncResult BeginSendMessage(ReliableUdpMessage reliableUdpMessage, IPEndPoint remoteEndPoint, AsyncCallback asyncCallback, Object state)
  // få resultat af asynkron sending
  offentlig bool EndSendMessage(IAsyncResult asyncResult)  
  // rydde op i ressourcer
  offentlig void Dispose()    
}

Modtagelse af meddelelser sker ved abonnement. Signaturen for delegaten til callback-metoden:

offentlig delegeret void ReliableUdpMessageCallback( ReliableUdpMessage reliableUdpMessage, IPEndPoint remoteClient );

Meddelelse:

offentlig klasse ReliableUdpMessage
{
  // meddelelsestype, simpel enum
  offentlig ReliableUdpMessageTypes Type { get; privat set; }
  // meddelelsesdata
  offentlig byte[] Body { get; privat set; }
  // hvis sat til true – vil bekræftelsesmekanismen for levering blive deaktiveret
  // for at overføre en specifik meddelelse
  offentlig bool NoAsk { get; privat set; }
}

For at abonnere på en specifik type meddelelser og/eller fra en specifik afsender bruges to valgfri parametre: ReliableUdpMessageTypes messageType og IPEndPoint ipEndPoint.

Meddelelsestyper:

offentlig enum ReliableUdpMessageTypes : kort
{ 
  // Enhver
  Any = 0,
  // Anmodning til STUN server 
  StunRequest = 1,
  // Svar fra STUN server
  StunResponse = 2,
  // Filoverførsel
  FileTransfer =3,
  // ...
}

Sending af meddelelser sker asynkront, til dette er der realiseret en asynkron programmeringsmodel i protokollen:

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

Resultatet af sending af meddelelse vil være true – hvis meddelelsen er nået frem til modtageren, og false – hvis forbindelsen blev lukket på grund af timeout:

offentlig bool EndSendMessage(IAsyncResult asyncResult)

Konklusion

Meget er ikke blevet beskrevet i denne artikel. Mekanismer til strømforhandling, håndtering af undtagelser og fejl, implementering af asynkrone metoder til at sende beskeder. Men kernen af protokollen, beskrivelsen af logikken for pakkehåndtering, oprettelsen af forbindelser og håndtering af timeouts, bør blive tydelig for dig.

Den demonstration, der er præsenteret af protokollen for pålidelig levering, er tilstrækkelig robust og fleksibel og opfylder tidligere bestemte krav. Men jeg vil gerne tilføje, at den beskrevne implementering kan forbedres. For eksempel, for at øge båndbredden og dynamisk ændre timerperioder i protokollen, kan mekanismer som sliding window og RTT tilføjes; det vil også være nyttigt at implementere en mekanisme til at bestemme MTU mellem forbindelsespunkterne (men kun i tilfælde af at sende store meddelelser).

Tak for din opmærksomhed, jeg ser frem til dine kommentarer og bemærkninger.

P.S. For dem, der er interesseret i detaljerne, eller bare ønsker at teste protokollen, er her et link til projektet på GitHube:
Projektet Reliable UDP

Nyttige links og artikler

  1. Specifikation af TCP-protokollen: på engelsk og på russisk
  2. Specifikation af UDP-protokollen: på engelsk og på russisk
  3. Diskussion af RUDP-protokollen: draft-ietf-sigtran-reliable-udp-00
  4. Reliable Data Protocol: rfc 908 og rfc 1151
  5. En simpel implementering af leveringsbekræftelse via UDP: Tag total kontrol over dit netværk med .NET og UDP
  6. Artikel, der beskriver mekanismer til at omgå NAT'er: Peer-to-Peer kommunikation over netværksadresseoversættere
  7. Implementering af asynkron programmeringsmodel: Implementering af CLR Asynchronous Programming Model og Hvordan man implementerer designmønstret IAsyncResult
  8. Overførsel af den asynkrone programmeringsmodel til en asynkron skabelon baseret på opgaver (APM til TAP):
    TPL og traditionel .NET asynkron programmering
    Interop med andre asynkrone mønstre og typer

Opdatering: Tak mayorovp og sidristij for ideen om at tilføje en opgave til grænsefladen. Bibliotekets kompatibilitet med ældre styresystemer forbliver intakt, da .NET Framework 4 understøtter både XP og 2003 server.

Kilde: habr.com

Køb pålidelig webhosting med DDoS-beskyttelse, VPS VDS-servere 🔥 Køb pålidelig webhosting med DDoS-beskyttelse, VPS VDS-servere | ProHoster