Implementering av Reliable Udp-protokollet för .Net

Internet har förändrats för länge sedan. Ett av internets huvudprotokoll - UDP används av applikationer inte bara för att leverera datagram och sändningar, utan också för att tillhandahålla "peer-to-peer"-anslutningar mellan nätverksnoder. På grund av sin enkla design har detta protokoll många tidigare oplanerade användningsområden, dock har bristerna i protokollet, såsom bristen på garanterad leverans, inte försvunnit någonstans. Den här artikeln beskriver implementeringen av det garanterade leveransprotokollet över UDP.
Innehåll:Entry
Protokollkrav
Pålitlig UDP-header
Allmänna principer för protokollet
Timeouts och protokolltimer
Tillförlitligt UDP-överföringstillståndsdiagram
Djupare in i koden. transmissionsstyrenhet
Djupare in i koden. stater

Djupare in i koden. Skapa och upprätta förbindelser
Djupare in i koden. Stänger anslutningen vid timeout
Djupare in i koden. Återställer dataöverföring
Pålitligt UDP API
Slutsats
Användbara länkar och artiklar

Entry

Internets ursprungliga arkitektur antog ett homogent adressutrymme där varje nod hade en global och unik IP-adress och kunde kommunicera direkt med andra noder. Nu har Internet faktiskt en annan arkitektur - ett område med globala IP-adresser och många områden med privata adresser gömda bakom NAT-enheter.I den här arkitekturen är det bara enheter i det globala adressutrymmet som enkelt kan kommunicera med vem som helst i nätverket eftersom de har en unik, globalt routbar IP-adress. En nod på ett privat nätverk kan ansluta till andra noder i samma nätverk, och kan även ansluta till andra välkända noder i det globala adressutrymmet. Denna interaktion uppnås till stor del på grund av nätverksadressöversättningsmekanismen. NAT-enheter, såsom Wi-Fi-routrar, skapar speciella översättningstabellposter för utgående anslutningar och ändrar IP-adresser och portnummer i paket. Detta tillåter utgående anslutningar från det privata nätverket till värdar i det globala adressutrymmet. Men samtidigt blockerar NAT-enheter vanligtvis all inkommande trafik om inte separata regler för inkommande anslutningar ställs in.

Denna arkitektur av Internet är tillräckligt korrekt för klient-serverkommunikation, där klienter kan vara i privata nätverk och servrar har en global adress. Men det skapar svårigheter för direkt koppling av två noder mellan olika privata nätverk. Direktanslutning av två noder är viktig för peer-to-peer-applikationer som röstöverföring (Skype), fjärrdatoråtkomst (TeamViewer) eller onlinespel.

En av de mest effektiva metoderna för att upprätta en peer-to-peer-anslutning mellan enheter på olika privata nätverk kallas hålslagning. Denna teknik används oftast med applikationer baserade på UDP-protokollet.

Men om din applikation behöver garanterad leverans av data, till exempel överför du filer mellan datorer, kommer användningen av UDP att ha många svårigheter på grund av det faktum att UDP inte är ett garanterat leveransprotokoll och inte tillhandahåller paketleverans i ordning, till skillnad från TCP protokoll.

I det här fallet, för att säkerställa garanterad paketleverans, krävs det att man implementerar ett applikationslagerprotokoll som tillhandahåller nödvändig funktionalitet och fungerar över UDP.

Jag vill genast notera att det finns en TCP-hålslagningsteknik för att upprätta TCP-anslutningar mellan noder i olika privata nätverk, men på grund av bristen på stöd för det av många NAT-enheter anses det vanligtvis inte vara det huvudsakliga sättet att ansluta sådana noder.

För resten av den här artikeln kommer jag bara att fokusera på implementeringen av det garanterade leveransprotokollet. Implementeringen av UDP-hålslagningstekniken kommer att beskrivas i följande artiklar.

Protokollkrav

  1. Pålitlig paketleverans implementerad genom en positiv återkopplingsmekanism (den så kallade positiva bekräftelsen)
  2. Behovet av effektiv överföring av big data, d.v.s. protokollet måste undvika onödig paketrelä
  3. Det bör vara möjligt att avbryta leveransbekräftelsemekanismen (möjligheten att fungera som ett "rent" UDP-protokoll)
  4. Möjlighet att implementera kommandoläge, med bekräftelse av varje meddelande
  5. Den grundläggande enheten för dataöverföring över protokollet måste vara ett meddelande

Dessa krav sammanfaller till stor del med kraven för Reliable Data Protocol som beskrivs i rfc 908 и rfc 1151, och jag förlitade mig på dessa standarder när jag utvecklade detta protokoll.

För att förstå dessa krav, låt oss titta på tidpunkten för dataöverföring mellan två nätverksnoder med hjälp av TCP- och UDP-protokollen. Låt i båda fallen ha ett paket förlorat.
Överföring av icke-interaktiv data över TCP:Implementering av Reliable Udp-protokollet för .Net

Som du kan se från diagrammet, i händelse av paketförlust, kommer TCP att upptäcka det förlorade paketet och rapportera det till avsändaren genom att fråga efter numret på det förlorade segmentet.
Dataöverföring via UDP-protokoll:Implementering av Reliable Udp-protokollet för .Net

UDP vidtar inga åtgärder för att upptäcka förluster. Kontroll av överföringsfel i UDP-protokollet är helt och hållet applikationens ansvar.

Feldetektering i TCP-protokollet uppnås genom att upprätta en förbindelse med en ändnod, lagra tillståndet för denna anslutning, indikera antalet skickade byte i varje pakethuvud och avisering av kvitton med användning av ett bekräftelsenummer.

Dessutom, för att förbättra prestandan (d.v.s. sända mer än ett segment utan att ta emot en bekräftelse), använder TCP-protokollet ett så kallat överföringsfönster - antalet byte data som avsändaren av segmentet förväntar sig att ta emot.

Mer information om TCP-protokollet finns i rfc 793, från UDP till rfc 768där de faktiskt definieras.

Av ovanstående är det tydligt att för att skapa ett tillförlitligt meddelandeleveransprotokoll över UDP (nedan kallat Pålitlig UDP), krävs det att implementera dataöverföringsmekanismer liknande TCP. Nämligen:

  • spara anslutningstillstånd
  • använd segmentnumrering
  • använda speciella bekräftelsepaket
  • använda en förenklad fönstermekanism för att öka protokollgenomströmningen

Dessutom behöver du:

  • signalera början av ett meddelande, för att allokera resurser för anslutningen
  • signalera slutet på ett meddelande, för att skicka det mottagna meddelandet till en uppströmsapplikation och frigöra protokollresurser
  • tillåt det anslutningsspecifika protokollet att inaktivera leveransbekräftelsemekanismen för att fungera som "ren" UDP

Pålitlig UDP-header

Kom ihåg att ett UDP-datagram är inkapslat i ett IP-datagram. Det pålitliga UDP-paketet "lindas" på lämpligt sätt i ett UDP-datagram.
Pålitlig UDP-huvudinkapsling:Implementering av Reliable Udp-protokollet för .Net

Strukturen för Reliable UDP-huvudet är ganska enkel:

Implementering av Reliable Udp-protokollet för .Net

  • Flaggor - paketkontrollflaggor
  • MessageType - meddelandetyp som används av uppströmsapplikationer för att prenumerera på specifika meddelanden
  • TransmissionId - sändningsnumret, tillsammans med mottagarens adress och port, identifierar anslutningen unikt
  • PacketNumber - paketnummer
  • Alternativ - ytterligare protokollalternativ. När det gäller det första paketet används det för att ange storleken på meddelandet

Flaggor är följande:

  • FirstPacket - det första paketet i meddelandet
  • NoAsk - meddelandet kräver ingen bekräftelsemekanism för att vara aktiverad
  • LastPacket - det sista paketet i meddelandet
  • RequestForPacket - bekräftelsepaket eller begäran om ett förlorat paket

Allmänna principer för protokollet

Eftersom Reliable UDP är inriktat på garanterad meddelandeöverföring mellan två noder måste den kunna upprätta en förbindelse med den andra sidan. För att upprätta en anslutning skickar avsändaren ett paket med FirstPacket-flaggan, vars svar kommer att innebära att anslutningen upprättas. Alla svarspaket, eller, med andra ord, bekräftelsepaket, ställer alltid in värdet på PacketNumber-fältet till ett mer än det största PacketNumber-värdet av framgångsrikt mottagna paket. Fältet Alternativ för det första paketet som skickas är storleken på meddelandet.

En liknande mekanism används för att avsluta en anslutning. LastPacket-flaggan sätts på det sista paketet i meddelandet. I svarspaketet anges numret på det sista paketet + 1, vilket för den mottagande sidan betyder framgångsrik leverans av meddelandet.
Anslutningsetablering och avslutningsdiagram:Implementering av Reliable Udp-protokollet för .Net

När anslutningen är upprättad börjar dataöverföringen. Data överförs i block av paket. Varje block, förutom det sista, innehåller ett fast antal paket. Det är lika med mottagar-/sändningsfönstrets storlek. Det sista datablocket kan ha färre paket. Efter att ha skickat varje block väntar sändningssidan på en leveransbekräftelse eller en begäran om att återleverera förlorade paket, vilket lämnar mottagnings-/sändningsfönstret öppet för att ta emot svar. Efter att ha mottagit bekräftelse på blockleverans, skiftar mottagnings-/sändningsfönstret och nästa datablock skickas.

Den mottagande sidan tar emot paketen. Varje paket kontrolleras för att se om det faller inom överföringsfönstret. Paket och dubbletter som inte faller in i fönstret elimineras. Därför att Eftersom storleken på fönstret är fast och densamma för mottagaren och avsändaren, skiftas fönstret vid leverans av ett block med paket utan förlust till att ta emot paket av nästa datablock och en leveransbekräftelse skickas . Om fönstret inte fylls inom den period som ställts in av arbetstimern kommer en kontroll att köras för att se vilka paket som inte levererades och förfrågningar om återleverans kommer att skickas.
Återsändningsdiagram:Implementering av Reliable Udp-protokollet för .Net

Timeouts och protokolltimer

Det finns flera anledningar till att en anslutning inte kan upprättas. Till exempel om den mottagande parten är offline. I det här fallet, när du försöker upprätta en anslutning, kommer anslutningen att stängas av timeout. Implementeringen av Reliable UDP använder två timers för att ställa in timeouts. Den första, arbetstimern, används för att vänta på svar från fjärrvärden. Om det avfyras på avsändarsidan skickas det senast skickade paketet igen. Om timern går ut hos mottagaren, utförs en kontroll av förlorade paket och förfrågningar om återleverans skickas.

Den andra timern är nödvändig för att stänga anslutningen om det inte finns någon kommunikation mellan noderna. För avsändarsidan startar den omedelbart efter att arbetstimern har gått ut och väntar på svar från fjärrnoden. Om det inte finns något svar inom den angivna perioden avbryts anslutningen och resurser frigörs. För den mottagande sidan startas anslutningsstängningstimern efter att arbetstimern gått ut två gånger. Detta är nödvändigt för att försäkra dig mot förlust av bekräftelsepaketet. När timern går ut avbryts också anslutningen och resurser frigörs.

Tillförlitligt UDP-överföringstillståndsdiagram

Principerna för protokollet implementeras i en ändlig tillståndsmaskin, vars tillstånd ansvarar för en viss logik för paketbearbetning.
Tillförlitligt UDP-tillståndsdiagram:

Implementering av Reliable Udp-protokollet för .Net

Stängt - är egentligen inte ett tillstånd, det är en start- och slutpunkt för automaten. För staten Stängt ett överföringskontrollblock tas emot, vilket, implementerande en asynkron UDP-server, vidarebefordrar paket till lämpliga anslutningar och startar tillståndsbehandling.

FirstPacketSending – i vilket utgångsläge den utgående anslutningen är när ett meddelande skickas.

I detta tillstånd skickas det första paketet för vanliga meddelanden. För meddelanden utan sändningsbekräftelse är detta det enda tillståndet där hela meddelandet skickas.

SendingCycle – grundtillstånd för överföring av meddelandepaket.

Övergång till det från staten FirstPacketSending utförs efter att det första paketet i meddelandet har skickats. Det är i detta tillstånd som alla bekräftelser och förfrågningar om återsändningar kommer. Det är möjligt att lämna det i två fall - vid framgångsrik leverans av meddelandet eller vid timeout.

FirstPacket Received – utgångsläget för mottagaren av meddelandet.

Den kontrollerar riktigheten av början av överföringen, skapar de nödvändiga strukturerna och skickar en bekräftelse på mottagandet av det första paketet.

För ett meddelande som består av ett enda paket och skickades utan användning av leveransbevis är detta det enda tillståndet. Efter bearbetning av ett sådant meddelande stängs anslutningen.

montering – grundläggande tillstånd för att ta emot meddelandepaket.

Den skriver paket till tillfällig lagring, kontrollerar paketförlust, skickar bekräftelser för leverans av ett block av paket och hela meddelandet, och skickar förfrågningar om återleverans av förlorade paket. Vid framgångsrik mottagning av hela meddelandet går anslutningen till status Avslutade, annars går en timeout ut.

Avslutade – stängning av anslutningen i händelse av framgångsrik mottagning av hela meddelandet.

Detta tillstånd är nödvändigt för sammanställningen av meddelandet och för fallet då leveransbekräftelsen av meddelandet förlorades på vägen till avsändaren. Detta tillstånd avslutas med en timeout, men anslutningen anses vara stängd.

Djupare in i koden. transmissionsstyrenhet

En av nyckelelementen i Reliable UDP är transmissionskontrollblocket. Uppgiften för detta block är att lagra aktuella anslutningar och hjälpelement, distribuera inkommande paket till motsvarande anslutningar, tillhandahålla ett gränssnitt för att skicka paket till en anslutning och implementera protokoll-API. Sändningskontrollblocket tar emot paket från UDP-lagret och vidarebefordrar dem till tillståndsmaskinen för bearbetning. För att ta emot paket implementerar den en asynkron UDP-server.
Några medlemmar av 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 av 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);
}

För varje meddelandeöverföring skapas en struktur som innehåller anslutningsinformation. Denna struktur kallas anslutningspost.
Några medlemmar av 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;
  //...
}

Djupare in i koden. stater

Stater implementerar tillståndsmaskinen för Reliable UDP-protokollet, där huvudbearbetningen av paket sker. Den abstrakta klassen ReliableUdpState tillhandahåller ett gränssnitt för staten:

Implementering av Reliable Udp-protokollet för .Net

Hela logiken i protokollet implementeras av klasserna som presenteras ovan, tillsammans med en hjälpklass som tillhandahåller statiska metoder, som till exempel att konstruera ReliableUdp-huvudet från anslutningsposten.

Därefter kommer vi att i detalj överväga implementeringen av gränssnittsmetoderna som bestämmer de grundläggande algoritmerna för protokollet.

DisposeByTimeout-metoden

Metoden DisposeByTimeout är ansvarig för att frigöra anslutningsresurser efter en timeout och för att signalera framgångsrik/misslyckad meddelandeleverans.
ReliableUdpState.DisposeByTimeout:

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

Det är bara åsidosatt i staten Avslutade.
Completed.DisposeByTimeout:

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

ProcessPackets metod

ProcessPackets-metoden ansvarar för ytterligare bearbetning av ett eller flera paket. Ringde direkt eller via en paketväntetimer.

I skick montering metoden åsidosätts och ansvarar för att kontrollera för förlorade paket och övergå till tillståndet Avslutade, om du tar emot det sista paketet och klarar kontrollen
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 skick SendingCycle denna metod anropas endast på en timer och är ansvarig för att återsända det senaste meddelandet, såväl som för att starta timern för stängning av anslutningen.
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 skick Avslutade metoden stoppar drifttimern och skickar meddelandet till prenumeranterna.
Completed.ProcessPackets:

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

ReceivePacket-metoden

I skick FirstPacket Received Metodens huvuduppgift är att avgöra om det första meddelandepaketet faktiskt anlände till gränssnittet, och även att samla in ett meddelande som består av ett enda paket.
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 skick SendingCycle denna metod åsidosätts för att acceptera leveransbekräftelser och begäranden om återsändning.
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 skick montering i ReceivePacket-metoden sker huvudarbetet med att sammanställa ett meddelande från inkommande paket.
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 skick Avslutade Metodens enda uppgift är att skicka en upprepad bekräftelse på framgångsrik leverans av meddelandet.
Completed.ReceivePacket:

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

Skicka paketmetod

I skick FirstPacketSending denna metod skickar det första datapaketet, eller, om meddelandet inte kräver leveransbekräftelse, hela meddelandet.
FirstPacketSending.SendPacket:

public override void SendPacket(ReliableUdpConnectionRecord connectionRecord)
{
  connectionRecord.PacketCounter = 0;
  connectionRecord.SndNext = 0;
  connectionRecord.WindowLowerBound = 0;       
  // если подтверждения не требуется - отправляем все пакеты
  // и высвобождаем ресурсы
  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 skick SendingCycle i denna metod skickas ett block av paket.
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 );
  }
}

Djupare in i koden. Skapa och upprätta förbindelser

Nu när vi är bekanta med de grundläggande tillstånden och metoderna som används för att bearbeta tillstånd, kan vi titta lite mer i detalj på flera exempel på protokollets funktion.
Dataöverföringsdiagram under normala förhållanden:Implementering av Reliable Udp-protokollet för .Net

Betrakta skapandet i detalj anslutningspost för att ansluta och skicka det första paketet. Överföringen initieras alltid av applikationen som anropar API:et för skicka meddelande. Därefter anropas StartTransmission-metoden för överföringskontrollblocket, vilket startar överföringen av data för det nya meddelandet.
Skapa en utgående anslutning:

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

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

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 att ha skickat det första paketet går avsändaren in i tillståndet SendingCycle – vänta på bekräftelse på paketleverans.
Den mottagande sidan, med EndReceive-metoden, tar emot det skickade paketet, skapar ett nytt anslutningspost och skickar detta paket, med en förtolkad rubrik, till tillståndsmetoden ReceivePacket för bearbetning FirstPacket Received
Skapa en anslutning på den mottagande sidan:

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

Ta emot det första paketet och skicka en bekräftelse (FirstPacketReceived state):

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

Djupare in i koden. Stänger anslutningen vid timeout

Timeouthantering är en viktig del av Reliable UDP. Tänk på ett exempel där en mellannod misslyckades och dataleverans i båda riktningarna blev omöjlig.
Diagram över att stänga en anslutning genom timeout:Implementering av Reliable Udp-protokollet för .Net

Som framgår av diagrammet startar avsändarens arbetstimer omedelbart efter att ett block med paket har skickats. Detta händer i statens SendPacket-metod SendingCycle.
Aktivering av arbetstimern (SendingCycle-tillstånd):

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

Timerperioder ställs in när anslutningen skapas. Som standard är ShortTimerPeriod 5 sekunder. I exemplet är den inställd på 1,5 sekunder.

För en inkommande anslutning startar timern efter att ha tagit emot det sista inkommande datapaketet, detta sker i ReceivePacket-metoden för staten montering
Aktivera arbetstimern (monteringsläge):

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

Inga fler paket kom till den inkommande anslutningen i väntan på den fungerande timern. Timern gick av och anropade ProcessPackets-metoden, där de förlorade paketen hittades och återleveransförfrågningar skickades för första gången.
Skickar förfrågningar om återleverans (monteringsläge):

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

Variabeln TimerSecondTry är inställd på sann. Denna variabel är ansvarig för att starta om arbetstimern.

På avsändarsidan triggas även arbetstimern och det senast skickade paketet skickas om.
Aktivera anslutningsstängningstimern (SendingCycle-tillstånd):

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

Därefter startar timern för stängning av anslutningen i den utgående anslutningen.
ReliableUdpState.StartCloseWaitTimer:

protected void StartCloseWaitTimer(ReliableUdpConnectionRecord connectionRecord)
{
  if (connectionRecord.CloseWaitTimer != null)
    connectionRecord.CloseWaitTimer.Change(connectionRecord.LongTimerPeriod, -1);
  else
    connectionRecord.CloseWaitTimer = new Timer(DisposeByTimeout, connectionRecord, connectionRecord.LongTimerPeriod, -1);
}

Timertiden för anslutningsstängning är 30 sekunder som standard.

Efter en kort tid triggas arbetstimern på mottagningssidan igen, förfrågningar skickas igen, varefter anslutningsstängningstimern för den inkommande anslutningen startas

När stängningstimerna aktiveras frigörs alla resurser för båda anslutningsposterna. Avsändaren rapporterar leveransfelet till uppströmsapplikationen (se Reliable UDP API).
Frigör anslutningspostresurser:

public void Dispose()
{
  try
  {
    System.Threading.Monitor.Enter(this.LockerReceive);
  }
  finally
  {
    Interlocked.Increment(ref this.IsDone);
    if (WaitForPacketsTimer != null)
    {
      WaitForPacketsTimer.Dispose();
    }
    if (CloseWaitTimer != null)
    {
      CloseWaitTimer.Dispose();
    }
    byte[] stream;
    Tcb.IncomingStreams.TryRemove(Key, out stream);
    stream = null;
    Tcb.OutcomingStreams.TryRemove(Key, out stream);
    stream = null;
    System.Threading.Monitor.Exit(this.LockerReceive);
  }
}

Djupare in i koden. Återställer dataöverföring

Återställningsdiagram för dataöverföring vid paketförlust:Implementering av Reliable Udp-protokollet för .Net

Som redan diskuterats när anslutningen stängdes vid timeout, när arbetstimern löper ut, kommer mottagaren att leta efter förlorade paket. Vid paketförlust kommer en lista över antalet paket som inte nådde mottagaren att sammanställas. Dessa nummer läggs in i LostPackets-arrayen för en specifik anslutning, och förfrågningar om återleverans skickas.
Skickar förfrågningar om att återleverera paket (monteringsläge):

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

Avsändaren kommer att acceptera begäran om återleverans och skicka de saknade paketen. Det är värt att notera att för närvarande har avsändaren redan startat anslutningsstängningstimern och när en begäran tas emot återställs den.
Återsända förlorade paket (SendingCycle-tillstånd):

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

Det återsända paketet (paket nr 3 i diagrammet) tas emot av den inkommande anslutningen. En kontroll görs för att säkerställa att mottagningsfönstret är fullt och normal dataöverföring återställs.
Söker efter träffar i mottagningsfönstret (monteringsläge):

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ålitligt UDP API

För att interagera med dataöverföringsprotokollet finns det en öppen Reliable Udp-klass, som är ett omslag över överföringskontrollblocket. Här är de viktigaste medlemmarna i 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()    
}

Meddelanden tas emot via prenumeration. Delegera signatur för återuppringningsmetod:

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

Meddelande:

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

För att prenumerera på en specifik meddelandetyp och/eller en specifik avsändare används två valfria parametrar: ReliableUdpMessageTypes messageType och IPEndPoint ipEndPoint.

Meddelandetyper:

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

Meddelandet skickas asynkront; för detta implementerar protokollet en asynkron programmeringsmodell:

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

Resultatet av att skicka ett meddelande kommer att vara sant - om meddelandet lyckades nå mottagaren och falskt - om anslutningen stängdes av timeout:

public bool EndSendMessage(IAsyncResult asyncResult)

Slutsats

Mycket har inte beskrivits i denna artikel. Trådmatchningsmekanismer, undantags- och felhantering, implementering av asynkrona meddelandesändningsmetoder. Men kärnan i protokollet, beskrivningen av logiken för att bearbeta paket, upprätta en anslutning och hantera timeouts, bör vara tydlig för dig.

Den demonstrerade versionen av det tillförlitliga leveransprotokollet är robust och flexibelt nog att uppfylla de tidigare definierade kraven. Men jag vill tillägga att den beskrivna implementeringen kan förbättras. Till exempel, för att öka genomströmningen och dynamiskt ändra timerperioder, kan mekanismer som glidande fönster och RTT läggas till i protokollet, det kommer också att vara användbart att implementera en mekanism för att bestämma MTU mellan anslutningsnoder (men bara om stora meddelanden skickas) .

Tack för din uppmärksamhet, jag ser fram emot dina kommentarer och kommentarer.

PS För den som är intresserad av detaljerna eller bara vill testa protokollet, länken till projektet på GitHube:
Pålitligt UDP-projekt

Användbara länkar och artiklar

  1. TCP-protokollspecifikation: på engelska и på ryska
  2. UDP-protokollspecifikation: på engelska и på ryska
  3. Diskussion om RUDP-protokollet: draft-ietf-sigtran-reliable-udp-00
  4. Tillförlitligt dataprotokoll: rfc 908 и rfc 1151
  5. En enkel implementering av leveransbekräftelse över UDP: Ta total kontroll över ditt nätverk med .NET och UDP
  6. En artikel som beskriver mekanismer för att övervinna NAT: Peer-to-Peer-kommunikation över nätverksadressöversättare
  7. Implementering av den asynkrona programmeringsmodellen: Implementering av den asynkrona programmeringsmodellen CLR и Hur man implementerar designmönstret IAsyncResult
  8. Portera den asynkrona programmeringsmodellen till det uppgiftsbaserade asynkrona mönstret (APM i TAP):
    TPL och traditionell .NET asynkron programmering
    Interoperera med andra asynkrona mönster och typer

Uppdatering: Tack mayorovp и sidristij för idén att lägga till en uppgift i gränssnittet. Bibliotekets kompatibilitet med gamla operativsystem kränks inte, eftersom Det fjärde ramverket stöder både XP- och 4-servern.

Källa: will.com

Lägg en kommentar