Implementering af Reliable Udp-protokollen til .Net

Internettet har ændret sig for længe siden. UDP, en af ​​de vigtigste internetprotokoller, bruges af applikationer ikke kun til at levere datagrammer og udsendelser, men også til at give peer-to-peer-forbindelser mellem netværksnoder. På grund af sin enkle struktur har denne protokol fundet mange tidligere uplanlagte anvendelser, selvom protokollens mangler, såsom manglen på garanteret levering, ikke er forsvundet. Denne artikel beskriver implementeringen af ​​en garanteret leveringsprotokol over UDP.
Indhold:Indrejse
Protokolkrav
Pålidelig UDP-header
Protokollens generelle principper
Timeouts og protokoltimere
Diagram over pålidelig UDP-transmissionstilstand
Dybere ned i koden. Transmissionsstyringsenhed
Dybere ned i koden. Stater

Dybere ned i koden. Oprettelse og etablering af forbindelser
Dybere ned i koden. Lukker forbindelse på grund af timeout
Dybere ned i koden. Gendan dataoverførsel
API-pålidelig UDP
Konklusion
Nyttige links og artikler

Indrejse

Internettets oprindelige arkitektur antog et ensartet adresserum, hvor hver node havde en globalt unik IP-adresse og kunne kommunikere direkte med andre noder. Nu har internettet faktisk en anden arkitektur - ét område med globale IP-adresser og mange områder med private adresser skjult bag NAT-enheder.I en sådan arkitektur er det kun enheder i det globale adresserum, der nemt kan kommunikere med andre på netværket, fordi de har en unik, globalt routerbar IP-adresse. En node på et privat netværk kan kommunikere med andre noder på det samme netværk og kan også kommunikere med andre velkendte noder i det globale adresserum. Denne interaktion opnås i høj grad gennem netværksadresseoversættelsesmekanismen. NAT-enheder, såsom Wi-Fi-routere, opretter særlige poster i oversættelsestabellerne for udgående forbindelser og ændrer IP-adresser og portnumre i pakker. Dette gør det muligt at etablere udgående forbindelser fra et privat netværk til noder i det globale adresserum. Men samtidig blokerer NAT-enheder normalt al indgående trafik, medmindre der er fastsat separate regler for indgående forbindelser.

Denne internetarkitektur er helt korrekt til klient-server-interaktion, når klienter kan være placeret i private netværk, og servere har en global adresse. Men det skaber vanskeligheder med direkte forbindelse af to noder mellem forskellige private netværk. Direkte forbindelse mellem to noder er vigtig for peer-to-peer-applikationer såsom stemmetransmission (Skype), fjernadgang til en computer (TeamViewer) eller online spil.

En af de mest effektive metoder til at etablere peer-to-peer-forbindelser mellem enheder placeret på forskellige private netværk kaldes "hulstansning". Denne teknik bruges oftest med UDP-protokolbaserede applikationer.

Men hvis din applikation kræver garanteret levering af data, for eksempel hvis du overfører filer mellem computere, vil brugen af ​​UDP forårsage mange vanskeligheder, da UDP ikke er en garanteret leveringsprotokol og ikke sikrer levering af pakker i rækkefølge, i modsætning til TCP-protokollen.

I dette tilfælde er det nødvendigt at implementere en applikationslagsprotokol, der leverer den nødvendige funktionalitet og fungerer oven på UDP, for at sikre garanteret levering af pakker.

Jeg vil gerne med det samme påpege, at der findes en TCP-hulstansningsteknik til at etablere TCP-forbindelser mellem noder i forskellige private netværk, men på grund af manglende understøttelse af den af ​​mange NAT-enheder, betragtes den normalt ikke som den primære metode til at forbinde sådanne noder.

I resten af ​​denne artikel vil jeg kun omhandle implementeringen af ​​protokollen for garanteret levering. Implementeringen af ​​UDP-hulstansningsteknikken vil blive beskrevet i de følgende artikler.

Protokolkrav

  1. Pålidelig pakkelevering implementeret gennem en positiv feedbackmekanisme (såkaldt positiv bekræftelse)
  2. Behovet for effektiv transmission af big data, dvs. protokollen skal undgå unødvendige pakketransmissioner
  3. Det burde være muligt at deaktivere leveringsbekræftelsesmekanismen (muligheden for at fungere som en "ren" UDP-protokol)
  4. Mulighed for implementering af kommandotilstand med bekræftelse af hver besked
  5. Den grundlæggende enhed for dataoverførsel i en protokol skal være en besked.

Disse krav stemmer stort set overens med kravene til den pålidelige dataprotokol, der er beskrevet i RFC 908 и RFC 1151, og jeg baserede denne protokol på disse standarder.

For at forstå disse krav, lad os se på tidsdiagrammerne for dataoverførsel mellem to netværksnoder ved hjælp af TCP- og UDP-protokoller. Lad os sige, at vi i begge tilfælde mister én pakke.
Overførsel af ikke-interaktive data via TCP:Implementering af Reliable Udp-protokollen til .Net

Som det kan ses af diagrammet, vil TCP i tilfælde af pakketab registrere den mistede pakke og underrette afsenderen om det ved at anmode om nummeret på det mistede segment.
Dataoverførsel via UDP-protokol:Implementering af Reliable Udp-protokollen til .Net

UDP tager ingen skridt til at opdage tab. Fejlkontrol i UDP-transmission er udelukkende applikationens ansvar.

Fejldetektion i TCP-protokollen opnås ved at etablere en forbindelse til en slutnode, opretholde forbindelsens tilstand, angive antallet af bytes, der sendes i hver pakkeheader, og underrette modtagelser ved hjælp af et bekræftelsesnummer.

Derudover bruger TCP-protokollen det såkaldte transmissionsvindue - antallet af bytes data, som afsenderen af ​​segmentet forventer at modtage - for at forbedre ydeevnen (dvs. sende mere end ét segment uden at modtage en bekræftelse).

Flere detaljer om TCP-protokollen kan findes i RFC 793, med UDP i RFC 768, hvor de faktisk er defineret.

Ud fra ovenstående er det tydeligt, at for at skabe en pålidelig meddelelsesleveringsprotokol over UDP (herefter benævnt Pålidelig UDP), er det nødvendigt at implementere dataoverførselsmekanismer svarende til TCP. Nemlig:

  • gem forbindelsesstatus
  • brug segmentnummerering
  • brug særlige bekræftelsespakker
  • Brug en forenklet vinduesmekanisme til at øge protokolgennemstrømningen

Derudover kræves følgende:

  • signalere starten på en besked for at allokere ressourcer til forbindelsen
  • signalere slutningen af ​​en besked for at sende den modtagne besked til upstream-applikationen og frigive protokolressourcer
  • tillad protokollen at deaktivere leveringsbekræftelsesmekanismen pr. forbindelse for at fungere som "ren" UDP

Pålidelig UDP-header

Husk at et UDP-datagram er indkapslet i et IP-datagram. Den pålidelige UDP-pakke "pakkes" derfor ind i et UDP-datagram.
Pålidelig UDP-headerindkapsling:Implementering af Reliable Udp-protokollen til .Net

Strukturen af ​​den pålidelige UDP-header er ret simpel:

Implementering af Reliable Udp-protokollen til .Net

  • Flag – pakkens kontrolflag
  • MessageType – meddelelsestype, der bruges af upstream-applikationer til at abonnere på bestemte meddelelser
  • TransmissionId — transmissionsnummeret identificerer sammen med modtagerens adresse og port entydigt forbindelsen.
  • Pakkenummer – pakkenummer
  • Valgmuligheder – yderligere protokolmuligheder. I tilfælde af den første pakke bruges den til at angive meddelelsens størrelse.

Flagene er som følger:

  • FirstPacket - den første pakke i beskeden
  • NoAsk - beskeden kræver ikke, at bekræftelsesmekanismen er aktiveret
  • Sidste pakke — den sidste pakke i beskeden
  • RequestForPacket - Bekræftelsespakke eller anmodning om mistet pakke

Protokollens generelle principper

Da pålidelig UDP fokuserer på garanteret meddelelsesoverførsel mellem to noder, skal den være i stand til at etablere en forbindelse med den anden side. For at etablere en forbindelse sender afsendersiden en pakke med FirstPacket-flaget, hvis svar vil indikere etableringen af ​​forbindelsen. Alle svarpakker eller bekræftelsespakker sætter altid værdien i feltet PacketNumber til én, der er større end den største PacketNumber-værdi for succesfuldt modtagne pakker. Feltet Indstillinger for den første sendte pakke indeholder beskedstørrelsen.

En lignende mekanisme bruges til at fuldføre forbindelsen. LastPacket-flaget sættes i den sidste pakke i beskeden. Svarpakken indeholder nummeret på den sidste pakke + 1, hvilket for modtagersiden betyder vellykket levering af beskeden.
Diagram over etablering og afslutning af forbindelse:Implementering af Reliable Udp-protokollen til .Net

Når forbindelsen er etableret, begynder dataoverførslen. Data transmitteres i blokke af pakker. Hver blok, undtagen den sidste, indeholder et fast antal pakker. Det er lig med størrelsen af ​​modtage-/sendevinduet. Den sidste datablok kan have færre pakker. Efter at have sendt hver blok, venter afsendersiden på bekræftelse af levering eller en anmodning om omlevering af mistede pakker, hvilket efterlader et modtage-/sendevindue åbent for svar. Efter modtagelse af bekræftelse på bloklevering forskydes modtage-/sendevinduet, og den næste datablok sendes.

Modtageren accepterer pakkerne. Hver pakke kontrolleres for at se, om den falder inden for transmissionsvinduet. Pakker og dubletter, der ikke passer ind i vinduet, filtreres fra. Da vinduesstørrelsen er strengt fastsat og den samme for både modtager og afsender, forskydes vinduet i tilfælde af levering af en blok af pakker uden tab for at modtage pakker fra den næste datablok, og der sendes en leveringsbekræftelse. Hvis vinduet ikke udfyldes inden for den periode, der er angivet af arbejdstimeren, iværksættes en kontrol for at bestemme, hvilke pakker der ikke blev leveret, og der sendes anmodninger om omlevering.
Retransmissionsdiagram:Implementering af Reliable Udp-protokollen til .Net

Timeouts og protokoltimere

Der er flere grunde til, at en forbindelse ikke kan etableres. For eksempel hvis modtageren er offline. I dette tilfælde vil forbindelsen blive lukket på grund af timeout, når der forsøges at oprette en forbindelse. Implementeringen af ​​pålidelige UDP bruger to timere til at indstille timeouts. Den første, arbejdstimeren, bruges til at vente på et svar fra den eksterne vært. Hvis den udløses på afsendersiden, sendes den sidst sendte pakke igen. Hvis timeren udløses hos modtageren, udføres der en kontrol for mistede pakker, og der sendes anmodninger om omlevering.

Den anden timer er nødvendig for at lukke forbindelsen, hvis der ikke er nogen forbindelse mellem noderne. For afsendersiden starter den umiddelbart efter, at arbejdstimeren udløber, og venter på et svar fra den eksterne node. Hvis der ikke er noget svar inden for den angivne periode, afbrydes forbindelsen, og ressourcerne frigives. For modtagersiden startes timeren for forbindelseslukning, efter at den fungerende timer har aktiveret to gange. Dette er nødvendigt for at sikre mod tab af bekræftelsespakken. Når timeren aktiveres, afbrydes forbindelsen også, og ressourcer frigives.

Diagram over pålidelig UDP-transmissionstilstand

Protokollens principper implementeres i en endelig tilstandsmaskine, hvis hver tilstand er ansvarlig for en specifik logik for pakkebehandling.
Pålidelig UDP-tilstandsdiagram:

Implementering af Reliable Udp-protokollen til .Net

Lukket – er faktisk ikke en tilstand, det er maskinens start- og slutpunkt. For staten Lukket En transmissionskontrolblok modtages, som, ved at implementere en asynkron UDP-server, videresender pakker til de relevante forbindelser og starter tilstandsbehandling.

Førstepakkeafsendelse – den udgående forbindelses oprindelige tilstand, når en besked sendes.

I denne tilstand sendes den første pakke til normale beskeder. For beskeder uden bekræftelse af afsendelse er dette den eneste tilstand - i den sendes hele beskeden.

Sendercyklus – den grundlæggende tilstand for transmission af beskedpakker.

Overgang til det fra staten Førstepakkeafsendelse udføres efter afsendelse af den første beskedpakke. Det er i denne tilstand, at alle bekræftelser og anmodninger om retransmissioner modtages. Det er muligt at afslutte den i to tilfælde - i tilfælde af vellykket levering af beskeden eller på grund af timeout.

FørstePakkeModtaget – den oprindelige tilstand for beskedmodtageren.

Den kontrollerer korrektheden af ​​transmissionens start, opretter de nødvendige strukturer og sender en bekræftelse på modtagelsen af ​​den første pakke.

For en besked, der består af en enkelt pakke og sendes uden brug af leveringsbekræftelse, er dette den eneste tilstand. Efter behandling af en sådan besked lukkes forbindelsen.

Samling – grundtilstanden for modtagelse af beskedpakker.

Den registrerer pakker i midlertidig lagring, kontrollerer for pakketab, sender bekræftelser på levering af en blok af pakker og hele beskeden og sender anmodninger om omlevering af mistede pakker. Hvis hele beskeden modtages, går forbindelsen i tilstanden Afsluttet, ellers udføres en timeout.

Afsluttet – lukker forbindelsen, hvis hele beskeden er modtaget.

Denne tilstand er nødvendig for samling af beskeder og i tilfælde af, at bekræftelsen af ​​beskedleveringen gik tabt på vej til afsenderen. Denne tilstand afsluttes med timeout, men forbindelsen betragtes som lukket.

Dybere ned i koden. Transmissionsstyringsenhed

Et af nøgleelementerne i pålidelig UDP er transmissionskontrolblokken. Formålet med denne blok er at gemme aktuelle forbindelser og hjælpeelementer, distribuere indgående pakker til de relevante forbindelser, give en grænseflade til at sende pakker til forbindelsen og implementere protokollens API. Transmissionskontrolblokken modtager pakker fra UDP-laget og videresender dem til tilstandsmaskinen til behandling. For at modtage pakker har den en asynkron UDP-server.
Nogle medlemmer af ReliableUdpConnectionControlBlock-klassen:

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

Implementering af en asynkron UDP-server:

private void Receive()
{
  EndPoint connectedClient = new IPEndPoint(IPAddress.Any, 0);
  // создаем новый буфер, для каждого socket.BeginReceiveFrom 
  byte[] buffer = new byte[DefaultMaxPacketSize + ReliableUdpHeader.Length];
  // передаем буфер в качестве параметра для асинхронного метода
  this.m_socketIn.BeginReceiveFrom(buffer, 0, buffer.Length, SocketFlags.None, ref connectedClient, EndReceive, buffer);
}   

private void EndReceive(IAsyncResult ar)
{
  EndPoint connectedClient = new IPEndPoint(IPAddress.Any, 0);
  int bytesRead = this.m_socketIn.EndReceiveFrom(ar, ref connectedClient);
  //пакет получен, готовы принимать следующий        
  Receive();
  // т.к. простейший способ решить вопрос с буфером - получить ссылку на него 
  // из IAsyncResult.AsyncState        
  byte[] bytes = ((byte[]) ar.AsyncState).Slice(0, bytesRead);
  // получаем заголовок пакета        
  ReliableUdpHeader header;
  if (!ReliableUdpStateTools.ReadReliableUdpHeader(bytes, out header))
  {          
    // пришел некорректный пакет - отбрасываем его
    return;
  }
  // конструируем ключ для определения connection record’а для пакета
  Tuple<EndPoint, Int32> key = new Tuple<EndPoint, Int32>(connectedClient, header.TransmissionId);
  // получаем существующую connection record или создаем новую
  ReliableUdpConnectionRecord record = m_listOfHandlers.GetOrAdd(key, new ReliableUdpConnectionRecord(key, this, header.ReliableUdpMessageType));
  // запускаем пакет в обработку в конечный автомат
  record.State.ReceivePacket(record, header, bytes);
}

For hver beskedoverførsel oprettes en struktur, der indeholder forbindelsesoplysninger. Denne struktur kaldes forbindelsesregistrering.
Nogle medlemmer af ReliableUdpConnectionRecord-klassen:

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

Dybere ned i koden. Stater

Stater implementerer den pålidelige UDP-protokolls tilstandsmaskine, hvor det meste pakkebehandling finder sted. Den abstrakte klasse ReliableUdpState leverer en grænseflade til tilstanden:

Implementering af Reliable Udp-protokollen til .Net

Hele protokollens logik implementeres af de ovenfor præsenterede klasser, sammen med en hjælpeklasse, der leverer statiske metoder, såsom for eksempel konstruktion af ReliableUdp-headeren fra forbindelsesposten.

Dernæst vil vi i detaljer se på implementeringen af ​​grænseflademetoder, der definerer de grundlæggende algoritmer for protokollen.

DisposeByTimeout-metoden

DisposeByTimeout-metoden er ansvarlig for at frigive forbindelsesressourcer efter en timeout og for at signalere, om meddelelsesleveringen er vellykket/fejlslagen.
ReliableUdpState.DisposeByTimeout:

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

Det tilsidesættes kun i staten Afsluttet.
Færdiggjort.AfskaffelseByTimeout:

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

ProcessPackets-metoden

ProcessPackets-metoden er ansvarlig for at udføre yderligere behandling på pakken eller pakkerne. Ringes direkte op eller via en pakkeventetimer.

I stand Samling Metoden tilskrives og er ansvarlig for at kontrollere for mistede pakker og overgang til tilstanden Afsluttet, i tilfælde af at den sidste pakke er modtaget og kontrollen er bestået
Assembling.ProcessPackets:

public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
  if (connectionRecord.IsDone != 0)
    return;
  if (!ReliableUdpStateTools.CheckForNoPacketLoss(connectionRecord, connectionRecord.IsLastPacketReceived != 0))
  {
    // есть потерянные пакеты, отсылаем запросы на них
    foreach (int seqNum in connectionRecord.LostPackets)
    {
      if (seqNum != 0)
      {
        ReliableUdpStateTools.SendAskForLostPacket(connectionRecord, seqNum);
      }
    }
    // устанавливаем таймер во второй раз, для повторной попытки передачи
    if (!connectionRecord.TimerSecondTry)
    {
      connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
      connectionRecord.TimerSecondTry = true;
      return;
    }
    // если после двух попыток срабатываний WaitForPacketTimer 
    // не удалось получить пакеты - запускаем таймер завершения соединения
    StartCloseWaitTimer(connectionRecord);
  }
  else if (connectionRecord.IsLastPacketReceived != 0)
  // успешная проверка 
  {
    // высылаем подтверждение о получении блока данных
    ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
    connectionRecord.State = connectionRecord.Tcb.States.Completed;
    connectionRecord.State.ProcessPackets(connectionRecord);
    // вместо моментальной реализации ресурсов
    // запускаем таймер, на случай, если
    // если последний ack не дойдет до отправителя и он запросит его снова.
    // по срабатыванию таймера - реализуем ресурсы
    // в состоянии Completed метод таймера переопределен
    StartCloseWaitTimer(connectionRecord);
  }
  // это случай, когда ack на блок пакетов был потерян
  else
  {
    if (!connectionRecord.TimerSecondTry)
    {
      ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
      connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
      connectionRecord.TimerSecondTry = true;
      return;
    }
    // запускаем таймер завершения соединения
    StartCloseWaitTimer(connectionRecord);
  }
}

I stand Sendercyklus Denne metode kaldes kun af timeren og er ansvarlig for at sende den sidste besked igen samt for at aktivere timeren til lukning af forbindelsen.
SendingCycle.ProcessPackets:

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

I stand Afsluttet Metoden stopper den kørende timer og sender en besked til abonnenterne.
Færdiggjorte.ProcessPackets:

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

ReceivePacket-metoden

I stand FørstePakkeModtaget Metodens hovedopgave er at bestemme, om den første pakke af beskeden rent faktisk ankom til grænsefladen, og også at indsamle en besked bestående af en enkelt pakke.
FørstePakkeModtaget.ModtagPakke:

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  if (!header.Flags.HasFlag(ReliableUdpHeaderFlags.FirstPacket))
    // отбрасываем пакет
    return;
  // комбинация двух флагов - FirstPacket и LastPacket - говорит что у нас единственное сообщение
  if (header.Flags.HasFlag(ReliableUdpHeaderFlags.FirstPacket) &
      header.Flags.HasFlag(ReliableUdpHeaderFlags.LastPacket))
  {
    ReliableUdpStateTools.CreateMessageFromSinglePacket(connectionRecord, header, payload.Slice(ReliableUdpHeader.Length, payload.Length));
    if (!header.Flags.HasFlag(ReliableUdpHeaderFlags.NoAsk))
    {
      // отправляем пакет подтверждение          
      ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
    }
    SetAsCompleted(connectionRecord);
    return;
  }
  // by design все packet numbers начинаются с 0;
  if (header.PacketNumber != 0)          
    return;
  ReliableUdpStateTools.InitIncomingBytesStorage(connectionRecord, header);
  ReliableUdpStateTools.WritePacketData(connectionRecord, header, payload);
  // считаем кол-во пакетов, которые должны прийти
  connectionRecord.NumberOfPackets = (int)Math.Ceiling((double) ((double) connectionRecord.IncomingStream.Length/(double) connectionRecord.BufferSize));
  // записываем номер последнего полученного пакета (0)
  connectionRecord.RcvCurrent = header.PacketNumber;
  // после сдвинули окно приема на 1
  connectionRecord.WindowLowerBound++;
  // переключаем состояние
  connectionRecord.State = connectionRecord.Tcb.States.Assembling;
  // если не требуется механизм подтверждение
  // запускаем таймер который высвободит все структуры         
  if (header.Flags.HasFlag(ReliableUdpHeaderFlags.NoAsk))
  {
    connectionRecord.CloseWaitTimer = new Timer(DisposeByTimeout, connectionRecord, connectionRecord.ShortTimerPeriod, -1);
  }
  else
  {
    ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
    connectionRecord.WaitForPacketsTimer = new Timer(CheckByTimer, connectionRecord, connectionRecord.ShortTimerPeriod, -1);
  }
}

I stand Sendercyklus Denne metode tilsidesættes for at acceptere leveringsbekræftelser og anmodninger om gentransmission.
SendingCycle.ReceivePacket:

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  if (connectionRecord.IsDone != 0)
    return;
  if (!header.Flags.HasFlag(ReliableUdpHeaderFlags.RequestForPacket))
    return;
  // расчет конечной границы окна
  // берется граница окна + 1, для получения подтверждений доставки
  int windowHighestBound = Math.Min((connectionRecord.WindowLowerBound + connectionRecord.WindowSize), (connectionRecord.NumberOfPackets));
  // проверка на попадание в окно        
  if (header.PacketNumber < connectionRecord.WindowLowerBound || header.PacketNumber > windowHighestBound)
    return;
  connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
  if (connectionRecord.CloseWaitTimer != null)
    connectionRecord.CloseWaitTimer.Change(-1, -1);
  // проверить на последний пакет:
  if (header.PacketNumber == connectionRecord.NumberOfPackets)
  {
    // передача завершена
    Interlocked.Increment(ref connectionRecord.IsDone);
    SetAsCompleted(connectionRecord);
    return;
  }
  // это ответ на первый пакет c подтверждением         
  if ((header.Flags.HasFlag(ReliableUdpHeaderFlags.FirstPacket) && header.PacketNumber == 1))
  {
    // без сдвига окна
    SendPacket(connectionRecord);
  }
  // пришло подтверждение о получении блока данных
  else if (header.PacketNumber == windowHighestBound)
  {
    // сдвигаем окно прием/передачи
    connectionRecord.WindowLowerBound += connectionRecord.WindowSize;
    // обнуляем массив контроля передачи
    connectionRecord.WindowControlArray.Nullify();
    // отправляем блок пакетов
    SendPacket(connectionRecord);
  }
  // это запрос на повторную передачу – отправляем требуемый пакет          
  else
    ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.RetransmissionCreateUdpPayload(connectionRecord, header.PacketNumber));
}

I stand Samling ReceivePacket-metoden er der, hvor hovedarbejdet med at samle beskeden fra indgående pakker finder sted.
Assembling.ReceivePacket:

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  if (connectionRecord.IsDone != 0)
    return;
  // обработка пакетов с отключенным механизмом подтверждения доставки
  if (header.Flags.HasFlag(ReliableUdpHeaderFlags.NoAsk))
  {
    // сбрасываем таймер
    connectionRecord.CloseWaitTimer.Change(connectionRecord.LongTimerPeriod, -1);
    // записываем данные
    ReliableUdpStateTools.WritePacketData(connectionRecord, header, payload);
    // если получили пакет с последним флагом - делаем завершаем          
    if (header.Flags.HasFlag(ReliableUdpHeaderFlags.LastPacket))
    {
      connectionRecord.State = connectionRecord.Tcb.States.Completed;
      connectionRecord.State.ProcessPackets(connectionRecord);
    }
    return;
  }        
  // расчет конечной границы окна
  int windowHighestBound = Math.Min((connectionRecord.WindowLowerBound + connectionRecord.WindowSize - 1), (connectionRecord.NumberOfPackets - 1));
  // отбрасываем не попадающие в окно пакеты
  if (header.PacketNumber < connectionRecord.WindowLowerBound || header.PacketNumber > (windowHighestBound))
    return;
  // отбрасываем дубликаты
  if (connectionRecord.WindowControlArray.Contains(header.PacketNumber))
    return;
  // записываем данные 
  ReliableUdpStateTools.WritePacketData(connectionRecord, header, payload);
  // увеличиваем счетчик пакетов        
  connectionRecord.PacketCounter++;
  // записываем в массив управления окном текущий номер пакета        
  connectionRecord.WindowControlArray[header.PacketNumber - connectionRecord.WindowLowerBound] = header.PacketNumber;
  // устанавливаем наибольший пришедший пакет        
  if (header.PacketNumber > connectionRecord.RcvCurrent)
    connectionRecord.RcvCurrent = header.PacketNumber;
  // перезапускам таймеры        
  connectionRecord.TimerSecondTry = false;
  connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
  if (connectionRecord.CloseWaitTimer != null)
    connectionRecord.CloseWaitTimer.Change(-1, -1);
  // если пришел последний пакет
  if (header.Flags.HasFlag(ReliableUdpHeaderFlags.LastPacket))
  {
    Interlocked.Increment(ref connectionRecord.IsLastPacketReceived);
  }
  // если нам пришли все пакеты окна, то сбрасываем счетчик
  // и высылаем пакет подтверждение
  else if (connectionRecord.PacketCounter == connectionRecord.WindowSize)
  {
    // сбрасываем счетчик.      
    connectionRecord.PacketCounter = 0;
    // сдвинули окно передачи
    connectionRecord.WindowLowerBound += connectionRecord.WindowSize;
    // обнуление массива управления передачей
    connectionRecord.WindowControlArray.Nullify();
    ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
  }
  // если последний пакет уже имеется        
  if (Thread.VolatileRead(ref connectionRecord.IsLastPacketReceived) != 0)
  {
    // проверяем пакеты          
    ProcessPackets(connectionRecord);
  }
}

I stand Afsluttet Metodens eneste opgave er at sende en bekræftelse på, at beskeden er modtaget igen.
Færdig.ModtagPakke:

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

SendPacket-metoden

I stand Førstepakkeafsendelse Denne metode sender den første datapakke, eller, hvis beskeden ikke kræver leveringsbekræftelse, hele beskeden.
FørstePakkeAfsendelse.SendPakke:

public override void SendPacket(ReliableUdpConnectionRecord connectionRecord)
{
  connectionRecord.PacketCounter = 0;
  connectionRecord.SndNext = 0;
  connectionRecord.WindowLowerBound = 0;       
  // если подтверждения не требуется - отправляем все пакеты
  // и высвобождаем ресурсы
  if (connectionRecord.IsNoAnswerNeeded)
  {
    // Здесь происходит отправка As Is
    do
    {
      ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.CreateUdpPayload(connectionRecord, ReliableUdpStateTools. CreateReliableUdpHeader(connectionRecord)));
      connectionRecord.SndNext++;
    } while (connectionRecord.SndNext < connectionRecord.NumberOfPackets);
    SetAsCompleted(connectionRecord);
    return;
  }
  // создаем заголовок пакета и отправляем его 
  ReliableUdpHeader header = ReliableUdpStateTools.CreateReliableUdpHeader(connectionRecord);
  ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.CreateUdpPayload(connectionRecord, header));
  // увеличиваем счетчик
  connectionRecord.SndNext++;
  // сдвигаем окно
  connectionRecord.WindowLowerBound++;
  connectionRecord.State = connectionRecord.Tcb.States.SendingCycle;
  // Запускаем таймер
  connectionRecord.WaitForPacketsTimer = new Timer(CheckByTimer, connectionRecord, connectionRecord.ShortTimerPeriod, -1);
}

I stand Sendercyklus I denne metode sendes en blok af pakker.
SendingCycle.SendPacket:

public override void SendPacket(ReliableUdpConnectionRecord connectionRecord)
{      
  // отправляем блок пакетов      
  for (connectionRecord.PacketCounter = 0;
        connectionRecord.PacketCounter < connectionRecord.WindowSize &&
        connectionRecord.SndNext < connectionRecord.NumberOfPackets;
        connectionRecord.PacketCounter++)
  {
    ReliableUdpHeader header = ReliableUdpStateTools.CreateReliableUdpHeader(connectionRecord);
    ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.CreateUdpPayload(connectionRecord, header));
    connectionRecord.SndNext++;
  }
  // на случай большого окна передачи, перезапускаем таймер после отправки
  connectionRecord.WaitForPacketsTimer.Change( connectionRecord.ShortTimerPeriod, -1 );
  if ( connectionRecord.CloseWaitTimer != null )
  {
    connectionRecord.CloseWaitTimer.Change( -1, -1 );
  }
}

Dybere ned i koden. Oprettelse og etablering af forbindelser

Nu hvor vi er blevet bekendt med de grundlæggende tilstande og de metoder, der bruges til at håndtere tilstande, kan vi se på et par eksempler på, hvordan protokollen fungerer lidt mere detaljeret.
Dataoverførselsdiagram under normale forhold:Implementering af Reliable Udp-protokollen til .Net

Lad os se nærmere på skabelsen forbindelsesregistrering for at oprette forbindelse og sende den første pakke. Initiativtageren til overførslen er altid den applikation, der kalder API-metoden for at sende en besked. Dernæst aktiveres StartTransmission-metoden i transmissionskontrolblokken, hvilket starter datatransmissionen for den nye besked.
Oprettelse af en udgående forbindelse:

private void StartTransmission(ReliableUdpMessage reliableUdpMessage, EndPoint endPoint, AsyncResultSendMessage asyncResult)
{
  if (m_isListenerStarted == 0)
  {
    if (this.LocalEndpoint == null)
    {
      throw new ArgumentNullException( "", "You must use constructor with parameters or start listener before sending message" );
    }
    // запускаем обработку входящих пакетов
    StartListener(LocalEndpoint);
  }
  // создаем ключ для словаря, на основе EndPoint и ReliableUdpHeader.TransmissionId        
  byte[] transmissionId = new byte[4];
  // создаем случайный номер transmissionId        
  m_randomCrypto.GetBytes(transmissionId);
  Tuple<EndPoint, Int32> key = new Tuple<EndPoint, Int32>(endPoint, BitConverter.ToInt32(transmissionId, 0));
  // создаем новую запись для соединения и проверяем, 
  // существует ли уже такой номер в наших словарях
  if (!m_listOfHandlers.TryAdd(key, new ReliableUdpConnectionRecord(key, this, reliableUdpMessage, asyncResult)))
  {
    // если существует – то повторно генерируем случайный номер 
    m_randomCrypto.GetBytes(transmissionId);
    key = new Tuple<EndPoint, Int32>(endPoint, BitConverter.ToInt32(transmissionId, 0));
    if (!m_listOfHandlers.TryAdd(key, new ReliableUdpConnectionRecord(key, this, reliableUdpMessage, asyncResult)))
      // если снова не удалось – генерируем исключение
      throw new ArgumentException("Pair TransmissionId & EndPoint is already exists in the dictionary");
  }
  // запустили состояние в обработку         
  m_listOfHandlers[key].State.SendPacket(m_listOfHandlers[key]);
}

Afsendelse af den første pakke (FirstPacketSending-tilstand):

public override void SendPacket(ReliableUdpConnectionRecord connectionRecord)
{
  connectionRecord.PacketCounter = 0;
  connectionRecord.SndNext = 0;
  connectionRecord.WindowLowerBound = 0;       
  // ... 
  // создаем заголовок пакета и отправляем его 
  ReliableUdpHeader header = ReliableUdpStateTools.CreateReliableUdpHeader(connectionRecord);
  ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.CreateUdpPayload(connectionRecord, header));
  // увеличиваем счетчик
  connectionRecord.SndNext++;
  // сдвигаем окно
  connectionRecord.WindowLowerBound++;
  // переходим в состояние SendingCycle
  connectionRecord.State = connectionRecord.Tcb.States.SendingCycle;
  // Запускаем таймер
  connectionRecord.WaitForPacketsTimer = new Timer(CheckByTimer, connectionRecord, connectionRecord.ShortTimerPeriod, -1);
}

Efter at have sendt den første pakke, går afsenderen ind i tilstanden Sendercyklus – vent på bekræftelse af pakkens levering.
Modtageren accepterer den sendte pakke ved hjælp af EndReceive-metoden og opretter en ny forbindelsesregistrering og sender denne pakke, med en forudparset header, til ReceivePacket-tilstandsmetoden til behandling. FørstePakkeModtaget
Oprettelse af en forbindelse på modtagersiden:

private void EndReceive(IAsyncResult ar)
{
  // ...
  // пакет получен
  // парсим заголовок пакета        
  ReliableUdpHeader header;
  if (!ReliableUdpStateTools.ReadReliableUdpHeader(bytes, out header))
  {          
    // пришел некорректный пакет - отбрасываем его
    return;
  }
  // конструируем ключ для определения connection record’а для пакета
  Tuple<EndPoint, Int32> key = new Tuple<EndPoint, Int32>(connectedClient, header.TransmissionId);
  // получаем существующую connection record или создаем новую
  ReliableUdpConnectionRecord record = m_listOfHandlers.GetOrAdd(key, new ReliableUdpConnectionRecord(key, this, header. ReliableUdpMessageType));
  // запускаем пакет в обработку в конечный автомат
  record.State.ReceivePacket(record, header, bytes);
}

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

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  if (!header.Flags.HasFlag(ReliableUdpHeaderFlags.FirstPacket))
    // отбрасываем пакет
    return;
  // ...
  // by design все packet numbers начинаются с 0;
  if (header.PacketNumber != 0)          
    return;
  // инициализируем массив для хранения частей сообщения
  ReliableUdpStateTools.InitIncomingBytesStorage(connectionRecord, header);
  // записываем данные пакет в массив
  ReliableUdpStateTools.WritePacketData(connectionRecord, header, payload);
  // считаем кол-во пакетов, которые должны прийти
  connectionRecord.NumberOfPackets = (int)Math.Ceiling((double) ((double) connectionRecord.IncomingStream.Length/(double) connectionRecord.BufferSize));
  // записываем номер последнего полученного пакета (0)
  connectionRecord.RcvCurrent = header.PacketNumber;
  // после сдвинули окно приема на 1
  connectionRecord.WindowLowerBound++;
  // переключаем состояние
  connectionRecord.State = connectionRecord.Tcb.States.Assembling;  
  if (/*если не требуется механизм подтверждение*/)
  // ...
  else
  {
    // отправляем подтверждение
    ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
    connectionRecord.WaitForPacketsTimer = new Timer(CheckByTimer, connectionRecord, connectionRecord.ShortTimerPeriod, -1);
  }
}

Dybere ned i koden. Lukker forbindelse på grund af timeout

Håndtering af timeouts er en vigtig del af pålidelig UDP. Lad os se på et eksempel, hvor en mellemliggende node fejler, og datalevering i begge retninger bliver umulig.
Diagram over lukning af forbindelsestimeout:Implementering af Reliable Udp-protokollen til .Net

Som det fremgår af diagrammet, tændes afsenderens arbejdstimer umiddelbart efter afsendelse af en blok af pakker. Dette sker i SendPacket-metoden for tilstanden Sendercyklus.
Aktivering af arbejdstimeren (SendingCycle-tilstand):

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

Timerperioder indstilles, når forbindelsen oprettes. Som standard er ShortTimerPeriod 5 sekunder. I eksemplet er den indstillet til 1,5 sekunder.

For en indgående forbindelse starter timeren efter modtagelse af den sidste indgående datapakke. Dette sker i ReceivePacket-metoden for tilstanden. Samling
Aktivering af arbejdstimeren (monteringstilstand):

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  // ... 
  // перезапускаем таймеры        
  connectionRecord.TimerSecondTry = false;
  connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
  if (connectionRecord.CloseWaitTimer != null)
    connectionRecord.CloseWaitTimer.Change(-1, -1);
  // ...
}

Der ankom ikke flere pakker på den indgående forbindelse, mens den fungerende timer ventede. Timeren startede og kaldte ProcessPackets-metoden, som registrerede de mistede pakker og sendte anmodninger om retransmission for første gang.
Afsendelse af anmodninger om omlevering (assembleringstilstand):

public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
  // ...        
  if (/*проверка на потерянные пакеты */)
  {
    // отправляем запросы на повторную доставку
    // устанавливаем таймер во второй раз, для повторной попытки передачи
    if (!connectionRecord.TimerSecondTry)
    {
      connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
    connectionRecord.TimerSecondTry = true;
    return;
    }
  // если после двух попыток срабатываний WaitForPacketTimer 
  // не удалось получить пакеты - запускаем таймер завершения соединения
  StartCloseWaitTimer(connectionRecord);
  }
  else if (/*пришел последний пакет и успешная проверка */)
  {
    // ...
    StartCloseWaitTimer(connectionRecord);
  }
  // если ack на блок пакетов был потерян
  else
  { 
    if (!connectionRecord.TimerSecondTry)
    {
      // повторно отсылаем ack
      connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
      connectionRecord.TimerSecondTry = true;
      return;
    }
    // запускаем таймер завершения соединения
    StartCloseWaitTimer(connectionRecord);
  }
}

TimerSecondTry-variablen blev indstillet til sand. Denne variabel er ansvarlig for genstart af arbejdstimeren.

På afsendersiden udløses også arbejdstimeren, og den sidst sendte pakke sendes igen.
Aktivering af timeren for forbindelseslukning (SendingCycle-tilstand):

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

Derefter startes timeren for forbindelseslukning i den udgående forbindelse.
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);
}

Timeout-perioden for forbindelseslukning er som standard 30 sekunder.

Efter kort tid udløses arbejdstimeren på modtagersiden igen, anmodninger sendes igen, hvorefter timeren for forbindelseslukning startes for den indgående forbindelse.

Når lukketimerne aktiveres, frigives alle ressourcer fra begge forbindelsesposter. Afsenderen rapporterer en leveringsfejl til upstream-applikationen (se Pålidelig UDP API).
Frigør ressourcer til forbindelsesposter:

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

Dybere ned i koden. Gendan dataoverførsel

Diagram over dataoverførselsgendannelse ved pakketab:Implementering af Reliable Udp-protokollen til .Net

Som beskrevet i afsnittet om lukning af en forbindelse ved timeout, vil modtageren kontrollere for mistede pakker, når arbejdstimeren udløber. I tilfælde af pakketab vil der blive udarbejdet en liste over pakkenumre, der ikke nåede frem til modtageren. Disse numre indtastes i LostPackets-arrayet for en specifik forbindelse, og anmodninger om omlevering sendes.
Afsendelse af anmodninger om omlevering af pakker (assembling-tilstand):

public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
  //...
  if (!ReliableUdpStateTools.CheckForNoPacketLoss(connectionRecord, connectionRecord.IsLastPacketReceived != 0))
  {
    // есть потерянные пакеты, отсылаем запросы на них
    foreach (int seqNum in connectionRecord.LostPackets)
    {
      if (seqNum != 0)
      {
        ReliableUdpStateTools.SendAskForLostPacket(connectionRecord, seqNum);
      }
    }
    // ...
  }
}

Afsenderen accepterer anmodningen om omlevering og sender de manglende pakker igen. Det er værd at bemærke, at afsenderen på dette tidspunkt allerede har startet timeren for forbindelseslukning, og at den nulstilles ved modtagelse af anmodningen.
Genafsendelse af mistede pakker (SendingCycle-tilstand):

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

Den gensendte pakke (pakke nr. 3 i diagrammet) modtages af den indgående forbindelse. Der udføres en kontrol for at se, om modtagevinduet er fuldt, og om normal dataoverførsel genoptages.
Kontrol af at komme ind i modtagervinduet (monteringstilstand):

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  // ...
  // увеличиваем счетчик пакетов        
  connectionRecord.PacketCounter++;
  // записываем в массив управления окном текущий номер пакета        
  connectionRecord.WindowControlArray[header.PacketNumber - connectionRecord.WindowLowerBound] = header.PacketNumber;
  // устанавливаем наибольший пришедший пакет        
  if (header.PacketNumber > connectionRecord.RcvCurrent)
    connectionRecord.RcvCurrent = header.PacketNumber;
  // перезапускам таймеры        
  connectionRecord.TimerSecondTry = false;
  connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
  if (connectionRecord.CloseWaitTimer != null)
    connectionRecord.CloseWaitTimer.Change(-1, -1);
  // ...
  // если нам пришли все пакеты окна, то сбрасываем счетчик
  // и высылаем пакет подтверждение
  else if (connectionRecord.PacketCounter == connectionRecord.WindowSize)
  {
    // сбрасываем счетчик.      
    connectionRecord.PacketCounter = 0;
    // сдвинули окно передачи
    connectionRecord.WindowLowerBound += connectionRecord.WindowSize;
    // обнуление массива управления передачей
    connectionRecord.WindowControlArray.Nullify();
    ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
  }
  // ...
}

API-pålidelig UDP

For at interagere med dataoverførselsprotokollen findes der en åben klasse Reliable Udp, som er en indpakning over overførselskontrolblokken. Her er de vigtigste medlemmer af klassen:

public sealed class ReliableUdp : IDisposable
{
  // получает локальную конечную точку
  public IPEndPoint LocalEndpoint    
  // создает экземпляр ReliableUdp и запускает
  // прослушивание входящих пакетов на указанном IP адресе
  // и порту. Значение 0 для порта означает использование
  // динамически выделенного порта
  public ReliableUdp(IPAddress localAddress, int port = 0) 
  // подписка на получение входящих сообщений
  public ReliableUdpSubscribeObject SubscribeOnMessages(ReliableUdpMessageCallback callback, ReliableUdpMessageTypes messageType = ReliableUdpMessageTypes.Any, IPEndPoint ipEndPoint = null)    
  // отписка от получения сообщений
  public void Unsubscribe(ReliableUdpSubscribeObject subscribeObject)
  // асинхронно отправить сообщение 
  // Примечание: совместимость с XP и Server 2003 не теряется, т.к. используется .NET Framework 4.0
  public Task<bool> SendMessageAsync(ReliableUdpMessage reliableUdpMessage, IPEndPoint remoteEndPoint, CancellationToken cToken)
  // начать асинхронную отправку сообщения
  public IAsyncResult BeginSendMessage(ReliableUdpMessage reliableUdpMessage, IPEndPoint remoteEndPoint, AsyncCallback asyncCallback, Object state)
  // получить результат асинхронной отправки
  public bool EndSendMessage(IAsyncResult asyncResult)  
  // очистить ресурсы
  public void Dispose()    
}

Modtagelse af beskeder sker via abonnement. Delegerets signatur for callback-metoden er:

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

Besked:

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

For at abonnere på en bestemt beskedtype og/eller en bestemt afsender bruges to valgfrie parametre: ReliableUdpMessageTypes messageType og IPEndPoint ipEndPoint.

Beskedtyper:

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

Afsendelse af en besked sker asynkront, og til dette formål implementerer protokollen en asynkron programmeringsmodel:

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

Resultatet af at sende en besked vil være sandt, hvis beskeden er nået frem til modtageren, og falsk, hvis forbindelsen blev lukket på grund af timeout:

public bool EndSendMessage(IAsyncResult asyncResult)

Konklusion

Meget er ikke blevet beskrevet inden for rammerne af denne artikel. Strømkoordineringsmekanismer, håndtering af undtagelser og fejl, implementering af asynkrone meddelelsesafsendelsesmetoder. Men kernen i protokollen, beskrivelsen af ​​logikken bag pakkebehandling, etablering af en forbindelse og håndtering af timeouts, burde blive klar for dig.

Den demonstrerede version af den pålidelige leveringsprotokol er ret robust og fleksibel og opfylder de tidligere definerede krav. Men jeg vil gerne tilføje, at den beskrevne implementering kan forbedres. For eksempel kan mekanismer som glidende vindue og RTT tilføjes til protokollen for at øge gennemløbshastigheden og dynamisk ændre timerperioder; Det vil også være nyttigt at implementere en mekanisme til at bestemme MTU'en mellem forbindelsesnoder (men kun i tilfælde af afsendelse af store beskeder).

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

PS For dem der er interesserede i detaljer eller bare vil teste protokollen, er her et link til projektet på GitHub:
Pålidelig UDP-projekt

Nyttige links og artikler

  1. TCP-protokolspecifikation: на английском и на русском
  2. UDP-protokolspecifikation: на английском и на русском
  3. Diskussion af RUDP-protokollen: udkast-ietf-sigtran-pålidelig-udp-00
  4. Pålidelig dataprotokol: RFC 908 и RFC 1151
  5. En simpel implementering af UDP-leveringsbekræftelse: Få fuld kontrol over dit netværk med .NET og UDP
  6. En artikel, der beskriver mekanismer til at overvinde NAT'er: Peer-to-peer-kommunikation på tværs af netværksadresseoversættere
  7. Implementering af den asynkrone programmeringsmodel: Implementering af CLR asynkron programmeringsmodel и Sådan implementerer du IAsyncResult-designmønsteret
  8. Flytning af den asynkrone programmeringsmodel til det opgavebaserede asynkrone mønster (APM til TAP):
    TPL og traditionel .NET asynkron programmering
    Interoperabilitet med andre asynkrone mønstre og typer

Opdatering: Tak borgmesterovp и sidristij for ideen om at tilføje en opgave til grænsefladen. Bibliotekets kompatibilitet med ældre operativsystemer er ikke krænket, fordi The 4th framework understøtter både XP og 2003 Server.

Kilde: www.habr.com

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