Internet har förĂ€ndrats mycket. En av de grundlĂ€ggande protokollen för Internet â UDP, anvĂ€nds av applikationer inte bara för att leverera datagram och sĂ€nda till flera mottagare, utan ocksĂ„ för att möjliggöra "peer-to-peer"-anslutningar mellan nĂ€tverksnoder. PĂ„ grund av sin enkla struktur har detta protokoll fĂ„tt mĂ„nga oanade anvĂ€ndningsomrĂ„den, Ă€ven om protokollets nackdelar, sĂ„som bristen pĂ„ garanterad leverans, fortfarande kvarstĂ„r. Denna artikel beskriver implementeringen av ett protokoll för garanterad leverans ovanpĂ„ UDP.
InnehÄll:
Inledning
Den ursprungliga arkitekturen för Internet förutsatte en homogen adresseringsstruktur, dĂ€r varje nod hade en global och unik IP-adress, och kunde kommunicera direkt med andra noder. Idag har Internet i praktiken en annan arkitektur â ett omrĂ„de för globala IP-adresser och mĂ„nga omrĂ„den med privata adresser, dolda bakom NAT-enheter.I en sĂ„dan arkitektur kan endast enheter som finns i det globala adressutrymmet enkelt interagera med andra i nĂ€tverket, eftersom de har en unik, globalt routbar IP-adress. En nod som befinner sig i ett privat nĂ€tverk kan ansluta till andra noder i samma nĂ€tverk samt ansluta till andra, vĂ€lkĂ€nda noder i det globala adressutrymmet. Denna interaktion uppnĂ„s delvis genom mekanismen för översĂ€ttning av nĂ€tverksadresser. NAT-enheter, till exempel Wi-Fi-routrar, skapar speciella poster i översĂ€ttningstabeller för utgĂ„ende anslutningar och modifierar IP-adresserna och portnumren i paketen. Detta möjliggör att upprĂ€tta en utgĂ„ende anslutning frĂ„n ett privat nĂ€tverk till noder i det globala adressutrymmet. Men samtidigt blockerar NAT-enheter vanligtvis all inkommande trafik om det inte finns speciella regler för inkommande anslutningar.
Den hÀr arkitekturen för Internet Àr ganska korrekt för klient-server-interaktion, dÀr klienterna kan befinna sig i privata nÀtverk medan servrar har globala adresser. Men den skapar svÄrigheter för direktanslutning mellan tvÄ noder i olika privata nÀtverk. olika privata nÀtverk. Direktanslutning mellan tvÄ noder Àr viktigt för "peer-to-peer"-applikationer, sÄsom röstsnack (Skype), fjÀrrÄtkomst till datorer (TeamViewer) eller onlinespel.
En av de mest effektiva metoderna för att etablera en peer-to-peer-anslutning mellan enheter i olika privata nÀtverk kallas "hole punching". Denna teknik anvÀnds oftast med applikationer baserade pÄ UDP-protokollet.
Men om din applikation krÀver garanterad dataleverans, till exempel om du överför filer mellan datorer, kommer det att uppstÄ mÄnga problem med att anvÀnda UDP, eftersom UDP inte Àr ett protokoll för garanterad leverans och inte sÀkerstÀller leverans av paket i ordning, till skillnad frÄn TCP-protokollet.
I sÄdana fall, för att sÀkerstÀlla garanterad paketsleverans, krÀvs det att implementera ett protokoll pÄ applikationsnivÄ som tillhandahÄller den nödvÀndiga funktionaliteten och gÄr ovanpÄ UDP.
Först och frÀmst vill jag pÄpeka att det finns en teknik för TCP hole punching, för att etablera TCP-anslutningar mellan noder i olika privata nÀtverk, men pÄ grund av bristen pÄ stöd frÄn mÄnga NAT-enheter betraktas den vanligtvis inte som det primÀra sÀttet att ansluta sÄdana noder.
I denna artikel kommer jag att fokusera enbart pÄ implementeringen av protokollet för pÄlitlig leverans. Implementeringen av tekniken för UDP hole punching kommer att beskrivas i kommande artiklar.
Krav pÄ protokollet
- PÄlitlig leverans av paket genom en mekanism för positiv bekrÀftelse (sÄ kallad positive acknowledgment)
- Behovet av effektiv överföring av stora datamÀngder, dvs. protokollet mÄste undvika onödiga retransmissioner av paket
- Det ska finnas möjlighet att avbryta bekrÀftelsemekanismen (möjlighet att fungera som ett "rent" UDP-protokoll)
- Möjlighet till implementering av kommandolÀge, med bekrÀftelse av varje meddelande
- Den grundlÀggande enheten för datatransfer genom protokollet ska vara ett meddelande
Dessa krav överensstÀmmer till stor del med kraven för Reliable Data Protocol, som beskrivs i och , och jag har baserat mig pÄ dessa standarder vid utvecklingen av detta protokoll.
För att förstÄ dessa krav, lÄt oss titta pÄ tidsdiagram över datatransfer mellan tvÄ nÀtverksnoder enligt protokollen TCP och UDP. LÄt oss anta att vi i bÄda fallen har förlorat ett paket.
Ăverföring av icke-interaktiva data via TCP:
Som framgÄr av diagrammet, vid paketförlust, kommer TCP att upptÀcka den förlorade paketet och informera avsÀndaren, och begÀra numret pÄ det förlorade segmentet.
Ăverföring av data med UDP-protokollet:
UDP vidtar inga ÄtgÀrder för att upptÀcka förluster. Felkontroll av överföringen i UDP-protokollet Àr helt upp till applikationen.
FelupptÀckning i TCP-protokollet uppnÄs genom att etablera en anslutning med den slutliga noden, bevara tillstÄndet för denna anslutning, ange nummer av skickade byte i varje pakets rubrik, samt meddelanden om mottagande med hjÀlp av bekrÀftelsenummer "acknowledge number".
Dessutom, för att öka prestandan (dvs. skicka mer Ă€n ett segment utan att fĂ„ bekrĂ€ftelse) anvĂ€nder TCP-protokollet sĂ„ kallat överföringsfönster â antalet byte data som avsĂ€ndaren av segmentet förvĂ€ntar sig att ta emot.
Mer information om TCP-protokollet kan hittas i , med UDP i , dÀr de faktiskt definieras.
FrÄn det ovanstÄende Àr det tydligt att för att skapa ett pÄlitligt meddelandeleveransprotokoll över UDP (hÀdanefter kallat Reliable UDP), Àr det nödvÀndigt att implementera liknande mekanismer för dataöverföring som TCP. Det handlar om att:
- bevara anslutningstillstÄnd
- anvÀnda segmentnumrering
- anvÀnda speciella bekrÀftelsepaket
- anvÀnda en förenklad fönstermekanism för att öka protokollets genomströmning
Dessutom krÀvs:
- att signalera början av meddelandet för att avsÀtta resurser för anslutningen
- att signalera slutet av meddelandet för att överföra det mottagna meddelandet till applikationen ovanför och frigöra protokollets resurser
- att proktollet för specifika anslutningar ska kunna stÀnga av bekrÀftelsemekanismen för leverans sÄ att det fungerar som "rent" UDP
Rubrik för Reliable UDP
Kom ihÄg att en UDP-datagram kapslas i en IP-datagram. Reliable UDP-paketet 'slÄs' dÀrmed in i en UDP-datagram.
Kapsling av Reliable UDP-headern:
Strukturen pÄ Reliable UDP-headern Àr ganska enkel:

- Flags â kontrollerande flaggor för paketet
- MessageType â meddelandetyp, anvĂ€nds av applikationer ovanför, för att prenumerera pĂ„ specifika meddelanden
- TransmissionId â överföringsnummer, tillsammans med adress och port för mottagaren, unikt identifierar anslutningen
- PacketNumber â paketsnummer
- Options â ytterligare alternativ för protokollet. I fallet med det första paketet anvĂ€nds den för att ange meddelandestorleken
Flaggor kan vara följande:
- FirstPacket â det första paketet av meddelandet
- NoAsk â meddelandet krĂ€ver inte att bekrĂ€ftelsemekanismen aktiveras
- LastPacket â det sista paketet av meddelandet
- RequestForPacket â bekrĂ€ftelsepaket eller begĂ€ran om förlorat paket
AllmÀnna principer för protokollets funktion
Eftersom Reliable UDP Àr inriktad pÄ garanterad överföring av meddelandet mellan tvÄ noder, mÄste det kunna etablera en anslutning med den andra sidan. För att etablera en anslutning skickar avsÀndarsidan ett paket med flaggan FirstPacket, svaret pÄ detta kommer att indikera att anslutningen har upprÀttats. Alla svarspaket, eller Ä sin sida, bekrÀftelsepaket, sÀtter alltid vÀrdet pÄ PacketNumber-fÀltet till ett mer Àn det största vÀrdet av PacketNumber för framgÄngsrikt mottagna paket. I Options-fÀltet för det första skickade paketet skrivs meddelandestorleken.
För att slutföra anslutningen anvÀnds en liknande mekanism. I det sista datablocket sÀtts flaggan LastPacket. I det svarande paketet anges numret pÄ det sista paketet + 1, vilket för mottagarsidan innebÀr att meddelandet har levererats framgÄngsrikt.
Diagram för att etablera och avsluta anslutning:
NÀr anslutningen har etablerats börjar dataöverföringen. Data överförs i paketblock. Varje block, förutom det sista, innehÄller ett fast antal paket. Detta antal motsvarar storleken pÄ mottagning/överföringens fönster. Det sista datablocket kan innehÄlla ett mindre antal paket. Efter att varje block har skickats vÀntar avsÀndarsidan pÄ en bekrÀftelse pÄ leverans eller en begÀran om att skicka om förlorade paket, medan fönstret för mottagning/överföring hÄlls öppet för att ta emot svar. Efter att bekrÀftelsen pÄ leverans av blocket har mottagits, flyttas fönstret för mottagning/överföring och nÀsta datablok skickas.
Mottagarsidan tar emot paketen. Varje paket kontrolleras för att se om det passar in i överföringsfönstret. Paket som inte passar in i fönstret och dubbletter sorteras bort. Eftersom storleken pÄ fönstret Àr strikt fast och identisk för bÄde mottagaren och avsÀndaren, flyttas fönstret för att ta emot paketen frÄn nÀsta datablok och ett bekrÀftelsepaket skickas. Om fönstret inte Àr fyllt inom den tid som har stÀllts in av arbetstimeren, pÄbörjas en kontroll av vilka paket som inte har levererats och begÀrningar om att skicka om de förlorade paketen skickas.
Diagram för omöverföring:
Timeouts och protokolltimer
Det finns flera anledningar till varför en anslutning kanske inte kan upprÀttas. Till exempel, om mottagarsidan Àr offline. I sÄ fall kommer anslutningen att stÀngas pÄ grund av timeout vid försök att upprÀtta anslutningen. I implementeringen av Reliable UDP anvÀnds tvÄ timers för att stÀlla in tidsgrÀnser. Den första, arbets timern, anvÀnds för att vÀnta pÄ svar frÄn den fjÀrranslutna vÀrden. Om den aktiveras pÄ avsÀndarsidan, skickas det senaste skickade paketet om. Om timern aktiveras pÄ mottagarens sida, kontrolleras det för förlorade paket och begÀrningar om att skicka om dem skickas.
Den andra timern Àr nödvÀndig för att stÀnga anslutningen om det inte finns nÄgon kommunikation mellan noderna. För avsÀndarens sida startar den omedelbart efter att arbets-timern aktiverats och vÀntar pÄ svar frÄn den avlÀgsna noden. Om inget svar ges inom den angivna perioden stÀngs anslutningen och resurserna frigörs. För mottagarens sida aktiveras stÀngnings-timern efter dubbel aktivering av arbets-timern. Detta Àr nödvÀndigt för att skydda mot förlust av bekrÀftelsepaket. NÀr timern aktiveras avslutas ocksÄ anslutningen och resurserna frigörs.
TillstÄndsdiagram för Reliable UDP-överföring
Protokollets arbetsprinciper Àr implementerade i en Àndlig automat, dÀr varje tillstÄnd ansvarar för en viss logik för paketbearbetning.
TillstÄndsdiagram för Reliable UDP:

StÀngd Àr faktiskt inte ett tillstÄnd, det Àr start- och slutpunkten för automaten. TillstÄndet StÀngd betraktas som styrblock för överföringen, som genom att implementera en asynkron UDP-server vidarebefordrar paket till motsvarande anslutningar och startar tillstÄndshanteringen.
FirstPacketSending Àr det initiala tillstÄndet för den utgÄende anslutningen nÀr meddelandet skickas.
I detta tillstĂ„nd skickas det första paketet för vanliga meddelanden. För meddelanden utan bekrĂ€ftelse pĂ„ leverans Ă€r detta det enda tillstĂ„ndet â i det sker hela meddelandets utskick.
SendingCycle Àr det huvudsakliga tillstÄndet för överföring av meddelandepaket.
ĂvergĂ„ngen till detta tillstĂ„nd frĂ„n tillstĂ„ndet FirstPacketSending sker efter att det första meddelandepaketet har skickats. Det Ă€r i detta tillstĂ„nd som alla bekrĂ€ftelser och begĂ€rningar om omöverföringar kommer. UtgĂ„ngen frĂ„n detta tillstĂ„nd Ă€r möjlig i tvĂ„ fall â vid framgĂ„ngsrik leverans av meddelandet eller vid tidsgrĂ€ns.
FirstPacketReceived Àr det initiala tillstÄndet för mottagaren av meddelandet.
I detta tillstÄnd kontrolleras korrektheten av överföringsstarten, nödvÀndiga strukturer skapas och en bekrÀftelse pÄ mottagandet av det första paketet skickas.
För ett meddelande som bestĂ„r av ett enda paket och skickas utan att anvĂ€nda leveransbekrĂ€ftelse â Ă€r detta det enda tillstĂ„ndet. Efter att detta meddelande har hanterats stĂ€ngs anslutningen.
Assembling Àr det huvudsakliga tillstÄndet för mottagning av meddelandepaket.
I den registreras paket i ett temporÀrt lac, kontrolleras för att sÀkerstÀlla att det inte finns nÄgra paketförluster, bekrÀftelser om leverans av paketblock och hela meddelanden skickas, samt förfrÄgningar om att skicka om förlorade paket. Vid framgÄngsrik mottagning av hela meddelandet gÄr anslutningen till staten Slutförd, annars görs en timeout-utgÄng.
Slutförd â stĂ€ngning av anslutningen vid framgĂ„ngsrik mottagning av hela meddelandet.
Detta tillstÄnd Àr nödvÀndigt för att samla meddelandet och för situationen nÀr bekrÀftelsen av meddelandets leverans har gÄtt förlorad pÄ vÀg till avsÀndaren. UtgÄng frÄn detta tillstÄnd sker via timeout, men anslutningen anses vara framgÄngsrikt stÀngd.
Djupare in i koden. Kontrollblock för överföring
En av de viktigaste komponenterna i Reliable UDP Ă€r överföringskontrollblocket. Uppgiften för detta block Ă€r att lagra aktuella anslutningar och hjĂ€lpelement, fördela inkommande paket till motsvarande anslutningar, tillhandahĂ„lla grĂ€nssnitt för att skicka paket till anslutningen och implementera protokollets API. Ăverföringskontrollblocket tar emot paket frĂ„n UDP-nivĂ„n och vidarebefordrar dem för behandling till en Ă€ndlig automat. För att ta emot paket har den en asynkron UDP-server implementerad.
NÄgra medlemmar i klassen ReliableUdpConnectionControlBlock:
internal class ReliableUdpConnectionControlBlock : IDisposable
{
// bytearray för angiven nyckel. AnvÀnds för att sammanstÀlla inkommande meddelanden
public ConcurrentDictionary<Tuple, byte[]> IncomingStreams { get; private set;}
// bytearray för angiven nyckel. AnvÀnds för att skicka utgÄende meddelanden.
public ConcurrentDictionary<Tuple, byte[]> OutcomingStreams { get; private set; }
// anslutningspost för angiven nyckel.
private readonly ConcurrentDictionary<Tuple, ReliableUdpConnectionRecord> m_listOfHandlers;
// lista över prenumeranter pÄ meddelanden.
private readonly List m_subscribers;
// lokal socket
private Socket m_socketIn;
// port för inkommande meddelanden
private int m_port;
// lokal IP-adress
private IPAddress m_ipAddress;
// lokal slutpunkt
public IPEndPoint LocalEndpoint { get; private set; }
// samling av förhandsinitialiserade
// tillstÄnd för den Àndliga automaten
public StatesCollection States { get; private set; }
// slumptalsgenerator. AnvÀnds för att skapa TransmissionId
private readonly RNGCryptoServiceProvider m_randomCrypto;
//...
}
Implementering av en asynkron UDP-server:
private void Receive()
{
EndPoint connectedClient = new IPEndPoint(IPAddress.Any, 0);
// skapar en ny buffer för varje socket.BeginReceiveFrom
byte[] buffer = new byte[DefaultMaxPacketSize + ReliableUdpHeader.Length];
// överför bufferten som parameter till den asynkrona metoden
this.m_socketIn.BeginReceiveFrom(buffer, 0, buffer.Length, SocketFlags.None, ref connectedClient, EndReceive, buffer);
}
private void EndReceive(IAsyncResult ar)
{
EndPoint connectedClient = new IPEndPoint(IPAddress.Any, 0);
int bytesRead = this.m_socketIn.EndReceiveFrom(ar, ref connectedClient);
// paketet har mottagits, redo att ta emot nÀsta
Receive();
// eftersom det enklaste sÀttet att hantera bufferten Àr att fÄ en referens till den
// frÄn IAsyncResult.AsyncState
byte[] bytes = ((byte[]) ar.AsyncState).Slice(0, bytesRead);
// hÀmta paketets header
ReliableUdpHeader header;
if (!ReliableUdpStateTools.ReadReliableUdpHeader(bytes, out header))
{
// felaktigt paket mottaget - avvisar det
return;
}
// konstruera nyckeln för att identifiera connection record för paketet
Tuple key = new Tuple(connectedClient, header.TransmissionId);
// hÀmta befintlig connection record eller skapa en ny
ReliableUdpConnectionRecord record = m_listOfHandlers.GetOrAdd(key, new ReliableUdpConnectionRecord(key, this, header.ReliableUdpMessageType));
// starta paketet i automatens behandling
record.State.ReceivePacket(record, header, bytes);
}
För varje meddelandetransfer skapas en struktur som innehÄller information om anslutningen. SÄdan struktur kallas connection record.
NÄgra medlemmar av klassen ReliableUdpConnectionRecord:
intern klass ReliableUdpConnectionRecord : IDisposable
{
// byte-array med meddelande
public byte[] IncomingStream { get; set; }
// referens till tillstÄndet för den slutna automaten
public ReliableUdpState State { get; set; }
// par som entydigt identifierar connection record
// i överföringskontrollblocket
public Tuple Key { get; private set;}
// nedre grÀns för mottagningsfönstret
public int WindowLowerBound;
// storlek pÄ överföringsfönstret
public readonly int WindowSize;
// paketnummer för att skicka
public int SndNext;
// antal paket som ska skickas
public int NumberOfPackets;
// överföringsnummer (detta Àr den andra delen av Tuple)
// för varje meddelande sin
public readonly Int32 TransmissionId;
// avlĂ€gsen IP-endpoint â sjĂ€lva mottagaren av meddelandet
public readonly IPEndPoint RemoteClient;
// storlek pÄ paketet för att undvika fragmentering pÄ IP-nivÄ
// fĂ„r inte överskrida MTU â (IP.Header + UDP.Header + RelaibleUDP.Header)
public readonly int BufferSize;
// block för överföringskontroll
public readonly ReliableUdpConnectionControlBlock Tcb;
// inkapslar resultaten av den asynkrona operationen för BeginSendMessage/EndSendMessage
public readonly AsyncResultSendMessage AsyncResult;
// inte skicka bekrÀftelsepaket
public bool IsNoAnswerNeeded;
// senaste korrekt mottagna paket (alltid instÀllt pÄ det största numret)
public int RcvCurrent;
// array med nummer för förlorade paket
public int[] LostPackets { get; private set; }
// kom det senaste paketet? AnvÀnds som bool.
public int IsLastPacketReceived = 0;
//...
}
Djupare in i koden. TillstÄnd
TillstÄnd implementerar den slutna automaten för protokollet Reliable UDP, dÀr huvudarbetet med paketen sker. Den abstrakta klassen ReliableUdpState tillhandahÄller ett grÀnssnitt för tillstÄndet:

All logik för protokollets funktionalitet implementeras av de ovan presenterade klasserna, tillsammans med en hjÀlparklass som tillhandahÄller statiska metoder, sÄsom exempelvis konstruktion av ReliableUdp-huvudet frÄn connection record.
Nedan kommer vi att gÄ igenom detaljerna i implementeringen av grÀnssnittets metoder som definierar protokollets huvudalgoritmer.
Metod DisposeByTimeout
Metoden DisposeByTimeout ansvarar för att frigöra anslutningsresurser vid timeout och för att signalera om ett meddelande har levererats framgÄngsrikt/inte framgÄngsrikt.
ReliableUdpState.DisposeByTimeout:
protected virtual void DisposeByTimeout(object record)
{
ReliableUdpConnectionRecord connectionRecord = (ReliableUdpConnectionRecord) record;
if (record.AsyncResult != null)
{
connectionRecord.AsyncResult.SetAsCompleted(false);
}
connectionRecord.Dispose();
}
Det Àr Äsidosatt endast i tillstÄndet Slutförd.
Completed.DisposeByTimeout:
protected override void DisposeByTimeout(object record)
{
ReliableUdpConnectionRecord connectionRecord = (ReliableUdpConnectionRecord) record;
// meddelar om framgÄngsrik mottagning av meddelande
SetAsCompleted(connectionRecord);
}
Metod ProcessPackets
Metoden ProcessPackets ansvarar för ytterligare behandling av ett eller flera paket. Den anropas direkt eller via en paketfördröjningstimer.
I tillstÄnd Assembling metoden Àr överskriven och ansvarar för att kontrollera förlorade paket och byta till tillstÄnd Slutförd, vid mottagandet av det sista paketet och framgÄngsrik verifiering
Assembling.ProcessPackets:
public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
if (connectionRecord.IsDone != 0)
return;
if (!ReliableUdpStateTools.CheckForNoPacketLoss(connectionRecord, connectionRecord.IsLastPacketReceived != 0))
{
// det finns förlorade paket, begÀr dem
foreach (int seqNum in connectionRecord.LostPackets)
{
if (seqNum != 0)
{
ReliableUdpStateTools.SendAskForLostPacket(connectionRecord, seqNum);
}
}
// stÀller in en timer för andra försöket att skicka
if (!connectionRecord.TimerSecondTry)
{
connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
connectionRecord.TimerSecondTry = true;
return;
}
// om efter tvÄ försök med WaitForPacketTimer
// inte kan fÄ paketen - startar stÀngningstimer
StartCloseWaitTimer(connectionRecord);
}
else if (connectionRecord.IsLastPacketReceived != 0)
// framgÄngsrik verifiering
{
// skickar bekrÀftelse pÄ mottagande av datablok
ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
connectionRecord.State = connectionRecord.Tcb.States.Completed;
connectionRecord.State.ProcessPackets(connectionRecord);
// istÀllet för omedelbar implementering av resurser
// startar vi en timer, ifall
// ifall sista ack inte nÄr avsÀndaren och denne begÀr det igen.
// pÄ timerutlösning - implementerar vi resurser
// i staten Completed Àr timer metoden överskriven
StartCloseWaitTimer(connectionRecord);
}
// detta Àr fallet nÀr ack för databloken har förlorats
else
{
if (!connectionRecord.TimerSecondTry)
{
ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
connectionRecord.TimerSecondTry = true;
return;
}
// startar stÀngningstimer
StartCloseWaitTimer(connectionRecord);
}
}
I tillstÄnd SendingCycle denna metod anropas endast via timern och ansvarar för att Äter skicka det senaste meddelandet samt aktivera stÀngningstimern.
SendingCycle.ProcessPackets:
public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
if (connectionRecord.IsDone != 0)
return;
// skickar om sista paketet
// (vid ÄterstÀllande av anslutning kommer mottagarens nod att skicka nya förfrÄgningar för de som inte nÄtt fram)
ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.RetransmissionCreateUdpPayload(connectionRecord, connectionRecord.SndNext - 1));
// aktiverar CloseWait-timern â för att vĂ€nta pĂ„ Ă„terstĂ€llande av anslutning eller dess avslutande
StartCloseWaitTimer(connectionRecord);
}
I tillstÄnd Slutförd metoden stoppar arbets timern och skickar ett meddelande till prenumeranter.
Completed.ProcessPackets:
public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
if (connectionRecord.WaitForPacketsTimer != null)
connectionRecord.WaitForPacketsTimer.Dispose();
// samlar meddelandet och skickar det till prenumeranter
ReliableUdpStateTools.CreateMessageFromMemoryStream(connectionRecord);
}
Metod ReceivePacket
I tillstÄnd FirstPacketReceived den huvudsakliga uppgiften för metoden Àr att avgöra om det första paketet av meddelandet verkligen kom till grÀnssnittet, samt att samla ett meddelande bestÄende av ett enda paket.
FirstPacketReceived.ReceivePacket:
public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
if (!header.Flags.HasFlag(ReliableUdpHeaderFlags.FirstPacket))
// ignorera paketet
return;
// kombinationen av tvÄ flaggor - FirstPacket och LastPacket - indikerar att vi har ett enda meddelande
if (header.Flags.HasFlag(ReliableUdpHeaderFlags.FirstPacket) &
header.Flags.HasFlag(ReliableUdpHeaderFlags.LastPacket))
{
ReliableUdpStateTools.CreateMessageFromSinglePacket(connectionRecord, header, payload.Slice(ReliableUdpHeader.Length, payload.Length));
if (!header.Flags.HasFlag(ReliableUdpHeaderFlags.NoAsk))
{
// skickar bekrÀftelsepaket
ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
}
SetAsCompleted(connectionRecord);
return;
}
// enligt design börjar alla paketnummer med 0;
if (header.PacketNumber != 0)
return;
ReliableUdpStateTools.InitIncomingBytesStorage(connectionRecord, header);
ReliableUdpStateTools.WritePacketData(connectionRecord, header, payload);
// rÀknar antalet paket som förvÀntas komma
connectionRecord.NumberOfPackets = (int)Math.Ceiling((double)((double)connectionRecord.IncomingStream.Length / (double)connectionRecord.BufferSize));
// registrera det senaste mottagna paketnumret (0)
connectionRecord.RcvCurrent = header.PacketNumber;
// flyttar sedan mottagningens fönster med 1
connectionRecord.WindowLowerBound++;
// vÀxlar tillstÄndet
connectionRecord.State = connectionRecord.Tcb.States.Assembling;
// om ingen bekrÀftelsemekanism krÀvs
// starta en timer som frigör alla strukturer
if (header.Flags.HasFlag(ReliableUdpHeaderFlags.NoAsk))
{
connectionRecord.CloseWaitTimer = new Timer(DisposeByTimeout, connectionRecord, connectionRecord.ShortTimerPeriod, -1);
}
else
{
ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
connectionRecord.WaitForPacketsTimer = new Timer(CheckByTimer, connectionRecord, connectionRecord.ShortTimerPeriod, -1);
}
}
I tillstÄnd SendingCycle denna metod Àr överskriven för att ta emot bekrÀftelser om leverans och begÀrningar om omöverföring.
SendingCycle.ReceivePacket:
public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
if (connectionRecord.IsDone != 0)
return;
if (!header.Flags.HasFlag(ReliableUdpHeaderFlags.RequestForPacket))
return;
// berÀkning av slutgrÀnsen för fönstret
// grÀnsen för fönstret + 1, för att fÄ leveransbekrÀftelse
int windowHighestBound = Math.Min((connectionRecord.WindowLowerBound + connectionRecord.WindowSize), (connectionRecord.NumberOfPackets));
// kontrollera om det ligger inom fönstret
if (header.PacketNumber windowHighestBound)
return;
connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
if (connectionRecord.CloseWaitTimer != null)
connectionRecord.CloseWaitTimer.Change(-1, -1);
// kontrollera sista paketet:
if (header.PacketNumber == connectionRecord.NumberOfPackets)
{
// överföringen Àr klar
Interlocked.Increment(ref connectionRecord.IsDone);
SetAsCompleted(connectionRecord);
return;
}
// detta Àr svar pÄ första paketet med bekrÀftelse
if ((header.Flags.HasFlag(ReliableUdpHeaderFlags.FirstPacket) && header.PacketNumber == 1))
{
// utan skiftning av fönstret
SendPacket(connectionRecord);
}
// bekrÀftelse pÄ mottagning av datablok
else if (header.PacketNumber == windowHighestBound)
{
// skiftar fönstret för mottagning/överföring
connectionRecord.WindowLowerBound += connectionRecord.WindowSize;
// ÄterstÀll kontrollarrayen för överföring
connectionRecord.WindowControlArray.Nullify();
// skicka paketblock
SendPacket(connectionRecord);
}
// detta Ă€r en begĂ€ran om Ă„teröverföring â skickar det begĂ€rda paketet
else
ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.RetransmissionCreateUdpPayload(connectionRecord, header.PacketNumber));
}
I tillstÄnd Assembling I metoden ReceivePacket sker det huvudsakliga arbetet med att sammanstÀlla meddelandet frÄn de inkommande paketen.
SammanstÀllning.ReceivePacket:
public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
if (connectionRecord.IsDone != 0)
return;
// behandling av paket med inaktiv bekrÀftelsemekanism
if (header.Flags.HasFlag(ReliableUdpHeaderFlags.NoAsk))
{
// ÄterstÀll timer
connectionRecord.CloseWaitTimer.Change(connectionRecord.LongTimerPeriod, -1);
// skriv data
ReliableUdpStateTools.WritePacketData(connectionRecord, header, payload);
// om vi har fÄtt paket med sista flaggan - avsluta
if (header.Flags.HasFlag(ReliableUdpHeaderFlags.LastPacket))
{
connectionRecord.State = connectionRecord.Tcb.States.Completed;
connectionRecord.State.ProcessPackets(connectionRecord);
}
return;
}
// berÀkna övre grÀns för fönstret
int windowHighestBound = Math.Min((connectionRecord.WindowLowerBound + connectionRecord.WindowSize - 1), (connectionRecord.NumberOfPackets - 1));
// filtrera bort paket som inte finns i fönstret
if (header.PacketNumber (windowHighestBound))
return;
// filtrera bort dubbletter
if (connectionRecord.WindowControlArray.Contains(header.PacketNumber))
return;
// skriv data
ReliableUdpStateTools.WritePacketData(connectionRecord, header, payload);
// öka paketrÀknaren
connectionRecord.PacketCounter++;
// skriv in det aktuella paketnumret i kontrollarrayen
connectionRecord.WindowControlArray[header.PacketNumber - connectionRecord.WindowLowerBound] = header.PacketNumber;
// stÀll in det största mottagna paketnumret
if (header.PacketNumber > connectionRecord.RcvCurrent)
connectionRecord.RcvCurrent = header.PacketNumber;
// ÄterstÀll timers
connectionRecord.TimerSecondTry = false;
connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
if (connectionRecord.CloseWaitTimer != null)
connectionRecord.CloseWaitTimer.Change(-1, -1);
// om sista paket kommit
if (header.Flags.HasFlag(ReliableUdpHeaderFlags.LastPacket))
{
Interlocked.Increment(ref connectionRecord.IsLastPacketReceived);
}
// om vi har fÄtt alla paket i fönstret, ÄterstÀll rÀknaren
// och skicka bekrÀftelsepaket
else if (connectionRecord.PacketCounter == connectionRecord.WindowSize)
{
// ÄterstÀll rÀknaren.
connectionRecord.PacketCounter = 0;
// flytta överföringsfönstret
connectionRecord.WindowLowerBound += connectionRecord.WindowSize;
// nollstÀll överföringskontrollarrayen
connectionRecord.WindowControlArray.Nullify();
ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
}
// om sista paket redan Àr mottaget
if (Thread.VolatileRead(ref connectionRecord.IsLastPacketReceived) != 0)
{
// kontrollera paketen
ProcessPackets(connectionRecord);
}
}
I tillstÄnd Slutförd den enda uppgiften för metoden Àr att skicka en ny bekrÀftelse om framgÄngsrik leverans av meddelandet.
Completed.ReceivePacket:
public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
// om sÀnda om den senaste packet igen pÄ grund av,
// att den senaste ack inte nÄdde avsÀndaren
if (header.Flags.HasFlag(ReliableUdpHeaderFlags.LastPacket))
{
ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
}
}
Metod SendPacket
I tillstÄnd FirstPacketSending denna metod skickar det första datapakets, eller, om meddelandet inte krÀver leveransbekrÀftelse - hela meddelandet.
FirstPacketSending.SendPacket:
public override void SendPacket(ReliableUdpConnectionRecord connectionRecord)
{
connectionRecord.PacketCounter = 0;
connectionRecord.SndNext = 0;
connectionRecord.WindowLowerBound = 0;
// om ingen bekrÀftelse behövs - skicka alla paket
// och frigör resurser
if (connectionRecord.IsNoAnswerNeeded)
{
// HÀr sker sÀndningen As Is
do
{
ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.CreateUdpPayload(connectionRecord, ReliableUdpStateTools.CreateReliableUdpHeader(connectionRecord)));
connectionRecord.SndNext++;
} while (connectionRecord.SndNext < connectionRecord.NumberOfPackets);
SetAsCompleted(connectionRecord);
return;
}
// skapa paketets header och skicka den
ReliableUdpHeader header = ReliableUdpStateTools.CreateReliableUdpHeader(connectionRecord);
ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.CreateUdpPayload(connectionRecord, header));
// öka rÀknaren
connectionRecord.SndNext++;
// flytta fönstret
connectionRecord.WindowLowerBound++;
connectionRecord.State = connectionRecord.Tcb.States.SendingCycle;
// Starta timern
connectionRecord.WaitForPacketsTimer = new Timer(CheckByTimer, connectionRecord, connectionRecord.ShortTimerPeriod, -1);
}
I tillstÄnd SendingCycle i den hÀr metoden sker sÀndningen av en paketblock.
SendingCycle.SendPacket:
public override void SendPacket(ReliableUdpConnectionRecord connectionRecord)
{
// skicka en paketblock
for (connectionRecord.PacketCounter = 0;
connectionRecord.PacketCounter < connectionRecord.WindowSize &&
connectionRecord.SndNext < connectionRecord.NumberOfPackets;
connectionRecord.PacketCounter++)
{
ReliableUdpHeader header = ReliableUdpStateTools.CreateReliableUdpHeader(connectionRecord);
ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.CreateUdpPayload(connectionRecord, header));
connectionRecord.SndNext++;
}
// vid en stor sÀndningsfönster, Äterstarta timern efter sÀndningen
connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
if (connectionRecord.CloseWaitTimer != null)
{
connectionRecord.CloseWaitTimer.Change(-1, -1);
}
}
Djupare in i koden. Skapande och upprÀttande av anslutningar
Nu nÀr vi har bekantat oss med de grundlÀggande tillstÄnden och metoderna som anvÀnds för att hantera tillstÄnd, kan vi gÄ lite mer pÄ djupet med nÄgra exempel pÄ protokollets arbete.
Diagram över datalÀmning under normala förhÄllanden:
LÄt oss ta en nÀrmare titt pÄ skapandet connection record för att etablera en koppling och skicka den första datapaketet. Initiativtagaren till överföringen Àr alltid applikationen som anropar API-metoden för att skicka meddelandet. DÀrefter anvÀnds metoden StartTransmission frÄn kontrollblocket för överföring, som pÄbörjar dataöverföringen för det nya meddelandet.
Skapande av en utgÄende koppling:
private void StartTransmission(ReliableUdpMessage reliableUdpMessage, EndPoint endPoint, AsyncResultSendMessage asyncResult)
{
if (m_isListenerStarted == 0)
{
if (this.LocalEndpoint == null)
{
throw new ArgumentNullException( "", "Du mÄste anvÀnda konstruktör med parametrar eller starta lyssnaren innan du skickar meddelande" );
}
// startar bearbetningen av inkommande paket
StartListener(LocalEndpoint);
}
// skapar nyckel för ordboken baserat pÄ EndPoint och ReliableUdpHeader.TransmissionId
byte[] transmissionId = new byte[4];
// skapar ett slumpmÀssigt nummer transmissionId
m_randomCrypto.GetBytes(transmissionId);
Tuple key = new Tuple(endPoint, BitConverter.ToInt32(transmissionId, 0));
// skapar en ny post för kopplingen och kontrollerar om det finns redan ett sÄdant nummer i vÄra ordböcker
if (!m_listOfHandlers.TryAdd(key, new ReliableUdpConnectionRecord(key, this, reliableUdpMessage, asyncResult)))
{
// om det finns â generera ett nytt slumpmĂ€ssigt nummer
m_randomCrypto.GetBytes(transmissionId);
key = new Tuple(endPoint, BitConverter.ToInt32(transmissionId, 0));
if (!m_listOfHandlers.TryAdd(key, new ReliableUdpConnectionRecord(key, this, reliableUdpMessage, asyncResult)))
// om det fortfarande misslyckas â generera ett undantag
throw new ArgumentException("Par TransmissionId & EndPoint finns redan i ordboken");
}
// har startat tillstÄndet för behandling
m_listOfHandlers[key].State.SendPacket(m_listOfHandlers[key]);
}
Skickar det första paketet (tillstÄnd FirstPacketSending):
public override void SendPacket(ReliableUdpConnectionRecord connectionRecord)
{
connectionRecord.PacketCounter = 0;
connectionRecord.SndNext = 0;
connectionRecord.WindowLowerBound = 0;
// ...
// skapar paketets header och skickar den
ReliableUdpHeader header = ReliableUdpStateTools.CreateReliableUdpHeader(connectionRecord);
ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.CreateUdpPayload(connectionRecord, header));
// ökar rÀknaren
connectionRecord.SndNext++;
// justerar fönstret
connectionRecord.WindowLowerBound++;
// gÄr in i tillstÄndet SendingCycle
connectionRecord.State = connectionRecord.Tcb.States.SendingCycle;
// startar timern
connectionRecord.WaitForPacketsTimer = new Timer(CheckByTimer, connectionRecord, connectionRecord.ShortTimerPeriod, -1);
}
Efter att det första paketet har skickats övergĂ„r avsĂ€ndaren till tillstĂ„nd SendingCycle â vĂ€ntar pĂ„ bekrĂ€ftelse av paketets leverans.
Mottagarsidan, med hjÀlp av metoden EndReceive, tar emot det skickade paketet, skapar en ny connection record och vidarebefordrar detta paket, med en förhandsparserad header, för behandling till metoden ReceivePacket i tillstÄndet FirstPacketReceived
Skapa anslutning pÄ mottagarsidan:
private void EndReceive(IAsyncResult ar)
{
// ...
// paket mottaget
// parsar paketets rubrik
ReliableUdpHeader header;
if (!ReliableUdpStateTools.ReadReliableUdpHeader(bytes, out header))
{
// ett ogiltigt paket har kommit - avvisar det
return;
}
// bygger nyckel för att identifiera anslutningsposten för paketet
Tuple key = new Tuple(connectedClient, header.TransmissionId);
// hÀmtar befintlig anslutningspost eller skapar en ny
ReliableUdpConnectionRecord record = m_listOfHandlers.GetOrAdd(key, new ReliableUdpConnectionRecord(key, this, header.ReliableUdpMessageType));
// startar paketet för behandling i tillstÄndsmaskinen
record.State.ReceivePacket(record, header, bytes);
}
Mottagning av första paketet och skickar bekrÀftelse (tillstÄnd FirstPacketReceived):
public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
if (!header.Flags.HasFlag(ReliableUdpHeaderFlags.FirstPacket))
// avvisar paketet
return;
// ...
// designat sÄ att alla paketnummer börjar frÄn 0;
if (header.PacketNumber != 0)
return;
// initierar array för att lagra meddelandets delar
ReliableUdpStateTools.InitIncomingBytesStorage(connectionRecord, header);
// skriver data pÄ paketet i arrayen
ReliableUdpStateTools.WritePacketData(connectionRecord, header, payload);
// berÀknar antalet paket som ska komma
connectionRecord.NumberOfPackets = (int)Math.Ceiling((double)((double)connectionRecord.IncomingStream.Length/(double)connectionRecord.BufferSize));
// registrerar numret pÄ det sista mottagna paketet (0)
connectionRecord.RcvCurrent = header.PacketNumber;
// efter detta flyttas mottagningsfönstret fram med 1
connectionRecord.WindowLowerBound++;
// byter tillstÄnd
connectionRecord.State = connectionRecord.Tcb.States.Assembling;
if (/*om bekrÀftelsemekanismen inte krÀvs*/)
// ...
else
{
// skickar bekrÀftelse
ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
connectionRecord.WaitForPacketsTimer = new Timer(CheckByTimer, connectionRecord, connectionRecord.ShortTimerPeriod, -1);
}
}
Djupare in i koden. StÀngning av anslutningen vid timeout
Hantering av tidsgrÀnser Àr en viktig del av Reliable UDP. LÄt oss se pÄ ett exempel dÀr det har uppstÄtt ett fel vid en mellanliggande nod och dataleveransen i bÄda riktningarna blivit omöjlig.
Diagram över stÀngning av anslutning pÄ grund av tidsgrÀns:
Som kan ses i diagrammet aktiveras arbetstimer hos avsÀndaren omedelbart efter avsÀndning av paketblock. Detta sker i metoden SendPacket i tillstÄndet SendingCycle.
Aktivering av arbetstimer (tillstÄnd SendingCycle):
public override void SendPacket(ReliableUdpConnectionRecord connectionRecord)
{
// skickar paketblock
// ...
// omstartar timern efter skickande
connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
if (connectionRecord.CloseWaitTimer != null)
connectionRecord.CloseWaitTimer.Change(-1, -1);
}
Tidsperioderna för timern anges vid skapandet av anslutningen. Som standard Àr ShortTimerPeriod 5 sekunder. I exemplet Àr den satt till 1,5 sekunder.
För en inkommande anslutning börjar timern efter att den senaste mottagna datapaketet har kommit, detta sker i metoden ReceivePacket i tillstÄndet Assembling
Aktivering av arbets timern (tillstÄnd Assembling):
public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
// ...
// Äterstartar timrarna
connectionRecord.TimerSecondTry = false;
connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
if (connectionRecord.CloseWaitTimer != null)
connectionRecord.CloseWaitTimer.Change(-1, -1);
// ...
}
I den inkommande anslutningen har det under vÀntetiden för arbets timern inte kommit fler paket. Timern har utlösts och kallat metoden ProcessPackets, dÀr förlorade paket har upptÀckts och för första gÄngen har begÀrningar om omleverans skickats.
Skickande av förfrÄgningar om omleverans (tillstÄnd Assembling):
public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
// ...
if (/*kontroll av förlorade paket*/)
{
// skickar förfrÄgningar om omleverans
// stÀller in timern för andra gÄngen, för Äterförsök av överföring
if (!connectionRecord.TimerSecondTry)
{
connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
connectionRecord.TimerSecondTry = true;
return;
}
// om efter tvÄ försök av WaitForPacketTimer
// har det inte gÄtt att fÄ paketen - starta stÀngningstimer för anslutningen
StartCloseWaitTimer(connectionRecord);
}
else if (/*det sista paketet kom och kontrolleringen lyckades*/)
{
// ...
StartCloseWaitTimer(connectionRecord);
}
// om ack för paketblocket har gÄtt förlorat
else
{
if (!connectionRecord.TimerSecondTry)
{
// skickar ack pÄ nytt
connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
connectionRecord.TimerSecondTry = true;
return;
}
// startar stÀngningstimer för anslutningen
StartCloseWaitTimer(connectionRecord);
}
}
Variabeln TimerSecondTry stÀlldes in pÄ true. Denna variabel ansvarar för omstart av arbets timern.
Ăven avsĂ€ndaren utlöser arbets timern och skickar pĂ„ nytt det senaste skickade paketet.
Aktivering av stÀngningstimer för anslutningen (tillstÄnd SendingCycle):
public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
// ...
// skickar pÄ nytt det senaste paketet
// ...
// aktiverar CloseWait-timern â för att vĂ€nta pĂ„ Ă„terstĂ€llning av anslutningen eller dess avslutning
StartCloseWaitTimer(connectionRecord);
}
Efter detta startas en stÀngningstimer för den utgÄende anslutningen.
ReliableUdpState.StartCloseWaitTimer:
protected void StartCloseWaitTimer(ReliableUdpConnectionRecord connectionRecord)
{
if (connectionRecord.CloseWaitTimer != null)
connectionRecord.CloseWaitTimer.Change(connectionRecord.LongTimerPeriod, -1);
else
connectionRecord.CloseWaitTimer = new Timer(DisposeByTimeout, connectionRecord, connectionRecord.LongTimerPeriod, -1);
}
StÀngningstimern för anslutningen har en standard vÀntetid pÄ 30 sekunder.
Efter en kort stund aktiveras arbetsstimern pÄ mottagarsidan igen, förfrÄgningar skickas pÄ nytt och stÀngningstimern för den inkommande anslutningen startas.
Vid aktivering av stÀngningstimrarna frigörs alla resurser för bÄda anslutningarna. AvsÀndaren rapporterar om misslyckad leverans till överordnat applikation (se API Reliable UDP).
Frigöring av resurser för anslutningspost:
public void Dispose()
{
try
{
System.Threading.Monitor.Enter(this.LockerReceive);
}
finally
{
Interlocked.Increment(ref this.IsDone);
if (WaitForPacketsTimer != null)
{
WaitForPacketsTimer.Dispose();
}
if (CloseWaitTimer != null)
{
CloseWaitTimer.Dispose();
}
byte[] stream;
Tcb.IncomingStreams.TryRemove(Key, out stream);
stream = null;
Tcb.OutcomingStreams.TryRemove(Key, out stream);
stream = null;
System.Threading.Monitor.Exit(this.LockerReceive);
}
}
Djupare in i koden. à terstÀllning av datatransmission
Diagram över ÄterstÀllning av datapaket vid paketförlust:
Som redan diskuterats, vid timeout av stÀngningen av anslutningen kommer mottagaren att kontrollera för paketförlust efter arbetsstimern har löpt ut. Vid paketförlust kommer en lista att sammanstÀllas över paketnummer som inte nÄtt mottagaren. Dessa nummer lÀggs till i arrayen LostPackets för den specifika anslutningen och förfrÄgningar om omleverans skickas.
Skickar förfrÄgningar om omleverans av paket (tillstÄnd Assembling):
public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
//...
if (!ReliableUdpStateTools.CheckForNoPacketLoss(connectionRecord, connectionRecord.IsLastPacketReceived != 0))
{
// det finns förlorade paket, skicka förfrÄgningar om dem
foreach (int seqNum in connectionRecord.LostPackets)
{
if (seqNum != 0)
{
ReliableUdpStateTools.SendAskForLostPacket(connectionRecord, seqNum);
}
}
// ...
}
}
AvsÀndaren kommer att ta emot en begÀran om omleverans och skicka de saknade paketen. Det Àr vÀrt att notera att avsÀndarens stÀngningstimer redan Àr aktiverad vid detta tillfÀlle och kommer att ÄterstÀllas vid mottagning av begÀran.
Omleverans av förlorade paket (tillstÄnd SendingCycle):
public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
// ...
connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
// ÄterstÀllning av nedstÀngningstimer
if (connectionRecord.CloseWaitTimer != null)
connectionRecord.CloseWaitTimer.Change(-1, -1);
// ...
// detta Ă€r en begĂ€ran om retransmission â skicka den begĂ€rda paketet
else
ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.RetransmissionCreateUdpPayload(connectionRecord, header.PacketNumber));
}
Det omdetonerade paketet (packet#3 i diagrammet) tas emot av den ingÄende anslutningen. En kontroll för fyllning av mottagningsfönster genomförs och normal dataöverföring ÄterstÀlls.
Kontroll av om det ligger inom mottagningsfönstret (tillstÄnd Assembling):
public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
// ...
// öka paketrÀknaren
connectionRecord.PacketCounter++;
// skriv det aktuella paketnumret till fönsterhanteringsarrayen
connectionRecord.WindowControlArray[header.PacketNumber - connectionRecord.WindowLowerBound] = header.PacketNumber;
// stÀll in det största mottagna paketet
if (header.PacketNumber > connectionRecord.RcvCurrent)
connectionRecord.RcvCurrent = header.PacketNumber;
// starta om timrarna
connectionRecord.TimerSecondTry = false;
connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
if (connectionRecord.CloseWaitTimer != null)
connectionRecord.CloseWaitTimer.Change(-1, -1);
// ...
// om vi har fÄtt alla paket i fönstret, nollstÀll rÀknaren
// och skicka bekrÀftelsepaketet
else if (connectionRecord.PacketCounter == connectionRecord.WindowSize)
{
// nollstÀll rÀknaren.
connectionRecord.PacketCounter = 0;
// flyttade överföringsfönstret
connectionRecord.WindowLowerBound += connectionRecord.WindowSize;
// nollstÀll överföringshanteringsarrayen
connectionRecord.WindowControlArray.Nullify();
ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
}
// ...
}
API för Reliable UDP
För interaktion med dataöverföringsprotokollet finns en öppen klass Reliable Udp, som Àr en omslag kring överföringskontrollblocket. HÀr Àr de viktigaste medlemmarna i klassen:
offentlig förseglad klass ReliableUdp : IDisposable
{
// fÄr den lokala Àndpunkten
offentlig IPEndPoint LocalEndpoint
// skapar en instans av ReliableUdp och startar
// lyssnar pÄ inkommande paket pÄ angiven IP-adress
// och port. VÀrde 0 för port innebÀr anvÀndning
// av en dynamiskt tilldelad port
offentlig ReliableUdp(IPAddress localAddress, int port = 0)
// prenumeration pÄ inkommande meddelanden
offentlig ReliableUdpSubscribeObject SubscribeOnMessages(ReliableUdpMessageCallback callback, ReliableUdpMessageTypes messageType = ReliableUdpMessageTypes.Any, IPEndPoint ipEndPoint = null)
// avprenumerera frÄn att ta emot meddelanden
offentlig void Unsubscribe(ReliableUdpSubscribeObject subscribeObject)
// asynkront skicka meddelande
// Obs: kompatibilitet med XP och Server 2003 gÄr inte förlorad, eftersom .NET Framework 4.0 anvÀnds
offentlig Task SendMessageAsync(ReliableUdpMessage reliableUdpMessage, IPEndPoint remoteEndPoint, CancellationToken cToken)
// starta asynkron skickning av meddelande
offentlig IAsyncResult BeginSendMessage(ReliableUdpMessage reliableUdpMessage, IPEndPoint remoteEndPoint, AsyncCallback asyncCallback, Object state)
// fÄ resultatet av den asynkrona sÀndningen
offentlig bool EndSendMessage(IAsyncResult asyncResult)
// rensa resurser
offentlig void Dispose()
}
Mottagande av meddelande sker genom prenumeration. Signaturen för delegeringen till Äteranropmetoden:
offentlig delegerad void ReliableUdpMessageCallback(ReliableUdpMessage reliableUdpMessage, IPEndPoint remoteClient);Meddelande:
offentlig klass ReliableUdpMessage
{
// meddelandetyp, enkel upprÀkning
offentlig ReliableUdpMessageTypes Type { fÄ; privat sÀtt; }
// meddelandets data
offentlig byte[] Body { fÄ; privat sÀtt; }
// om instÀllt pÄ true - bekrÀftelsemekanismen för leverans kommer att inaktiveras
// för att skicka ett specifikt meddelande
offentlig bool NoAsk { fÄ; privat sÀtt; }
}
För att prenumerera pÄ en specifik typ av meddelanden och/eller en specifik avsÀndare anvÀnds tvÄ valfria parametrar: ReliableUdpMessageTypes messageType och IPEndPoint ipEndPoint.
Meddelandetyper:
offentlig enum ReliableUdpMessageTypes : kort
{
// Vilken som helst
Any = 0,
// FörfrÄgan till STUN-server
StunRequest = 1,
// Svar frÄn STUN-server
StunResponse = 2,
// Filöverföring
FileTransfer = 3,
// ...
}
Skickandet av meddelande sker asynkront, för detta har protokollet implementerat en asynkron programmeringsmodell:
offentlig IAsyncResult BeginSendMessage(ReliableUdpMessage reliableUdpMessage, IPEndPoint remoteEndPoint, AsyncCallback asyncCallback, Object state)
Resultatet av meddelandesÀndning kommer att vara true - om meddelandet framgÄngsrikt nÄtt mottagaren och false - om anslutningen stÀngdes pÄ grund av tidsgrÀns:
offentlig bool EndSendMessage(IAsyncResult asyncResult)
Slutsats
Mycket har inte beskrivits i denna artikel. Mekanismer för strömöverenskommelse, hantering av undantag och fel samt implementering av asynkrona meddelandesÀndningsmetoder. Men kÀrnan i protokollet, beskrivningen av logiken för paketbehandling, anslutningsinstÀllningar och timeouthantering, bör klargöras för dig.
Den demonstrerade versionen av det pÄlitliga leveransprotokollet Àr tillrÀckligt stabil och flexibel och motsvarar vissa tidigare krav. Men jag vill tillÀgga att den beskrivna implementeringen kan förbÀttras. Till exempel, för att öka kapaciteten och dynamiskt justera timerintervall kan mekanismer som sliding window och RTT lÀggas till protokollet, och det skulle Àven vara anvÀndbart att implementera en mekanism för att bestÀmma MTU mellan anslutningsnoderna (men endast vid sÀndning av stora meddelanden).
Tack för uppmÀrksamheten, jag ser fram emot dina kommentarer och anmÀrkningar.
P.S. För den som Àr intresserad av detaljer eller bara vill testa protokollet, hÀr Àr lÀnken till projektet pÄ GitHub:
Nyttiga lÀnkar och artiklar
- Specifikation för TCP-protokollet: och
- Specifikation för UDP-protokollet: och
- Diskussion om RUDP-protokollet:
- Reliable Data Protocol: och
- En enkel implementering av leveransbekrÀftelse över UDP:
- Artikel som beskriver mekanismer för att övervinna NAT:er:
- Implementering av asynkron programmeringsmodell: och
- Ăverföring av den asynkrona programmeringsmodellen till ett asynkront mönster baserat pĂ„ uppgifter (APM till TAP):
Uppdatering: Tack och för idén om att lÀgga till en uppgift till grÀnsnittet. Kompatibiliteten för biblioteket med Àldre OS pÄverkas inte, eftersom den fjÀrde ramen stöder bÄde XP och 2003-server.
KĂ€lla: habr.com
