Implementering av Reliable UDP-protokollet för .Net

Internet har förĂ€ndrats mycket. En av de grundlĂ€ggande protokollen för Internet – UDP, anvĂ€nds av applikationer inte bara för att leverera datagram och sĂ€nda till flera mottagare, utan ocksĂ„ för att möjliggöra "peer-to-peer"-anslutningar mellan nĂ€tverksnoder. PĂ„ grund av sin enkla struktur har detta protokoll fĂ„tt mĂ„nga oanade anvĂ€ndningsomrĂ„den, Ă€ven om protokollets nackdelar, sĂ„som bristen pĂ„ garanterad leverans, fortfarande kvarstĂ„r. Denna artikel beskriver implementeringen av ett protokoll för garanterad leverans ovanpĂ„ UDP.
InnehÄll:Inledning
Krav pÄ protokollet
Rubrik för Reliable UDP
AllmÀnna principer för protokollets funktion
Timeouts och protokolltimer
TillstÄndsdiagram för Reliable UDP-överföring
Djupare in i koden. Kontrollblock för överföring
Djupare in i koden. TillstÄnd

Djupare in i koden. Skapande och upprÀttande av anslutningar
Djupare in i koden. StÀngning av anslutningen vid timeout
Djupare in i koden. ÅterstĂ€llning av datatransmission
API för Reliable UDP
Slutsats
Nyttiga lÀnkar och artiklar

Inledning

Den ursprungliga arkitekturen för Internet förutsatte en homogen adresseringsstruktur, dĂ€r varje nod hade en global och unik IP-adress, och kunde kommunicera direkt med andra noder. Idag har Internet i praktiken en annan arkitektur – ett omrĂ„de för globala IP-adresser och mĂ„nga omrĂ„den med privata adresser, dolda bakom NAT-enheter.I en sĂ„dan arkitektur kan endast enheter som finns i det globala adressutrymmet enkelt interagera med andra i nĂ€tverket, eftersom de har en unik, globalt routbar IP-adress. En nod som befinner sig i ett privat nĂ€tverk kan ansluta till andra noder i samma nĂ€tverk samt ansluta till andra, vĂ€lkĂ€nda noder i det globala adressutrymmet. Denna interaktion uppnĂ„s delvis genom mekanismen för översĂ€ttning av nĂ€tverksadresser. NAT-enheter, till exempel Wi-Fi-routrar, skapar speciella poster i översĂ€ttningstabeller för utgĂ„ende anslutningar och modifierar IP-adresserna och portnumren i paketen. Detta möjliggör att upprĂ€tta en utgĂ„ende anslutning frĂ„n ett privat nĂ€tverk till noder i det globala adressutrymmet. Men samtidigt blockerar NAT-enheter vanligtvis all inkommande trafik om det inte finns speciella regler för inkommande anslutningar.

Den hÀr arkitekturen för Internet Àr ganska korrekt för klient-server-interaktion, dÀr klienterna kan befinna sig i privata nÀtverk medan servrar har globala adresser. Men den skapar svÄrigheter för direktanslutning mellan tvÄ noder i olika privata nÀtverk. olika privata nÀtverk. Direktanslutning mellan tvÄ noder Àr viktigt för "peer-to-peer"-applikationer, sÄsom röstsnack (Skype), fjÀrrÄtkomst till datorer (TeamViewer) eller onlinespel.

En av de mest effektiva metoderna för att etablera en peer-to-peer-anslutning mellan enheter i olika privata nÀtverk kallas "hole punching". Denna teknik anvÀnds oftast med applikationer baserade pÄ UDP-protokollet.

Men om din applikation krÀver garanterad dataleverans, till exempel om du överför filer mellan datorer, kommer det att uppstÄ mÄnga problem med att anvÀnda UDP, eftersom UDP inte Àr ett protokoll för garanterad leverans och inte sÀkerstÀller leverans av paket i ordning, till skillnad frÄn TCP-protokollet.

I sÄdana fall, för att sÀkerstÀlla garanterad paketsleverans, krÀvs det att implementera ett protokoll pÄ applikationsnivÄ som tillhandahÄller den nödvÀndiga funktionaliteten och gÄr ovanpÄ UDP.

Först och frÀmst vill jag pÄpeka att det finns en teknik för TCP hole punching, för att etablera TCP-anslutningar mellan noder i olika privata nÀtverk, men pÄ grund av bristen pÄ stöd frÄn mÄnga NAT-enheter betraktas den vanligtvis inte som det primÀra sÀttet att ansluta sÄdana noder.

I denna artikel kommer jag att fokusera enbart pÄ implementeringen av protokollet för pÄlitlig leverans. Implementeringen av tekniken för UDP hole punching kommer att beskrivas i kommande artiklar.

Krav pÄ protokollet

  1. PÄlitlig leverans av paket genom en mekanism för positiv bekrÀftelse (sÄ kallad positive acknowledgment)
  2. Behovet av effektiv överföring av stora datamÀngder, dvs. protokollet mÄste undvika onödiga retransmissioner av paket
  3. Det ska finnas möjlighet att avbryta bekrÀftelsemekanismen (möjlighet att fungera som ett "rent" UDP-protokoll)
  4. Möjlighet till implementering av kommandolÀge, med bekrÀftelse av varje meddelande
  5. Den grundlÀggande enheten för datatransfer genom protokollet ska vara ett meddelande

Dessa krav överensstÀmmer till stor del med kraven för Reliable Data Protocol, som beskrivs i rfc 908 och rfc 1151, och jag har baserat mig pÄ dessa standarder vid utvecklingen av detta protokoll.

För att förstÄ dessa krav, lÄt oss titta pÄ tidsdiagram över datatransfer mellan tvÄ nÀtverksnoder enligt protokollen TCP och UDP. LÄt oss anta att vi i bÄda fallen har förlorat ett paket.
Överföring av icke-interaktiva data via TCP:Implementering av Reliable UDP-protokollet för .Net

Som framgÄr av diagrammet, vid paketförlust, kommer TCP att upptÀcka den förlorade paketet och informera avsÀndaren, och begÀra numret pÄ det förlorade segmentet.
Överföring av data med UDP-protokollet:Implementering av Reliable UDP-protokollet för .Net

UDP vidtar inga ÄtgÀrder för att upptÀcka förluster. Felkontroll av överföringen i UDP-protokollet Àr helt upp till applikationen.

FelupptÀckning i TCP-protokollet uppnÄs genom att etablera en anslutning med den slutliga noden, bevara tillstÄndet för denna anslutning, ange nummer av skickade byte i varje pakets rubrik, samt meddelanden om mottagande med hjÀlp av bekrÀftelsenummer "acknowledge number".

Dessutom, för att öka prestandan (dvs. skicka mer Ă€n ett segment utan att fĂ„ bekrĂ€ftelse) anvĂ€nder TCP-protokollet sĂ„ kallat överföringsfönster — antalet byte data som avsĂ€ndaren av segmentet förvĂ€ntar sig att ta emot.

Mer information om TCP-protokollet kan hittas i rfc 793, med UDP i rfc 768, dÀr de faktiskt definieras.

FrÄn det ovanstÄende Àr det tydligt att för att skapa ett pÄlitligt meddelandeleveransprotokoll över UDP (hÀdanefter kallat Reliable UDP), Àr det nödvÀndigt att implementera liknande mekanismer för dataöverföring som TCP. Det handlar om att:

  • bevara anslutningstillstĂ„nd
  • anvĂ€nda segmentnumrering
  • anvĂ€nda speciella bekrĂ€ftelsepaket
  • anvĂ€nda en förenklad fönstermekanism för att öka protokollets genomströmning

Dessutom krÀvs:

  • att signalera början av meddelandet för att avsĂ€tta resurser för anslutningen
  • att signalera slutet av meddelandet för att överföra det mottagna meddelandet till applikationen ovanför och frigöra protokollets resurser
  • att proktollet för specifika anslutningar ska kunna stĂ€nga av bekrĂ€ftelsemekanismen för leverans sĂ„ att det fungerar som "rent" UDP

Rubrik för Reliable UDP

Kom ihÄg att en UDP-datagram kapslas i en IP-datagram. Reliable UDP-paketet 'slÄs' dÀrmed in i en UDP-datagram.
Kapsling av Reliable UDP-headern:Implementering av Reliable UDP-protokollet för .Net

Strukturen pÄ Reliable UDP-headern Àr ganska enkel:

Implementering av Reliable UDP-protokollet för .Net

  • Flags – kontrollerande flaggor för paketet
  • MessageType – meddelandetyp, anvĂ€nds av applikationer ovanför, för att prenumerera pĂ„ specifika meddelanden
  • TransmissionId — överföringsnummer, tillsammans med adress och port för mottagaren, unikt identifierar anslutningen
  • PacketNumber – paketsnummer
  • Options – ytterligare alternativ för protokollet. I fallet med det första paketet anvĂ€nds den för att ange meddelandestorleken

Flaggor kan vara följande:

  • FirstPacket — det första paketet av meddelandet
  • NoAsk — meddelandet krĂ€ver inte att bekrĂ€ftelsemekanismen aktiveras
  • LastPacket — det sista paketet av meddelandet
  • RequestForPacket — bekrĂ€ftelsepaket eller begĂ€ran om förlorat paket

AllmÀnna principer för protokollets funktion

Eftersom Reliable UDP Àr inriktad pÄ garanterad överföring av meddelandet mellan tvÄ noder, mÄste det kunna etablera en anslutning med den andra sidan. För att etablera en anslutning skickar avsÀndarsidan ett paket med flaggan FirstPacket, svaret pÄ detta kommer att indikera att anslutningen har upprÀttats. Alla svarspaket, eller Ä sin sida, bekrÀftelsepaket, sÀtter alltid vÀrdet pÄ PacketNumber-fÀltet till ett mer Àn det största vÀrdet av PacketNumber för framgÄngsrikt mottagna paket. I Options-fÀltet för det första skickade paketet skrivs meddelandestorleken.

För att slutföra anslutningen anvÀnds en liknande mekanism. I det sista datablocket sÀtts flaggan LastPacket. I det svarande paketet anges numret pÄ det sista paketet + 1, vilket för mottagarsidan innebÀr att meddelandet har levererats framgÄngsrikt.
Diagram för att etablera och avsluta anslutning:Implementering av Reliable UDP-protokollet för .Net

NÀr anslutningen har etablerats börjar dataöverföringen. Data överförs i paketblock. Varje block, förutom det sista, innehÄller ett fast antal paket. Detta antal motsvarar storleken pÄ mottagning/överföringens fönster. Det sista datablocket kan innehÄlla ett mindre antal paket. Efter att varje block har skickats vÀntar avsÀndarsidan pÄ en bekrÀftelse pÄ leverans eller en begÀran om att skicka om förlorade paket, medan fönstret för mottagning/överföring hÄlls öppet för att ta emot svar. Efter att bekrÀftelsen pÄ leverans av blocket har mottagits, flyttas fönstret för mottagning/överföring och nÀsta datablok skickas.

Mottagarsidan tar emot paketen. Varje paket kontrolleras för att se om det passar in i överföringsfönstret. Paket som inte passar in i fönstret och dubbletter sorteras bort. Eftersom storleken pÄ fönstret Àr strikt fast och identisk för bÄde mottagaren och avsÀndaren, flyttas fönstret för att ta emot paketen frÄn nÀsta datablok och ett bekrÀftelsepaket skickas. Om fönstret inte Àr fyllt inom den tid som har stÀllts in av arbetstimeren, pÄbörjas en kontroll av vilka paket som inte har levererats och begÀrningar om att skicka om de förlorade paketen skickas.
Diagram för omöverföring:Implementering av Reliable UDP-protokollet för .Net

Timeouts och protokolltimer

Det finns flera anledningar till varför en anslutning kanske inte kan upprÀttas. Till exempel, om mottagarsidan Àr offline. I sÄ fall kommer anslutningen att stÀngas pÄ grund av timeout vid försök att upprÀtta anslutningen. I implementeringen av Reliable UDP anvÀnds tvÄ timers för att stÀlla in tidsgrÀnser. Den första, arbets timern, anvÀnds för att vÀnta pÄ svar frÄn den fjÀrranslutna vÀrden. Om den aktiveras pÄ avsÀndarsidan, skickas det senaste skickade paketet om. Om timern aktiveras pÄ mottagarens sida, kontrolleras det för förlorade paket och begÀrningar om att skicka om dem skickas.

Den andra timern Àr nödvÀndig för att stÀnga anslutningen om det inte finns nÄgon kommunikation mellan noderna. För avsÀndarens sida startar den omedelbart efter att arbets-timern aktiverats och vÀntar pÄ svar frÄn den avlÀgsna noden. Om inget svar ges inom den angivna perioden stÀngs anslutningen och resurserna frigörs. För mottagarens sida aktiveras stÀngnings-timern efter dubbel aktivering av arbets-timern. Detta Àr nödvÀndigt för att skydda mot förlust av bekrÀftelsepaket. NÀr timern aktiveras avslutas ocksÄ anslutningen och resurserna frigörs.

TillstÄndsdiagram för Reliable UDP-överföring

Protokollets arbetsprinciper Àr implementerade i en Àndlig automat, dÀr varje tillstÄnd ansvarar för en viss logik för paketbearbetning.
TillstÄndsdiagram för Reliable UDP:

Implementering av Reliable UDP-protokollet för .Net

StÀngd Àr faktiskt inte ett tillstÄnd, det Àr start- och slutpunkten för automaten. TillstÄndet StÀngd betraktas som styrblock för överföringen, som genom att implementera en asynkron UDP-server vidarebefordrar paket till motsvarande anslutningar och startar tillstÄndshanteringen.

FirstPacketSending Àr det initiala tillstÄndet för den utgÄende anslutningen nÀr meddelandet skickas.

I detta tillstĂ„nd skickas det första paketet för vanliga meddelanden. För meddelanden utan bekrĂ€ftelse pĂ„ leverans Ă€r detta det enda tillstĂ„ndet – i det sker hela meddelandets utskick.

SendingCycle Àr det huvudsakliga tillstÄndet för överföring av meddelandepaket.

ÖvergĂ„ngen till detta tillstĂ„nd frĂ„n tillstĂ„ndet FirstPacketSending sker efter att det första meddelandepaketet har skickats. Det Ă€r i detta tillstĂ„nd som alla bekrĂ€ftelser och begĂ€rningar om omöverföringar kommer. UtgĂ„ngen frĂ„n detta tillstĂ„nd Ă€r möjlig i tvĂ„ fall – vid framgĂ„ngsrik leverans av meddelandet eller vid tidsgrĂ€ns.

FirstPacketReceived Àr det initiala tillstÄndet för mottagaren av meddelandet.

I detta tillstÄnd kontrolleras korrektheten av överföringsstarten, nödvÀndiga strukturer skapas och en bekrÀftelse pÄ mottagandet av det första paketet skickas.

För ett meddelande som bestĂ„r av ett enda paket och skickas utan att anvĂ€nda leveransbekrĂ€ftelse – Ă€r detta det enda tillstĂ„ndet. Efter att detta meddelande har hanterats stĂ€ngs anslutningen.

Assembling Àr det huvudsakliga tillstÄndet för mottagning av meddelandepaket.

I den registreras paket i ett temporÀrt lac, kontrolleras för att sÀkerstÀlla att det inte finns nÄgra paketförluster, bekrÀftelser om leverans av paketblock och hela meddelanden skickas, samt förfrÄgningar om att skicka om förlorade paket. Vid framgÄngsrik mottagning av hela meddelandet gÄr anslutningen till staten Slutförd, annars görs en timeout-utgÄng.

Slutförd – stĂ€ngning av anslutningen vid framgĂ„ngsrik mottagning av hela meddelandet.

Detta tillstÄnd Àr nödvÀndigt för att samla meddelandet och för situationen nÀr bekrÀftelsen av meddelandets leverans har gÄtt förlorad pÄ vÀg till avsÀndaren. UtgÄng frÄn detta tillstÄnd sker via timeout, men anslutningen anses vara framgÄngsrikt stÀngd.

Djupare in i koden. Kontrollblock för överföring

En av de viktigaste komponenterna i Reliable UDP Ă€r överföringskontrollblocket. Uppgiften för detta block Ă€r att lagra aktuella anslutningar och hjĂ€lpelement, fördela inkommande paket till motsvarande anslutningar, tillhandahĂ„lla grĂ€nssnitt för att skicka paket till anslutningen och implementera protokollets API. Överföringskontrollblocket tar emot paket frĂ„n UDP-nivĂ„n och vidarebefordrar dem för behandling till en Ă€ndlig automat. För att ta emot paket har den en asynkron UDP-server implementerad.
NÄgra medlemmar i klassen ReliableUdpConnectionControlBlock:

internal class ReliableUdpConnectionControlBlock : IDisposable
{
  // bytearray för angiven nyckel. AnvÀnds för att sammanstÀlla inkommande meddelanden
  public ConcurrentDictionary<Tuple, byte[]> IncomingStreams { get; private set;}
  // bytearray för angiven nyckel. AnvÀnds för att skicka utgÄende meddelanden.
  public ConcurrentDictionary<Tuple, byte[]> OutcomingStreams { get; private set; }
  // anslutningspost för angiven nyckel.
  private readonly ConcurrentDictionary<Tuple, ReliableUdpConnectionRecord> m_listOfHandlers;
  // lista över prenumeranter pÄ meddelanden.
  private readonly List m_subscribers;
  // lokal socket
  private Socket m_socketIn;
  // port för inkommande meddelanden
  private int m_port;
  // lokal IP-adress
  private IPAddress m_ipAddress;
  // lokal slutpunkt
  public IPEndPoint LocalEndpoint { get; private set; }
  // samling av förhandsinitialiserade
  // tillstÄnd för den Àndliga automaten
  public StatesCollection States { get; private set; }
  // slumptalsgenerator. AnvÀnds för att skapa TransmissionId
  private readonly RNGCryptoServiceProvider m_randomCrypto;
  	//...
}

Implementering av en asynkron UDP-server:

private void Receive()
{
  EndPoint connectedClient = new IPEndPoint(IPAddress.Any, 0);
  // skapar en ny buffer för varje socket.BeginReceiveFrom 
  byte[] buffer = new byte[DefaultMaxPacketSize + ReliableUdpHeader.Length];
  // överför bufferten som parameter till den asynkrona metoden
  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);
  // paketet har mottagits, redo att ta emot nÀsta        
  Receive();
  // eftersom det enklaste sÀttet att hantera bufferten Àr att fÄ en referens till den 
  // frÄn IAsyncResult.AsyncState        
  byte[] bytes = ((byte[]) ar.AsyncState).Slice(0, bytesRead);
  // hÀmta paketets header        
  ReliableUdpHeader header;
  if (!ReliableUdpStateTools.ReadReliableUdpHeader(bytes, out header))
  {          
    // felaktigt paket mottaget - avvisar det
    return;
  }
  // konstruera nyckeln för att identifiera connection record för paketet
  Tuple key = new Tuple(connectedClient, header.TransmissionId);
  // hÀmta befintlig connection record eller skapa en ny
  ReliableUdpConnectionRecord record = m_listOfHandlers.GetOrAdd(key, new ReliableUdpConnectionRecord(key, this, header.ReliableUdpMessageType));
  // starta paketet i automatens behandling
  record.State.ReceivePacket(record, header, bytes);
}

För varje meddelandetransfer skapas en struktur som innehÄller information om anslutningen. SÄdan struktur kallas connection record.
NÄgra medlemmar av klassen ReliableUdpConnectionRecord:

intern klass ReliableUdpConnectionRecord : IDisposable
{    
  // byte-array med meddelande    
  public byte[] IncomingStream { get; set; }
  // referens till tillstÄndet för den slutna automaten    
  public ReliableUdpState State { get; set; }    
  // par som entydigt identifierar connection record
  // i överföringskontrollblocket     
  public Tuple Key { get; private set;}
  // nedre grÀns för mottagningsfönstret    
  public int WindowLowerBound;
  // storlek pÄ överföringsfönstret
  public readonly int WindowSize;     
  // paketnummer för att skicka
  public int SndNext;
  // antal paket som ska skickas
  public int NumberOfPackets;
  // överföringsnummer (detta Àr den andra delen av Tuple)
  // för varje meddelande sin	
  public readonly Int32 TransmissionId;
  // avlĂ€gsen IP-endpoint – sjĂ€lva mottagaren av meddelandet
  public readonly IPEndPoint RemoteClient;
  // storlek pÄ paketet för att undvika fragmentering pÄ IP-nivÄ
  // fĂ„r inte överskrida MTU – (IP.Header + UDP.Header + RelaibleUDP.Header)
  public readonly int BufferSize;
  // block för överföringskontroll
  public readonly ReliableUdpConnectionControlBlock Tcb;
  // inkapslar resultaten av den asynkrona operationen för BeginSendMessage/EndSendMessage
  public readonly AsyncResultSendMessage AsyncResult;
  // inte skicka bekrÀftelsepaket
  public bool IsNoAnswerNeeded;
  // senaste korrekt mottagna paket (alltid instÀllt pÄ det största numret)
  public int RcvCurrent;
  // array med nummer för förlorade paket
  public int[] LostPackets { get; private set; }
  // kom det senaste paketet? AnvÀnds som bool.
  public int IsLastPacketReceived = 0;
  //...
}

Djupare in i koden. TillstÄnd

TillstÄnd implementerar den slutna automaten för protokollet Reliable UDP, dÀr huvudarbetet med paketen sker. Den abstrakta klassen ReliableUdpState tillhandahÄller ett grÀnssnitt för tillstÄndet:

Implementering av Reliable UDP-protokollet för .Net

All logik för protokollets funktionalitet implementeras av de ovan presenterade klasserna, tillsammans med en hjÀlparklass som tillhandahÄller statiska metoder, sÄsom exempelvis konstruktion av ReliableUdp-huvudet frÄn connection record.

Nedan kommer vi att gÄ igenom detaljerna i implementeringen av grÀnssnittets metoder som definierar protokollets huvudalgoritmer.

Metod DisposeByTimeout

Metoden DisposeByTimeout ansvarar för att frigöra anslutningsresurser vid timeout och för att signalera om ett meddelande har levererats framgÄngsrikt/inte framgÄngsrikt.
ReliableUdpState.DisposeByTimeout:

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

Det Àr Äsidosatt endast i tillstÄndet Slutförd.
Completed.DisposeByTimeout:

protected override void DisposeByTimeout(object record)
{
  ReliableUdpConnectionRecord connectionRecord = (ReliableUdpConnectionRecord) record;
  // meddelar om framgÄngsrik mottagning av meddelande
  SetAsCompleted(connectionRecord);        
}

Metod ProcessPackets

Metoden ProcessPackets ansvarar för ytterligare behandling av ett eller flera paket. Den anropas direkt eller via en paketfördröjningstimer.

I tillstÄnd Assembling metoden Àr överskriven och ansvarar för att kontrollera förlorade paket och byta till tillstÄnd Slutförd, vid mottagandet av det sista paketet och framgÄngsrik verifiering
Assembling.ProcessPackets:

public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
  if (connectionRecord.IsDone != 0)
    return;
  if (!ReliableUdpStateTools.CheckForNoPacketLoss(connectionRecord, connectionRecord.IsLastPacketReceived != 0))
  {
    // det finns förlorade paket, begÀr dem
    foreach (int seqNum in connectionRecord.LostPackets)
    {
      if (seqNum != 0)
      {
        ReliableUdpStateTools.SendAskForLostPacket(connectionRecord, seqNum);
      }
    }
    // stÀller in en timer för andra försöket att skicka
    if (!connectionRecord.TimerSecondTry)
    {
      connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
      connectionRecord.TimerSecondTry = true;
      return;
    }
    // om efter tvÄ försök med WaitForPacketTimer 
    // inte kan fÄ paketen - startar stÀngningstimer
    StartCloseWaitTimer(connectionRecord);
  }
  else if (connectionRecord.IsLastPacketReceived != 0)
  // framgÄngsrik verifiering 
  {
    // skickar bekrÀftelse pÄ mottagande av datablok
    ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
    connectionRecord.State = connectionRecord.Tcb.States.Completed;
    connectionRecord.State.ProcessPackets(connectionRecord);
    // istÀllet för omedelbar implementering av resurser
    // startar vi en timer, ifall
    // ifall sista ack inte nÄr avsÀndaren och denne begÀr det igen.
    // pÄ timerutlösning - implementerar vi resurser
    // i staten Completed Àr timer metoden överskriven
    StartCloseWaitTimer(connectionRecord);
  }
  // detta Àr fallet nÀr ack för databloken har förlorats
  else
  {
    if (!connectionRecord.TimerSecondTry)
    {
      ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
      connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
      connectionRecord.TimerSecondTry = true;
      return;
    }
    // startar stÀngningstimer
    StartCloseWaitTimer(connectionRecord);
  }
}

I tillstÄnd SendingCycle denna metod anropas endast via timern och ansvarar för att Äter skicka det senaste meddelandet samt aktivera stÀngningstimern.
SendingCycle.ProcessPackets:

public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
  if (connectionRecord.IsDone != 0)
    return;        
  // skickar om sista paketet 
  // (vid ÄterstÀllande av anslutning kommer mottagarens nod att skicka nya förfrÄgningar för de som inte nÄtt fram)        
  ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.RetransmissionCreateUdpPayload(connectionRecord, connectionRecord.SndNext - 1));
  // aktiverar CloseWait-timern – för att vĂ€nta pĂ„ Ă„terstĂ€llande av anslutning eller dess avslutande
  StartCloseWaitTimer(connectionRecord);
}

I tillstÄnd Slutförd metoden stoppar arbets timern och skickar ett meddelande till prenumeranter.
Completed.ProcessPackets:

public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
  if (connectionRecord.WaitForPacketsTimer != null)
    connectionRecord.WaitForPacketsTimer.Dispose();
  // samlar meddelandet och skickar det till prenumeranter
  ReliableUdpStateTools.CreateMessageFromMemoryStream(connectionRecord);
}

Metod ReceivePacket

I tillstÄnd FirstPacketReceived den huvudsakliga uppgiften för metoden Àr att avgöra om det första paketet av meddelandet verkligen kom till grÀnssnittet, samt att samla ett meddelande bestÄende av ett enda paket.
FirstPacketReceived.ReceivePacket:

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  if (!header.Flags.HasFlag(ReliableUdpHeaderFlags.FirstPacket))
    // ignorera paketet
    return;
  // kombinationen av tvÄ flaggor - FirstPacket och LastPacket - indikerar att vi har ett enda meddelande
  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))
    {
      // skickar bekrÀftelsepaket
      ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
    }
    SetAsCompleted(connectionRecord);
    return;
  }
  // enligt design börjar alla paketnummer med 0;
  if (header.PacketNumber != 0)
    return;
  ReliableUdpStateTools.InitIncomingBytesStorage(connectionRecord, header);
  ReliableUdpStateTools.WritePacketData(connectionRecord, header, payload);
  // rÀknar antalet paket som förvÀntas komma
  connectionRecord.NumberOfPackets = (int)Math.Ceiling((double)((double)connectionRecord.IncomingStream.Length / (double)connectionRecord.BufferSize));
  // registrera det senaste mottagna paketnumret (0)
  connectionRecord.RcvCurrent = header.PacketNumber;
  // flyttar sedan mottagningens fönster med 1
  connectionRecord.WindowLowerBound++;
  // vÀxlar tillstÄndet
  connectionRecord.State = connectionRecord.Tcb.States.Assembling;
  // om ingen bekrÀftelsemekanism krÀvs
  // starta en timer som frigör alla strukturer
  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 tillstÄnd SendingCycle denna metod Àr överskriven för att ta emot bekrÀftelser om leverans och begÀrningar om omöverföring.
SendingCycle.ReceivePacket:

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  if (connectionRecord.IsDone != 0)
    return;
  if (!header.Flags.HasFlag(ReliableUdpHeaderFlags.RequestForPacket))
    return;
  // berÀkning av slutgrÀnsen för fönstret
  // grÀnsen för fönstret + 1, för att fÄ leveransbekrÀftelse
  int windowHighestBound = Math.Min((connectionRecord.WindowLowerBound + connectionRecord.WindowSize), (connectionRecord.NumberOfPackets));
  // kontrollera om det ligger inom fönstret        
  if (header.PacketNumber  windowHighestBound)
    return;
  connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
  if (connectionRecord.CloseWaitTimer != null)
    connectionRecord.CloseWaitTimer.Change(-1, -1);
  // kontrollera sista paketet:
  if (header.PacketNumber == connectionRecord.NumberOfPackets)
  {
    // överföringen Àr klar
    Interlocked.Increment(ref connectionRecord.IsDone);
    SetAsCompleted(connectionRecord);
    return;
  }
  // detta Àr svar pÄ första paketet med bekrÀftelse         
  if ((header.Flags.HasFlag(ReliableUdpHeaderFlags.FirstPacket) && header.PacketNumber == 1))
  {
    // utan skiftning av fönstret
    SendPacket(connectionRecord);
  }
  // bekrÀftelse pÄ mottagning av datablok
  else if (header.PacketNumber == windowHighestBound)
  {
    // skiftar fönstret för mottagning/överföring
    connectionRecord.WindowLowerBound += connectionRecord.WindowSize;
    // ÄterstÀll kontrollarrayen för överföring
    connectionRecord.WindowControlArray.Nullify();
    // skicka paketblock
    SendPacket(connectionRecord);
  }
  // detta Ă€r en begĂ€ran om Ă„teröverföring – skickar det begĂ€rda paketet          
  else
    ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.RetransmissionCreateUdpPayload(connectionRecord, header.PacketNumber));
}

I tillstÄnd Assembling I metoden ReceivePacket sker det huvudsakliga arbetet med att sammanstÀlla meddelandet frÄn de inkommande paketen.
SammanstÀllning.ReceivePacket:

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  if (connectionRecord.IsDone != 0)
    return;
  // behandling av paket med inaktiv bekrÀftelsemekanism
  if (header.Flags.HasFlag(ReliableUdpHeaderFlags.NoAsk))
  {
    // ÄterstÀll timer
    connectionRecord.CloseWaitTimer.Change(connectionRecord.LongTimerPeriod, -1);
    // skriv data
    ReliableUdpStateTools.WritePacketData(connectionRecord, header, payload);
    // om vi har fÄtt paket med sista flaggan - avsluta
    if (header.Flags.HasFlag(ReliableUdpHeaderFlags.LastPacket))
    {
      connectionRecord.State = connectionRecord.Tcb.States.Completed;
      connectionRecord.State.ProcessPackets(connectionRecord);
    }
    return;
  }
  // berÀkna övre grÀns för fönstret
  int windowHighestBound = Math.Min((connectionRecord.WindowLowerBound + connectionRecord.WindowSize - 1), (connectionRecord.NumberOfPackets - 1));
  // filtrera bort paket som inte finns i fönstret
  if (header.PacketNumber  (windowHighestBound))
    return;
  // filtrera bort dubbletter
  if (connectionRecord.WindowControlArray.Contains(header.PacketNumber))
    return;
  // skriv data
  ReliableUdpStateTools.WritePacketData(connectionRecord, header, payload);
  // öka paketrÀknaren
  connectionRecord.PacketCounter++;
  // skriv in det aktuella paketnumret i kontrollarrayen
  connectionRecord.WindowControlArray[header.PacketNumber - connectionRecord.WindowLowerBound] = header.PacketNumber;
  // stÀll in det största mottagna paketnumret
  if (header.PacketNumber > connectionRecord.RcvCurrent)
    connectionRecord.RcvCurrent = header.PacketNumber;
  // ÄterstÀll timers
  connectionRecord.TimerSecondTry = false;
  connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
  if (connectionRecord.CloseWaitTimer != null)
    connectionRecord.CloseWaitTimer.Change(-1, -1);
  // om sista paket kommit
  if (header.Flags.HasFlag(ReliableUdpHeaderFlags.LastPacket))
  {
    Interlocked.Increment(ref connectionRecord.IsLastPacketReceived);
  }
  // om vi har fÄtt alla paket i fönstret, ÄterstÀll rÀknaren
  // och skicka bekrÀftelsepaket
  else if (connectionRecord.PacketCounter == connectionRecord.WindowSize)
  {
    // ÄterstÀll rÀknaren.
    connectionRecord.PacketCounter = 0;
    // flytta överföringsfönstret
    connectionRecord.WindowLowerBound += connectionRecord.WindowSize;
    // nollstÀll överföringskontrollarrayen
    connectionRecord.WindowControlArray.Nullify();
    ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
  }
  // om sista paket redan Àr mottaget
  if (Thread.VolatileRead(ref connectionRecord.IsLastPacketReceived) != 0)
  {
    // kontrollera paketen
    ProcessPackets(connectionRecord);
  }
}

I tillstÄnd Slutförd den enda uppgiften för metoden Àr att skicka en ny bekrÀftelse om framgÄngsrik leverans av meddelandet.
Completed.ReceivePacket:

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  	// om sÀnda om den senaste packet igen pÄ grund av,
  	// att den senaste ack inte nÄdde avsÀndaren
  if (header.Flags.HasFlag(ReliableUdpHeaderFlags.LastPacket))
  {
    ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
  }
}

Metod SendPacket

I tillstÄnd FirstPacketSending denna metod skickar det första datapakets, eller, om meddelandet inte krÀver leveransbekrÀftelse - hela meddelandet.
FirstPacketSending.SendPacket:

public override void SendPacket(ReliableUdpConnectionRecord connectionRecord)
{
  connectionRecord.PacketCounter = 0;
  connectionRecord.SndNext = 0;
  connectionRecord.WindowLowerBound = 0;       
  	// om ingen bekrÀftelse behövs - skicka alla paket
  	// och frigör resurser
  if (connectionRecord.IsNoAnswerNeeded)
  {
    	// HÀr sker sÀndningen As Is
    do
    {
      ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.CreateUdpPayload(connectionRecord, ReliableUdpStateTools.CreateReliableUdpHeader(connectionRecord)));
      connectionRecord.SndNext++;
    } while (connectionRecord.SndNext < connectionRecord.NumberOfPackets);
    SetAsCompleted(connectionRecord);
    return;
  }
  	// skapa paketets header och skicka den
  ReliableUdpHeader header = ReliableUdpStateTools.CreateReliableUdpHeader(connectionRecord);
  ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.CreateUdpPayload(connectionRecord, header));
  	// öka rÀknaren
  connectionRecord.SndNext++;
  	// flytta fönstret
  connectionRecord.WindowLowerBound++;
  connectionRecord.State = connectionRecord.Tcb.States.SendingCycle;
  	// Starta timern
  connectionRecord.WaitForPacketsTimer = new Timer(CheckByTimer, connectionRecord, connectionRecord.ShortTimerPeriod, -1);
}

I tillstÄnd SendingCycle i den hÀr metoden sker sÀndningen av en paketblock.
SendingCycle.SendPacket:

public override void SendPacket(ReliableUdpConnectionRecord connectionRecord)
{      
  	// skicka en paketblock      
  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++;
  }
  	// vid en stor sÀndningsfönster, Äterstarta timern efter sÀndningen
  connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
  if (connectionRecord.CloseWaitTimer != null)
  {
    connectionRecord.CloseWaitTimer.Change(-1, -1);
  }
}

Djupare in i koden. Skapande och upprÀttande av anslutningar

Nu nÀr vi har bekantat oss med de grundlÀggande tillstÄnden och metoderna som anvÀnds för att hantera tillstÄnd, kan vi gÄ lite mer pÄ djupet med nÄgra exempel pÄ protokollets arbete.
Diagram över datalÀmning under normala förhÄllanden:Implementering av Reliable UDP-protokollet för .Net

LÄt oss ta en nÀrmare titt pÄ skapandet connection record för att etablera en koppling och skicka den första datapaketet. Initiativtagaren till överföringen Àr alltid applikationen som anropar API-metoden för att skicka meddelandet. DÀrefter anvÀnds metoden StartTransmission frÄn kontrollblocket för överföring, som pÄbörjar dataöverföringen för det nya meddelandet.
Skapande av en utgÄende koppling:

private void StartTransmission(ReliableUdpMessage reliableUdpMessage, EndPoint endPoint, AsyncResultSendMessage asyncResult)
{
  if (m_isListenerStarted == 0)
  {
    if (this.LocalEndpoint == null)
    {
      throw new ArgumentNullException( "", "Du mÄste anvÀnda konstruktör med parametrar eller starta lyssnaren innan du skickar meddelande" );
    }
    // startar bearbetningen av inkommande paket
    StartListener(LocalEndpoint);
  }
  // skapar nyckel för ordboken baserat pÄ EndPoint och ReliableUdpHeader.TransmissionId
  byte[] transmissionId = new byte[4];
  // skapar ett slumpmÀssigt nummer transmissionId
  m_randomCrypto.GetBytes(transmissionId);
  Tuple key = new Tuple(endPoint, BitConverter.ToInt32(transmissionId, 0));
  // skapar en ny post för kopplingen och kontrollerar om det finns redan ett sÄdant nummer i vÄra ordböcker
  if (!m_listOfHandlers.TryAdd(key, new ReliableUdpConnectionRecord(key, this, reliableUdpMessage, asyncResult)))
  {
    // om det finns – generera ett nytt slumpmĂ€ssigt nummer
    m_randomCrypto.GetBytes(transmissionId);
    key = new Tuple(endPoint, BitConverter.ToInt32(transmissionId, 0));
    if (!m_listOfHandlers.TryAdd(key, new ReliableUdpConnectionRecord(key, this, reliableUdpMessage, asyncResult)))
      // om det fortfarande misslyckas – generera ett undantag
      throw new ArgumentException("Par TransmissionId & EndPoint finns redan i ordboken");
  }
  // har startat tillstÄndet för behandling
  m_listOfHandlers[key].State.SendPacket(m_listOfHandlers[key]);
}

Skickar det första paketet (tillstÄnd FirstPacketSending):

public override void SendPacket(ReliableUdpConnectionRecord connectionRecord)
{
  connectionRecord.PacketCounter = 0;
  connectionRecord.SndNext = 0;
  connectionRecord.WindowLowerBound = 0;       
  // ... 
  // skapar paketets header och skickar den
  ReliableUdpHeader header = ReliableUdpStateTools.CreateReliableUdpHeader(connectionRecord);
  ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.CreateUdpPayload(connectionRecord, header));
  // ökar rÀknaren
  connectionRecord.SndNext++;
  // justerar fönstret
  connectionRecord.WindowLowerBound++;
  // gÄr in i tillstÄndet SendingCycle
  connectionRecord.State = connectionRecord.Tcb.States.SendingCycle;
  // startar timern
  connectionRecord.WaitForPacketsTimer = new Timer(CheckByTimer, connectionRecord, connectionRecord.ShortTimerPeriod, -1);
}

Efter att det första paketet har skickats övergĂ„r avsĂ€ndaren till tillstĂ„nd SendingCycle – vĂ€ntar pĂ„ bekrĂ€ftelse av paketets leverans.
Mottagarsidan, med hjÀlp av metoden EndReceive, tar emot det skickade paketet, skapar en ny connection record och vidarebefordrar detta paket, med en förhandsparserad header, för behandling till metoden ReceivePacket i tillstÄndet FirstPacketReceived
Skapa anslutning pÄ mottagarsidan:

private void EndReceive(IAsyncResult ar)
{
  // ...
  // paket mottaget
  // parsar paketets rubrik
  ReliableUdpHeader header;
  if (!ReliableUdpStateTools.ReadReliableUdpHeader(bytes, out header))
  {          
    // ett ogiltigt paket har kommit - avvisar det
    return;
  }
  // bygger nyckel för att identifiera anslutningsposten för paketet
  Tuple key = new Tuple(connectedClient, header.TransmissionId);
  // hÀmtar befintlig anslutningspost eller skapar en ny
  ReliableUdpConnectionRecord record = m_listOfHandlers.GetOrAdd(key, new ReliableUdpConnectionRecord(key, this, header.ReliableUdpMessageType));
  // startar paketet för behandling i tillstÄndsmaskinen
  record.State.ReceivePacket(record, header, bytes);
}

Mottagning av första paketet och skickar bekrÀftelse (tillstÄnd FirstPacketReceived):

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  if (!header.Flags.HasFlag(ReliableUdpHeaderFlags.FirstPacket))
    // avvisar paketet
    return;
  // ...
  // designat sÄ att alla paketnummer börjar frÄn 0;
  if (header.PacketNumber != 0)          
    return;
  // initierar array för att lagra meddelandets delar
  ReliableUdpStateTools.InitIncomingBytesStorage(connectionRecord, header);
  // skriver data pÄ paketet i arrayen
  ReliableUdpStateTools.WritePacketData(connectionRecord, header, payload);
  // berÀknar antalet paket som ska komma
  connectionRecord.NumberOfPackets = (int)Math.Ceiling((double)((double)connectionRecord.IncomingStream.Length/(double)connectionRecord.BufferSize));
  // registrerar numret pÄ det sista mottagna paketet (0)
  connectionRecord.RcvCurrent = header.PacketNumber;
  // efter detta flyttas mottagningsfönstret fram med 1
  connectionRecord.WindowLowerBound++;
  // byter tillstÄnd
  connectionRecord.State = connectionRecord.Tcb.States.Assembling;
  if (/*om bekrÀftelsemekanismen inte krÀvs*/)
  // ...
  else
  {
    // skickar bekrÀftelse
    ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
    connectionRecord.WaitForPacketsTimer = new Timer(CheckByTimer, connectionRecord, connectionRecord.ShortTimerPeriod, -1);
  }
}

Djupare in i koden. StÀngning av anslutningen vid timeout

Hantering av tidsgrÀnser Àr en viktig del av Reliable UDP. LÄt oss se pÄ ett exempel dÀr det har uppstÄtt ett fel vid en mellanliggande nod och dataleveransen i bÄda riktningarna blivit omöjlig.
Diagram över stÀngning av anslutning pÄ grund av tidsgrÀns:Implementering av Reliable UDP-protokollet för .Net

Som kan ses i diagrammet aktiveras arbetstimer hos avsÀndaren omedelbart efter avsÀndning av paketblock. Detta sker i metoden SendPacket i tillstÄndet SendingCycle.
Aktivering av arbetstimer (tillstÄnd SendingCycle):

public override void SendPacket(ReliableUdpConnectionRecord connectionRecord)
{      
  // skickar paketblock
  // ...   
  // omstartar timern efter skickande
  connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
  if (connectionRecord.CloseWaitTimer != null)
    connectionRecord.CloseWaitTimer.Change(-1, -1);
}

Tidsperioderna för timern anges vid skapandet av anslutningen. Som standard Àr ShortTimerPeriod 5 sekunder. I exemplet Àr den satt till 1,5 sekunder.

För en inkommande anslutning börjar timern efter att den senaste mottagna datapaketet har kommit, detta sker i metoden ReceivePacket i tillstÄndet Assembling
Aktivering av arbets timern (tillstÄnd Assembling):

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

I den inkommande anslutningen har det under vÀntetiden för arbets timern inte kommit fler paket. Timern har utlösts och kallat metoden ProcessPackets, dÀr förlorade paket har upptÀckts och för första gÄngen har begÀrningar om omleverans skickats.
Skickande av förfrÄgningar om omleverans (tillstÄnd Assembling):

public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
  // ...        
  if (/*kontroll av förlorade paket*/) 
  {
    // skickar förfrÄgningar om omleverans
    // stÀller in timern för andra gÄngen, för Äterförsök av överföring
    if (!connectionRecord.TimerSecondTry) 
    {
      connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
      connectionRecord.TimerSecondTry = true;
      return;
    }
    // om efter tvÄ försök av WaitForPacketTimer
    // har det inte gÄtt att fÄ paketen - starta stÀngningstimer för anslutningen
    StartCloseWaitTimer(connectionRecord);
  }
  else if (/*det sista paketet kom och kontrolleringen lyckades*/) 
  {
    // ...
    StartCloseWaitTimer(connectionRecord);
  }
  // om ack för paketblocket har gÄtt förlorat
  else 
  { 
    if (!connectionRecord.TimerSecondTry) 
    {
      // skickar ack pÄ nytt
      connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
      connectionRecord.TimerSecondTry = true;
      return;
    }
    // startar stÀngningstimer för anslutningen
    StartCloseWaitTimer(connectionRecord);
  }
}

Variabeln TimerSecondTry stÀlldes in pÄ true. Denna variabel ansvarar för omstart av arbets timern.

Även avsĂ€ndaren utlöser arbets timern och skickar pĂ„ nytt det senaste skickade paketet.
Aktivering av stÀngningstimer för anslutningen (tillstÄnd SendingCycle):

public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
  // ...        
  // skickar pÄ nytt det senaste paketet 
  // ...        
  // aktiverar CloseWait-timern – för att vĂ€nta pĂ„ Ă„terstĂ€llning av anslutningen eller dess avslutning
  StartCloseWaitTimer(connectionRecord);
}

Efter detta startas en stÀngningstimer för den utgÄende anslutningen.
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);
}

StÀngningstimern för anslutningen har en standard vÀntetid pÄ 30 sekunder.

Efter en kort stund aktiveras arbetsstimern pÄ mottagarsidan igen, förfrÄgningar skickas pÄ nytt och stÀngningstimern för den inkommande anslutningen startas.

Vid aktivering av stÀngningstimrarna frigörs alla resurser för bÄda anslutningarna. AvsÀndaren rapporterar om misslyckad leverans till överordnat applikation (se API Reliable UDP).
Frigöring av resurser för anslutningspost:

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

Djupare in i koden. ÅterstĂ€llning av datatransmission

Diagram över ÄterstÀllning av datapaket vid paketförlust:Implementering av Reliable UDP-protokollet för .Net

Som redan diskuterats, vid timeout av stÀngningen av anslutningen kommer mottagaren att kontrollera för paketförlust efter arbetsstimern har löpt ut. Vid paketförlust kommer en lista att sammanstÀllas över paketnummer som inte nÄtt mottagaren. Dessa nummer lÀggs till i arrayen LostPackets för den specifika anslutningen och förfrÄgningar om omleverans skickas.
Skickar förfrÄgningar om omleverans av paket (tillstÄnd Assembling):

public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
  //...
  if (!ReliableUdpStateTools.CheckForNoPacketLoss(connectionRecord, connectionRecord.IsLastPacketReceived != 0))
  {
    // det finns förlorade paket, skicka förfrÄgningar om dem
    foreach (int seqNum in connectionRecord.LostPackets)
    {
      if (seqNum != 0)
      {
        ReliableUdpStateTools.SendAskForLostPacket(connectionRecord, seqNum);
      }
    }
    // ...
  }
}

AvsÀndaren kommer att ta emot en begÀran om omleverans och skicka de saknade paketen. Det Àr vÀrt att notera att avsÀndarens stÀngningstimer redan Àr aktiverad vid detta tillfÀlle och kommer att ÄterstÀllas vid mottagning av begÀran.
Omleverans av förlorade paket (tillstÄnd SendingCycle):

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  // ...
  connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
  // ÄterstÀllning av nedstÀngningstimer
  if (connectionRecord.CloseWaitTimer != null)
    connectionRecord.CloseWaitTimer.Change(-1, -1);
  // ...
  // detta Ă€r en begĂ€ran om retransmission – skicka den begĂ€rda paketet
  else
    ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.RetransmissionCreateUdpPayload(connectionRecord, header.PacketNumber));
}

Det omdetonerade paketet (packet#3 i diagrammet) tas emot av den ingÄende anslutningen. En kontroll för fyllning av mottagningsfönster genomförs och normal dataöverföring ÄterstÀlls.
Kontroll av om det ligger inom mottagningsfönstret (tillstÄnd Assembling):

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  // ...
  // öka paketrÀknaren
  connectionRecord.PacketCounter++;
  // skriv det aktuella paketnumret till fönsterhanteringsarrayen
  connectionRecord.WindowControlArray[header.PacketNumber - connectionRecord.WindowLowerBound] = header.PacketNumber;
  // stÀll in det största mottagna paketet
  if (header.PacketNumber > connectionRecord.RcvCurrent)
    connectionRecord.RcvCurrent = header.PacketNumber;
  // starta om timrarna
  connectionRecord.TimerSecondTry = false;
  connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
  if (connectionRecord.CloseWaitTimer != null)
    connectionRecord.CloseWaitTimer.Change(-1, -1);
  // ...
  // om vi har fÄtt alla paket i fönstret, nollstÀll rÀknaren
  // och skicka bekrÀftelsepaketet
  else if (connectionRecord.PacketCounter == connectionRecord.WindowSize)
  {
    // nollstÀll rÀknaren.
    connectionRecord.PacketCounter = 0;
    // flyttade överföringsfönstret
    connectionRecord.WindowLowerBound += connectionRecord.WindowSize;
    // nollstÀll överföringshanteringsarrayen
    connectionRecord.WindowControlArray.Nullify();
    ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
  }
  // ...
}

API för Reliable UDP

För interaktion med dataöverföringsprotokollet finns en öppen klass Reliable Udp, som Àr en omslag kring överföringskontrollblocket. HÀr Àr de viktigaste medlemmarna i klassen:

offentlig förseglad klass ReliableUdp : IDisposable
{
  // fÄr den lokala Àndpunkten
  offentlig IPEndPoint LocalEndpoint    
  // skapar en instans av ReliableUdp och startar
  // lyssnar pÄ inkommande paket pÄ angiven IP-adress
  // och port. VÀrde 0 för port innebÀr anvÀndning
  // av en dynamiskt tilldelad port
  offentlig ReliableUdp(IPAddress localAddress, int port = 0) 
  // prenumeration pÄ inkommande meddelanden
  offentlig ReliableUdpSubscribeObject SubscribeOnMessages(ReliableUdpMessageCallback callback, ReliableUdpMessageTypes messageType = ReliableUdpMessageTypes.Any, IPEndPoint ipEndPoint = null)    
  // avprenumerera frÄn att ta emot meddelanden
  offentlig void Unsubscribe(ReliableUdpSubscribeObject subscribeObject)
  // asynkront skicka meddelande 
  // Obs: kompatibilitet med XP och Server 2003 gÄr inte förlorad, eftersom .NET Framework 4.0 anvÀnds
  offentlig Task SendMessageAsync(ReliableUdpMessage reliableUdpMessage, IPEndPoint remoteEndPoint, CancellationToken cToken)
  // starta asynkron skickning av meddelande
  offentlig IAsyncResult BeginSendMessage(ReliableUdpMessage reliableUdpMessage, IPEndPoint remoteEndPoint, AsyncCallback asyncCallback, Object state)
  // fÄ resultatet av den asynkrona sÀndningen
  offentlig bool EndSendMessage(IAsyncResult asyncResult)  
  // rensa resurser
  offentlig void Dispose()    
}

Mottagande av meddelande sker genom prenumeration. Signaturen för delegeringen till Äteranropmetoden:

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

Meddelande:

offentlig klass ReliableUdpMessage
{
  // meddelandetyp, enkel upprÀkning
  offentlig ReliableUdpMessageTypes Type { fÄ; privat sÀtt; }
  // meddelandets data
  offentlig byte[] Body { fÄ; privat sÀtt; }
  // om instÀllt pÄ true - bekrÀftelsemekanismen för leverans kommer att inaktiveras
  // för att skicka ett specifikt meddelande
  offentlig bool NoAsk { fÄ; privat sÀtt; }
}

För att prenumerera pÄ en specifik typ av meddelanden och/eller en specifik avsÀndare anvÀnds tvÄ valfria parametrar: ReliableUdpMessageTypes messageType och IPEndPoint ipEndPoint.

Meddelandetyper:

offentlig enum ReliableUdpMessageTypes : kort
{ 
  // Vilken som helst
  Any = 0,
  // FörfrÄgan till STUN-server
  StunRequest = 1,
  // Svar frÄn STUN-server
  StunResponse = 2,
  // Filöverföring
  FileTransfer = 3,
  // ...
}

Skickandet av meddelande sker asynkront, för detta har protokollet implementerat en asynkron programmeringsmodell:

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

Resultatet av meddelandesÀndning kommer att vara true - om meddelandet framgÄngsrikt nÄtt mottagaren och false - om anslutningen stÀngdes pÄ grund av tidsgrÀns:

offentlig bool EndSendMessage(IAsyncResult asyncResult)

Slutsats

Mycket har inte beskrivits i denna artikel. Mekanismer för strömöverenskommelse, hantering av undantag och fel samt implementering av asynkrona meddelandesÀndningsmetoder. Men kÀrnan i protokollet, beskrivningen av logiken för paketbehandling, anslutningsinstÀllningar och timeouthantering, bör klargöras för dig.

Den demonstrerade versionen av det pÄlitliga leveransprotokollet Àr tillrÀckligt stabil och flexibel och motsvarar vissa tidigare krav. Men jag vill tillÀgga att den beskrivna implementeringen kan förbÀttras. Till exempel, för att öka kapaciteten och dynamiskt justera timerintervall kan mekanismer som sliding window och RTT lÀggas till protokollet, och det skulle Àven vara anvÀndbart att implementera en mekanism för att bestÀmma MTU mellan anslutningsnoderna (men endast vid sÀndning av stora meddelanden).

Tack för uppmÀrksamheten, jag ser fram emot dina kommentarer och anmÀrkningar.

P.S. För den som Àr intresserad av detaljer eller bara vill testa protokollet, hÀr Àr lÀnken till projektet pÄ GitHub:
Projektet Reliable UDP

Nyttiga lÀnkar och artiklar

  1. Specifikation för TCP-protokollet: pÄ engelska och pÄ ryska
  2. Specifikation för UDP-protokollet: pÄ engelska och pÄ ryska
  3. Diskussion om RUDP-protokollet: draft-ietf-sigtran-reliable-udp-00
  4. Reliable Data Protocol: rfc 908 och rfc 1151
  5. En enkel implementering av leveransbekrÀftelse över UDP: Ta full kontroll över ditt nÀtverk med .NET och UDP
  6. Artikel som beskriver mekanismer för att övervinna NAT:er: Peer-to-Peer-kommunikation över nÀtverksadressöversÀttare
  7. Implementering av asynkron programmeringsmodell: Implementing the CLR Asynchronous Programming Model och Hur man implementerar designmönstret IAsyncResult
  8. Överföring av den asynkrona programmeringsmodellen till ett asynkront mönster baserat pĂ„ uppgifter (APM till TAP):
    TPL och traditionell .NET asynkron programmering
    Interop med andra asynkrona mönster och typer

Uppdatering: Tack mayorovp och sidristij för idén om att lÀgga till en uppgift till grÀnsnittet. Kompatibiliteten för biblioteket med Àldre OS pÄverkas inte, eftersom den fjÀrde ramen stöder bÄde XP och 2003-server.

KĂ€lla: habr.com

Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar đŸ”„ Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar | ProHoster