Implementering af Reliable Udp-protokollen til .Net

Internettet har ændret sig for længe siden. En af internettets hovedprotokoller - UDP bruges af applikationer ikke kun til at levere datagrammer og udsendelser, men også til at give "peer-to-peer" forbindelser mellem netværksknuder. På grund af dets enkle design har denne protokol mange tidligere uplanlagte anvendelser, dog er manglerne ved protokollen, såsom manglen på garanteret levering, ikke forsvundet nogen steder. Denne artikel beskriver implementeringen af ​​den garanterede leveringsprotokol over UDP.
Indhold:Indrejse
Protokolkrav
Pålidelig UDP-header
Generelle principper for protokollen
Timeouts og protokoltimere
Pålidelig UDP-transmissionstilstandsdiagram
Dybere ind i koden. transmissionskontrolenhed
Dybere ind i koden. stater

Dybere ind i koden. Oprettelse og etablering af forbindelser
Dybere ind i koden. Lukker forbindelsen ved timeout
Dybere ind i koden. Gendannelse af dataoverførsel
Pålidelig UDP API
Konklusion
Nyttige links og artikler

Indrejse

Internettets oprindelige arkitektur antog et homogent adresserum, hvor hver node havde en global og 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 denne arkitektur er det kun enheder i det globale adresserum, der nemt kan kommunikere med alle på netværket, fordi de har en unik, globalt routbar IP-adresse. En node på et privat netværk kan oprette forbindelse til andre noder på samme netværk, og kan også oprette forbindelse til andre velkendte noder i det globale adresserum. Denne interaktion opnås hovedsageligt på grund af netværksadresseoversættelsesmekanismen. NAT-enheder, såsom Wi-Fi-routere, opretter specielle oversættelsestabelposter til udgående forbindelser og ændrer IP-adresser og portnumre i pakker. Dette tillader udgående forbindelser fra det private netværk til værter 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 arkitektur af internettet er korrekt nok til klient-server-kommunikation, hvor klienter kan være i private netværk, og servere har en global adresse. Men det skaber vanskeligheder for den direkte forbindelse mellem to knudepunkter forskellige private netværk. En direkte forbindelse mellem to noder er vigtig for peer-to-peer-applikationer såsom stemmetransmission (Skype), få ​​fjernadgang til en computer (TeamViewer) eller onlinespil.

En af de mest effektive metoder til at etablere en peer-to-peer-forbindelse mellem enheder på forskellige private netværk kaldes hulning. Denne teknik er mest almindeligt anvendt med applikationer baseret på UDP-protokollen.

Men hvis din applikation kræver garanteret levering af data, for eksempel overfører du filer mellem computere, så vil brugen af ​​UDP have mange vanskeligheder på grund af det faktum, at UDP ikke er en garanteret leveringsprotokol og ikke leverer pakkelevering i rækkefølge, i modsætning til TCP. protokol.

I dette tilfælde, for at sikre garanteret pakkelevering, er det nødvendigt at implementere en applikationslagsprotokol, der giver den nødvendige funktionalitet og fungerer over UDP.

Jeg vil med det samme bemærke, at der er en TCP-hulteknik til at etablere TCP-forbindelser mellem noder i forskellige private netværk, men på grund af manglende understøttelse af det af mange NAT-enheder, betragtes det normalt ikke som den vigtigste måde at forbinde sådanne noder.

I resten af ​​denne artikel vil jeg kun fokusere på implementeringen af ​​den garanterede leveringsprotokol. Implementeringen af ​​UDP-hulstanseteknikken vil blive beskrevet i de følgende artikler.

Protokolkrav

  1. Pålidelig pakkelevering implementeret gennem en positiv feedback-mekanisme (den såkaldte positive anerkendelse)
  2. Behovet for effektiv overførsel af big data, dvs. protokollen skal undgå unødvendig pakkeoverførsel
  3. Det bør være muligt at annullere leveringsbekræftelsesmekanismen (evnen til at fungere som en "ren" UDP-protokol)
  4. Evne til at implementere kommandotilstand med bekræftelse af hver besked
  5. Den grundlæggende enhed for dataoverførsel over protokollen skal være en besked

Disse krav falder stort set sammen med kravene til Reliable Data Protocol beskrevet i RFC 908 и RFC 1151, og jeg stolede på disse standarder, da jeg udviklede denne protokol.

For at forstå disse krav, lad os se på timingen af ​​dataoverførsel mellem to netværksknuder ved hjælp af TCP- og UDP-protokollerne. Lad os i begge tilfælde have en pakke tabt.
Overførsel af ikke-interaktive data over TCP:Implementering af Reliable Udp-protokollen til .Net

Som du kan se på diagrammet, vil TCP i tilfælde af pakketab opdage den tabte pakke og rapportere den til afsenderen ved at bede om nummeret på det tabte segment.
Dataoverførsel via UDP-protokol:Implementering af Reliable Udp-protokollen til .Net

UDP tager ikke nogen trin til registrering af tab. Kontrol af transmissionsfejl i UDP-protokollen er helt og holdent applikationens ansvar.

Fejldetektering i TCP-protokollen opnås ved at etablere en forbindelse med en slutknude, gemme denne forbindelses tilstand, angive antallet af bytes, der sendes i hver pakkehoved, og give besked om kvitteringer ved hjælp af et bekræftelsesnummer.

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

For mere information om TCP-protokollen, se RFC 793, fra UDP til RFC 768hvor de i virkeligheden er defineret.

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

  • gem forbindelsestilstand
  • brug segmentnummerering
  • bruge specielle konfirmationspakker
  • bruge en forenklet vinduesmekanisme til at øge protokolgennemstrømningen

Derudover har du brug for:

  • signalere starten af ​​en besked for at allokere ressourcer til forbindelsen
  • signalere slutningen af ​​en meddelelse for at videregive den modtagne meddelelse til opstrømsapplikationen og frigive protokolressourcer
  • tillade den forbindelsesspecifikke protokol at deaktivere leveringsbekræftelsesmekanismen til 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" passende ind i et UDP-datagram.
Pålidelig UDP-headerindkapsling:Implementering af Reliable Udp-protokollen til .Net

Strukturen af ​​Reliable UDP-headeren er ret enkel:

Implementering af Reliable Udp-protokollen til .Net

  • Flag - pakkekontrolflag
  • MessageType - beskedtype, der bruges af upstream-applikationer til at abonnere på specifikke beskeder
  • TransmissionId - nummeret på transmissionen, sammen med adressen og porten på modtageren, identificerer entydigt forbindelsen
  • Pakkenummer - pakkenummer
  • Indstillinger - yderligere protokolindstillinger. I tilfælde af den første pakke bruges den til at angive størrelsen af ​​meddelelsen

Flag er som følger:

  • FirstPacket - den første pakke i beskeden
  • NoAsk - meddelelsen kræver ikke, at en bekræftelsesmekanisme er aktiveret
  • LastPacket - den sidste pakke i beskeden
  • RequestForPacket - bekræftelsespakke eller anmodning om en tabt pakke

Generelle principper for protokollen

Da Reliable UDP er fokuseret på garanteret meddelelsestransmission mellem to noder, skal den være i stand til at etablere en forbindelse med den anden side. For at etablere en forbindelse sender afsenderen en pakke med FirstPacket-flaget, hvis svar vil betyde, at forbindelsen er etableret. Alle svarpakker, eller med andre ord bekræftelsespakker, sætter altid værdien af ​​feltet PacketNumber til én mere end den største PacketNumber-værdi af vellykket modtagne pakker. Indstillinger-feltet for den første pakke, der sendes, er størrelsen på beskeden.

En lignende mekanisme bruges til at afslutte en forbindelse. LastPacket-flaget er sat på den sidste pakke i beskeden. I svarpakken er nummeret på den sidste pakke + 1 angivet, hvilket for den modtagende side betyder vellykket levering af beskeden.
Forbindelsesetablering og afslutningsdiagram: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 modtage/sende vinduets størrelse. Den sidste blok af data kan have færre pakker. Efter at have sendt hver blok, venter afsendersiden på en leveringsbekræftelse eller en anmodning om at genlevere tabte pakker, hvilket efterlader modtage-/transmissionsvinduet åbent for at modtage svar. Efter modtagelse af bekræftelse af bloklevering, skifter modtage/sende vinduet, og den næste blok af data sendes.

Den modtagende side modtager pakkerne. Hver pakke kontrolleres for at se, om den falder inden for transmissionsvinduet. Pakker og dubletter, der ikke falder ind i vinduet, filtreres fra. Fordi Hvis størrelsen af ​​vinduet er fast og den samme for modtageren og afsenderen, så i tilfælde af at en blok af pakker leveres uden tab, flyttes vinduet til at modtage pakker med den næste blok af data, og en leveringsbekræftelse er sendt. Hvis vinduet ikke fyldes op inden for den periode, der er indstillet af arbejdstimeren, vil der blive startet en kontrol af, hvilke pakker der ikke er blevet leveret, og anmodninger om omlevering vil blive sendt.
Gentransmissionsdiagram:Implementering af Reliable Udp-protokollen til .Net

Timeouts og protokoltimere

Der er flere grunde til, at en forbindelse ikke kan etableres. For eksempel hvis den modtagende part er offline. I dette tilfælde, når du forsøger at etablere en forbindelse, vil forbindelsen blive lukket ved timeout. Implementeringen af ​​pålidelig 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, bliver den sidst afsendte pakke sendt igen. Hvis timeren udløber hos modtageren, udføres en kontrol for mistede pakker, og anmodninger om genlevering sendes.

Den anden timer er nødvendig for at lukke forbindelsen i tilfælde af manglende kommunikation mellem noderne. For afsendersiden starter den umiddelbart efter, at arbejdstimeren udløber, og venter på svar fra den eksterne node. Hvis der ikke er noget svar inden for den angivne periode, afbrydes forbindelsen, og ressourcer frigives. For den modtagende side startes forbindelseslukningstimeren, efter at arbejdstimeren udløber to gange. Dette er nødvendigt for at sikre sig mod tab af bekræftelsespakken. Når timeren udløber, afbrydes forbindelsen også, og ressourcer frigives.

Pålidelig UDP-transmissionstilstandsdiagram

Principperne for protokollen er implementeret i en finite state-maskine, hvor hver tilstand er ansvarlig for en vis logik for pakkebehandling.
Pålideligt UDP-tilstandsdiagram:

Implementering af Reliable Udp-protokollen til .Net

Lukket - er egentlig ikke en tilstand, det er et start- og slutpunkt for automaten. For staten Lukket der modtages en transmissionskontrolblok, som ved at implementere en asynkron UDP-server videresender pakker til de passende forbindelser og starter tilstandsbehandling.

FirstPacketSending – den oprindelige tilstand, hvori den udgående forbindelse er, når beskeden sendes.

I denne tilstand sendes den første pakke til normale beskeder. For beskeder uden afsendelsesbekræftelse er dette den eneste tilstand, hvor hele beskeden sendes.

Sendercyklus – grundtilstand for transmission af meddelelsespakker.

Overgang til det fra staten FirstPacketSending udføres efter den første pakke af beskeden er blevet sendt. Det er i denne tilstand, at alle bekræftelser og anmodninger om genudsendelser kommer. Det er muligt at forlade det i to tilfælde - i tilfælde af vellykket levering af beskeden eller ved timeout.

FirstPacketReceived – den oprindelige tilstand for modtageren af ​​meddelelsen.

Den kontrollerer rigtigheden af ​​begyndelsen af ​​transmissionen, skaber de nødvendige strukturer og sender en bekræftelse på modtagelsen af ​​den første pakke.

For en meddelelse, der består af en enkelt pakke og blev sendt uden brug af bevis for levering, er dette den eneste tilstand. Efter behandling af en sådan besked lukkes forbindelsen.

Samling – grundlæggende tilstand for modtagelse af beskedpakker.

Den skriver pakker til midlertidigt lager, kontrollerer for pakketab, sender kvitteringer for levering af en blok af pakker og hele beskeden og sender anmodninger om genlevering af tabte pakker. I tilfælde af vellykket modtagelse af hele beskeden, går forbindelsen i tilstanden Afsluttet, ellers udløber en timeout.

Afsluttet – lukning af forbindelsen i tilfælde af vellykket modtagelse af hele beskeden.

Denne tilstand er nødvendig for samlingen af ​​meddelelsen og for tilfældet, hvor leveringsbekræftelsen af ​​meddelelsen gik tabt på vej til afsenderen. Denne tilstand forlades med en timeout, men forbindelsen anses for at være lukket.

Dybere ind i koden. transmissionskontrolenhed

Et af nøgleelementerne i Reliable UDP er transmissionskontrolblokken. Opgaven med denne blok er at lagre aktuelle forbindelser og hjælpeelementer, distribuere indgående pakker til de tilsvarende forbindelser, tilvejebringe en grænseflade til at sende pakker til en forbindelse og implementere protokol-API'en. Transmissionskontrolblokken modtager pakker fra UDP-laget og videresender dem til tilstandsmaskinen til behandling. For at modtage pakker implementerer den en asynkron UDP-server.
Nogle medlemmer af klassen ReliableUdpConnectionControlBlock:

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

Implementering af 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 meddelelsesoverførsel oprettes en struktur, der indeholder information om forbindelsen. Sådan en struktur kaldes forbindelsesrekord.
Nogle medlemmer af klassen ReliableUdpConnectionRecord:

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

Dybere ind i koden. stater

Stater implementerer tilstandsmaskinen i Reliable UDP-protokollen, hvor hovedbehandlingen af ​​pakker finder sted. Den abstrakte klasse ReliableUdpState giver en grænseflade til staten:

Implementering af Reliable Udp-protokollen til .Net

Hele logikken i protokollen implementeres af klasserne præsenteret ovenfor, sammen med en hjælpeklasse, der giver statiske metoder, såsom for eksempel at konstruere ReliableUdp-headeren fra forbindelsesposten.

Dernæst vil vi i detaljer overveje implementeringen af ​​grænseflademetoderne, der bestemmer protokollens grundlæggende algoritmer.

DisposeByTimeout metode

DisposeByTimeout-metoden er ansvarlig for at frigive forbindelsesressourcer efter en timeout og for at signalere vellykket/mislykket meddelelseslevering.
ReliableUdpState.DisposeByTimeout:

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

Det er kun tilsidesat i staten Afsluttet.
Completed.DisposeByTimeout:

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

ProcessPackets metode

ProcessPackets-metoden er ansvarlig for yderligere behandling af en pakke eller pakker. Ringet direkte eller via en pakkeventetimer.

I stand Samling metoden tilsidesættes og er ansvarlig for at tjekke for tabte pakker og skifte til tilstanden Afsluttet, i tilfælde af at modtage den sidste pakke og bestå en vellykket kontrol
Samling.Procespakker:

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 på en timer, og er ansvarlig for at gensende den sidste besked, samt at aktivere forbindelseslukningstimeren.
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 beskeden til abonnenterne.
Completed.Procespakker:

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

ReceivePacket metode

I stand FirstPacketReceived metodens hovedopgave er at bestemme, om den første meddelelsespakke faktisk ankom til grænsefladen, og også at indsamle en meddelelse bestående af en enkelt pakke.
FirstPacketReceived.ReceivePacket:

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  if (!header.Flags.HasFlag(ReliableUdpHeaderFlags.FirstPacket))
    // отбрасываем пакет
    return;
  // комбинация двух флагов - 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 gentransmissionsanmodninger.
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 i ReceivePacket-metoden foregår hovedarbejdet med at samle en besked fra indgående pakker.
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 genanerkendelse af den vellykkede levering af beskeden.
Completed.ReceivePacket:

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

Send pakkemetode

I stand FirstPacketSending denne metode sender den første pakke med data, eller hvis meddelelsen ikke kræver leveringsbekræftelse, hele meddelelsen.
FirstPacketSending.SendPacket:

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

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 ind i koden. Oprettelse og etablering af forbindelser

Nu hvor vi har set de grundlæggende tilstande og de metoder, der bruges til at håndtere tilstande, lad os nedbryde et par eksempler på, hvordan protokollen fungerer lidt mere detaljeret.
Datatransmissionsdiagram under normale forhold:Implementering af Reliable Udp-protokollen til .Net

Overvej i detaljer skabelsen forbindelsesrekord at forbinde og sende den første pakke. Overførslen initieres altid af den applikation, der kalder send message API. Dernæst påkaldes StartTransmission-metoden for transmissionskontrolblokken, som starter transmissionen af ​​data for den nye meddelelse.
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 pakkelevering.
Den modtagende side, ved hjælp af EndReceive-metoden, modtager den sendte pakke, opretter en ny forbindelsesrekord og sender denne pakke, med en forudparset header, til ReceivePacket-metoden for staten til behandling FirstPacketReceived
Oprettelse af en forbindelse på den modtagende side:

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 ind i koden. Lukker forbindelsen ved timeout

Timeout-håndtering er en vigtig del af Reliable UDP. Overvej et eksempel, hvor en mellemnode fejlede, og datalevering i begge retninger blev umulig.
Diagram til lukning af en forbindelse ved timeout:Implementering af Reliable Udp-protokollen til .Net

Som det kan ses af diagrammet, starter afsenderens arbejdstimer umiddelbart efter afsendelse af en blok af pakker. Dette sker i statens SendPacket-metode 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 );
}

Timerperioderne indstilles, når forbindelsen oprettes. Standard ShortTimerPeriod er 5 sekunder. I eksemplet er den sat 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 staten 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 man ventede på den fungerende timer. Timeren gik i gang og kaldte ProcessPackets-metoden, hvor de tabte pakker blev fundet, og anmodninger om omlevering blev sendt for første gang.
Sender anmodninger om omlevering (samlende tilstand):

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 er indstillet til sand. Denne variabel er ansvarlig for at genstarte arbejdstimeren.

På afsenderens side udløses også arbejdstimeren, og den sidst afsendte pakke sendes igen.
Aktiverer forbindelseslukningstimer (SendingCycle-tilstand):

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

Derefter starter lukningstimeren for forbindelsen 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);
}

Timer-timeoutperioden for forbindelseslukning er som standard 30 sekunder.

Efter kort tid udløses arbejdstimeren på modtagerens side igen, anmodninger sendes igen, hvorefter forbindelseslukningstimeren starter for den indgående forbindelse

Når lukketimerne udløses, frigives alle ressourcer i begge forbindelsesposter. Afsenderen rapporterer leveringsfejlen til upstream-applikationen (se Reliable UDP API).
Frigivelse af forbindelsesregistreringsressourcer:

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 ind i koden. Gendannelse af dataoverførsel

Datatransmissionsgendannelsesdiagram i tilfælde af pakketab:Implementering af Reliable Udp-protokollen til .Net

Som allerede diskuteret under lukning af forbindelsen ved timeout, når arbejdstimeren udløber, vil modtageren kontrollere for tabte pakker. I tilfælde af pakketab vil der blive udarbejdet en liste over antallet af pakker, der ikke nåede modtageren. Disse numre indtastes i LostPackets-arrayet for en specifik forbindelse, og anmodninger om omlevering sendes.
Afsendelse af anmodninger om genlevering af pakker (samlende 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 vil acceptere anmodningen om omlevering og sende de manglende pakker. Det er værd at bemærke, at i dette øjeblik har afsenderen allerede startet lukningstimeren for forbindelsen, og når en anmodning modtages, nulstilles den.
Gensender 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 #3 i diagrammet) modtages af den indgående forbindelse. Der foretages en kontrol for at se, om modtagevinduet er fuldt, og normal datatransmission genoprettes.
Kontrollerer for hits i modtagevinduet (samlende tilstand):

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

Pålidelig UDP API

For at interagere med dataoverførselsprotokollen er der en åben Reliable Udp-klasse, 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()    
}

Beskeder modtages med abonnement. Delegeret signatur for tilbagekaldsmetoden:

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 specifik meddelelsestype og/eller en specifik afsender bruges to valgfrie parametre: ReliableUdpMessageTypes messageType og IPEndPoint ipEndPoint.

Meddelelsestyper:

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

Meddelelsen sendes asynkront; til dette 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 nåede frem til modtageren og falsk - hvis forbindelsen blev lukket på grund af timeout:

public bool EndSendMessage(IAsyncResult asyncResult)

Konklusion

Meget er ikke blevet beskrevet i denne artikel. Trådmatchningsmekanismer, undtagelses- og fejlhåndtering, implementering af asynkrone beskedafsendelsesmetoder. Men kernen i protokollen, beskrivelsen af ​​logikken til behandling af pakker, etablering af en forbindelse og håndtering af timeouts, burde være klar for dig.

Den demonstrerede version af den pålidelige leveringsprotokol er robust og fleksibel nok til at opfylde de tidligere definerede krav. Men jeg vil tilføje, at den beskrevne implementering kan forbedres. For eksempel, for at øge gennemløbet og dynamisk ændre timerperioder, kan mekanismer såsom glidende vindue og RTT føjes til protokollen, det vil også være nyttigt at implementere en mekanisme til at bestemme MTU mellem forbindelsesknuder (men kun hvis store meddelelser sendes) .

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

PS For dem, der er interesseret i detaljerne eller bare vil teste protokollen, linket til projektet på GitHube:
Pålideligt UDP-projekt

Nyttige links og artikler

  1. TCP-protokolspecifikation: на английском и на русском
  2. UDP-protokolspecifikation: на английском и на русском
  3. Diskussion af RUDP-protokollen: draft-ietf-sigtran-reliable-udp-00
  4. Pålidelig dataprotokol: RFC 908 и RFC 1151
  5. En simpel implementering af leveringsbekræftelse over UDP: Tag total kontrol over dit netværk med .NET og UDP
  6. Artikel, der beskriver NAT-gennemløbsmekanismer: Peer-to-Peer-kommunikation på tværs af netværksadresseoversættere
  7. Implementering af den asynkrone programmeringsmodel: Implementering af CLR asynkron programmeringsmodel и Sådan implementeres IAsyncResult-designmønsteret
  8. Portering af den asynkrone programmeringsmodel til det opgavebaserede asynkrone mønster (APM i TAP):
    TPL og traditionel .NET asynkron programmering
    Interop med andre asynkrone mønstre og typer

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

Kilde: www.habr.com

Tilføj en kommentar