Internettet har længe ændret sig. En af de grundlæggende protokoller på internettet – UDP – anvendes ikke kun af applikationer til levering af datagrammer og broadcasting, men også til at sikre "peer-to-peer" forbindelser mellem netværksnoder. På grund af dets enkle struktur er der opstået mange uventede anvendelser af denne protokol, men ulemperne ved protokollen, såsom manglende garanti for levering, er stadig til stede. Denne artikel beskriver implementeringen af en protokol for garanteret levering over UDP.
Indhold:
Introduktion
Den oprindelige arkitektur af internettet antog et homogent adresserum, hvor hver node havde en global og unik IP-adresse, og kunne kommunikere direkte med andre noder. I dag har internettet imidlertid en anden arkitektur – én zone for globale IP-adresser og mange zoner med private adresser, skjult bag NAT-enheder.I denne arkitektur kan kun enheder, der befinder sig i det globale adresseområde, nemt interagere med andre i nettet, da de har en unik, globalt routable IP-adresse. En node, der befinder sig i et privat netværk, kan forbinde sig med andre noder i samme netværk samt forbindes med andre kendte noder i det globale adresseområde. Denne interaktion opnås i høj grad takket være mekanismen til oversættelse af netværksadresser. NAT-enheder, såsom Wi-Fi-routere, opretter specielle optegnelser i oversættelsestabellerne for udgående forbindelser og ændrer IP-adresserne og portnumrene i pakkerne. Dette gør det muligt at etablere en udgående forbindelse fra det private netværk til noder i det globale adresseområde. Men samtidig blokerer NAT-enheder normalt al indgående trafik, medmindre der er opsat specifikke regler for indgående forbindelser.
Denne arkitektur af internettet er ret passende til klient-server-interaktion, hvor klienter kan befinde sig i private netværk, mens serverne har en global adresse. Men den skaber vanskeligheder for direkte forbindelse mellem to noder i forskellige private netværk. forskellige private netværk. Direkte forbindelser mellem to noder er vigtige for peer-to-peer-applikationer, såsom stemmeoverførsel (Skype), fjernadgang til computeren (TeamViewer) eller online-spil.
En af de mest effektive metoder til at etablere peer-to-peer-forbindelser mellem enheder i forskellige private netværk kaldes 'hole punching'. Denne teknik bruges oftest med applikationer baseret på UDP-protokollen.
Men hvis din applikation kræver garanteret levering af data, for eksempel når du overfører filer mellem computere, vil der opstå mange udfordringer ved brug af UDP, da UDP ikke er en protokol til garanteret levering og ikke sikrer, at pakkerne leveres i rækkefølge, i modsætning til TCP-protokollen.
I dette tilfælde kræves det at implementere en applikationsprotokol, der sikrer nødvendig funktionalitet og fungerer oven på UDP for at sikre garanteret levering af pakker.
Jeg vil straks bemærke, at der findes en teknik kaldet TCP hole punching til etablering af TCP-forbindelser mellem noder i forskellige private netværk, men på grund af manglende support fra mange NAT-enheder betragtes den sædvanligvis ikke som den primære metode til at forbinde sådanne noder.
I denne artikel vil jeg kun fokusere på implementeringen af et protokol for garanteret levering. Implementeringen af UDP hole punching-teknikken vil blive beskrevet i følgende artikler.
Krav til protokollen
- Sikker levering af pakker, der er realiseret gennem en positiv feedback-mekanisme (den såkaldte positive acknowledgment)
- Der er behov for effektiv transmission af store data, dvs. protokollen bør undgå unødvendige retransmissioner af pakker.
- Der skal være mulighed for at annullere bekræftelsesmekanismen (mulighed for at fungere som en "ren" UDP-protokol).
- Mulighed for at implementere kommandotilstand med bekræftelse af hver meddelelse.
- Den grundlæggende enhed for dataoverførsel via protokollen skal være en meddelelse.
Disse krav stemmer i høj grad overens med kravene til Reliable Data Protocol, der er beskrevet i og , og jeg har baseret mig på disse standarder ved udviklingen af denne protokol.
For at forstå disse krav, lad os se på tidsdiagrammerne for dataoverførsel mellem to noder i netværket via TCP- og UDP-protokoller. Lad os antage, at vi i begge tilfælde har mistet en pakke.
Overførsel af ikke-interaktive data via TCP:
Som det fremgår af diagrammet, vil TCP opdage den mistede pakke i tilfælde af pakkeudfald og informere afsenderen om dette ved at anmode om nummeret på den mistede segment.
Dataoverførsel via UDP-protokollen:
UDP tager ingen skridt til at opdage tab. Fejlkontrol i UDP-protokollen er fuldstændig overladt til applikationen.
Fejlregistrering i TCP-protokollen opnås gennem oprettelsen af forbindelse til slutpunktet, opretholdelse af tilstanden for denne forbindelse, angivelse af nummeret på de sendte byte i hver pakkehoved og meddelelser om modtagelse ved hjælp af bekræftelsesnummeret 'acknowledge number'.
Derudover anvender TCP-protokollen for at forbedre ydelsen (dvs. at sende mere end ét segment uden at modtage bekræftelse) det såkaldte transmissionsvindue — antallet af byte data, som segmentets afsender forventer at modtage.
Du kan læse mere detaljeret om TCP-protokollen i , med UDP i , hvor de faktisk er defineret.
Som det er beskrevet ovenfor, er det klart, at for at skabe en pålidelig protokol til levering af meddelelser over UDP (som vi herefter vil kalde Reliable UDP), er det nødvendigt at implementere mekanismer til datatransmission, der ligner dem fra TCP. Det vil sige:
- bevare forbindelsens tilstand
- bruge segmentnumre
- benytte specielle bekræftelsespakker
- anvende en forenklet vinduesmecanisme for at øge protokollens båndbredde
Derudover er det nødvendigt:
- at signalere begyndelsen af meddelelsen for at afsætte ressourcer til forbindelsen
- at signalere slutningen af meddelelsen for at overføre den modtagne meddelelse til den højere applikation og frigive protokollens ressourcer
- at tillade protokollen at deaktivere bekræftelsesmekanismen for specifikke forbindelser, så den kan fungere som «ren» UDP
Overskrift for Reliable UDP
Lad os huske, at UDP-datagrammet er indkapslet i IP-datagrammet. Reliable UDP-pakken 'pakkes' derfor ind i UDP-datagrammet.
Indkapsling af Reliable UDP-headeren:
Strukturen af Reliable UDP-headeren er ret enkel:

- Flags – kontrolflag for pakken
- MessageType – meddelelsestypen, der bruges af højere applikationer til at abonnere på bestemte meddelelser
- TransmissionId — overførselsnummer, der sammen med adressen og porten til modtageren entydigt identificerer forbindelsen
- PacketNumber – pakke nummer
- Options – yderligere protokolmuligheder. I tilfælde af den første pakke bruges det til at angive størrelsen på meddelelsen
Flagene kan være som følger:
- FirstPacket — den første pakke i meddelelsen
- NoAsk — meddelelsen kræver ikke aktivering af bekræftelsesmekanismen
- LastPacket — den sidste pakke i meddelelsen
- RequestForPacket — bekræftelsespakke eller anmodning om en mistet pakke
Generelle principper for protokollens funktion
Da Reliable UDP er centreret omkring garanteret levering af meddelelser mellem to noder, skal den kunne etablere forbindelse til den anden part. For at etablere forbindelsen sender afsendersiden en pakke med flaget FirstPacket, hvorefter svaret vil indikere, at forbindelsen er etableret. Alle svarpakker, eller med andre ord bekræftelsespakker, angiver altid værdien af PacketNumber-feltet til en mere end den største værdi af PacketNumber blandt de succesfuldt modtagne pakker. I Options-feltet for den første sendte pakke indtastes størrelsen på meddelelsen.
For completing the connection, a similar mechanism is used. In the last message packet, a LastPacket flag is set. In the response packet, the number of the last packet + 1 is indicated, which for the receiving side means successful delivery of the message.
Diagram of connection establishment and termination:
When the connection is established, data transmission begins. Data is transmitted in blocks of packets. Each block, except for the last one, contains a fixed number of packets. This number is equal to the size of the reception/transmission window. The last data block may contain fewer packets. After sending each block, the sender waits for an acknowledgment of delivery or a request for retransmission of lost packets, leaving the reception/transmission window open for receiving responses. After receiving the acknowledgment of the delivery of the block, the reception/transmission window shifts and the next block of data is sent.
The receiving side accepts the packets. Each packet is checked against the transmission window. Packets that do not fall within the window and duplicates are discarded. Since the size of the window is strictly fixed and the same for both the receiver and the sender, in the case of a successful delivery of the packet block, the window shifts to receive packets from the next data block, and an acknowledgment of delivery is sent. If the window is not filled within the period set by the working timer, a check will be initiated to determine which packets were not delivered, and requests for retransmission will be sent.
Diagram of retransmission:
Timeouts og timere for protokollen
There are several reasons why a connection cannot be established. For example, if the receiving side is offline. In this case, when trying to establish a connection, it will be closed due to a timeout. In the implementation of Reliable UDP, two timers are used for setting timeouts. The first, the working timer, is used to wait for a response from the remote host. If it triggers on the sender side, the last sent packet is retransmitted. If the timer triggers on the receiver side, a check for lost packets is performed, and requests for retransmission are sent.
Den anden timer er nødvendig for at lukke forbindelsen, hvis der ikke er noget signal mellem noderne. For afsenderens side starter den straks efter at arbejds- timeren udløser, og venter på svar fra den fjernværende node. Hvis der ikke er noget svar inden for den fastsatte periode, afsluttes forbindelsen, og ressourcerne frigives. For modtagerens side starter forbindelsens lukketimer efter at arbejds-timeren udløser to gange. Dette er nødvendigt for at forsikre mod tab af bekræftelsespakken. Når timeren udløber, afsluttes forbindelsen også, og ressourcerne frigives.
Tilstandsgraf for transmission af Reliable UDP
Protokollens driftsprincipper er implementeret i en endelig automat, hvor hver tilstand har ansvaret for en bestemt logik ved behandling af pakker.
Z-diagram for Reliable UDP:

Lukket – er faktisk ikke en tilstand, men et start- og slutpunkt for automaten. 'Tilstanden Lukket tager højde for kontrolblokken for transmissionen, som, når den implementerer en asynkron UDP-server, omdirigerer pakker til de relevante forbindelser og starter behandlingen af tilstandene.
FørstePakkesender – starttilstand, hvor den udgående forbindelse befinder sig, når der sendes en besked.
I denne tilstand sendes den første pakke for normale beskeder. For beskeder uden bekræftelse på sending, er dette den eneste tilstand - her sendes hele beskeden.
Sendecyklus – hovedtilstanden for transmission af transmissionspakker.
Overgangen til denne tilstand fra 'sendt' sker efter at den første pakke er sendt. Det er netop til denne tilstand, at alle bekræftelser og anmodninger om genoverførsler kommer. Udgange fra denne tilstand kan ske under to omstændigheder - i tilfælde af en vellykket levering af beskeden eller ved timeout. FørstePakkesender FørstePakkeModtaget
– starttilstand for modtageren af beskeden. Her kontrolleres rigtigheden af begyndelsen af transmissionen, de nødvendige strukturer oprettes, og der sendes en bekræftelse på modtagelsen af den første pakke.
For en besked bestående af en enkelt pakke og sendt uden brug af leveringsbekræftelse - er dette den eneste tilstand. Efter behandlingen af sådanne beskeder lukkes forbindelsen.
Sammenstilling
– hovedtilstand for modtagelse af pakkerne i beskeden. – den grundlæggende tilstand for modtagelse af meddelelsespakker.
Det registrerer pakker i et midlertidigt lager, tjekker for tab af pakker, sender bekræftelser om levering af pakke-blokke og meddelelser i sin helhed, og sender anmodninger om genlevering af tabte pakker. I tilfælde af vellykket modtagelse af hele meddelelsen – går forbindelsen til tilstanden Completed, ellers udføres en timeout-udgang.
Completed – lukning af forbindelsen i tilfælde af vellykket modtagelse af hele meddelelsen.
Denne tilstand er nødvendig for at samle meddelelsen og i tilfælde af at bekræftelsen om meddelelselevering blev tabt undervejs til afsenderen. Udgange fra denne tilstand sker ved timeout, men forbindelsen betragtes som vellykket lukket.
Dybdegående i koden. Kontrollaget for transmission
En af de centrale elementer i Reliable UDP – transmission control block. Opgaven for denne blok er at opbevare nuværende forbindelser og hjælpeelementer, fordele modtagne pakker til de relevante forbindelser, tilbyde et interface til at sende pakker til forbindelsen og implementere protokolens API. Transmission control block modtager pakker fra UDP-niveauet og videresender dem til behandling i en endelig automat. For at modtage pakker er der implementeret en asynkron UDP-server i den.
Nogle medlemmer af klassen ReliableUdpConnectionControlBlock:
internal class ReliableUdpConnectionControlBlock : IDisposable
{
// byte-array til angivet nøgle. Bruges til at samle indkommende meddelelser
public ConcurrentDictionary<Tuple<EndPoint, Int32>, byte[]> IncomingStreams { get; private set;}
// byte-array til angivet nøgle. Bruges til at sende udgående meddelelser.
public ConcurrentDictionary<Tuple<EndPoint, Int32>, byte[]> OutcomingStreams { get; private set; }
// connection record for the specified key.
private readonly ConcurrentDictionary<Tuple<EndPoint, Int32>, ReliableUdpConnectionRecord> m_listOfHandlers;
// liste over abonnenter til meddelelser.
private readonly List<ReliableUdpSubscribeObject> m_subscribers;
// lokal sokkel
private Socket m_socketIn;
// port til indkommende meddelelser
private int m_port;
// lokal IP-adresse
private IPAddress m_ipAddress;
// lokal slutpunkt
public IPEndPoint LocalEndpoint { get; private set; }
// samling af forudinitialiserede
// tilstande for den endelige automat
public StatesCollection States { get; private set; }
// tilfældig talgenerator. Bruges til at generere TransmissionId
private readonly RNGCryptoServiceProvider m_randomCrypto;
//...
}
Implementering af en asynkron UDP-server:
private void Receive()
{
EndPoint connectedClient = new IPEndPoint(IPAddress.Any, 0);
// Opretter en ny buffer til hver socket.BeginReceiveFrom
byte[] buffer = new byte[DefaultMaxPacketSize + ReliableUdpHeader.Length];
// Sender bufferen som parameter til den asynkrone metode
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);
// Pakken er modtaget, klar til at modtage den næste
Receive();
// Da den enkleste måde at løse bufferproblemet på - få en reference til det
// fra IAsyncResult.AsyncState
byte[] bytes = ((byte[]) ar.AsyncState).Slice(0, bytesRead);
// Modtager headeren af pakken
ReliableUdpHeader header;
if (!ReliableUdpStateTools.ReadReliableUdpHeader(bytes, out header))
{
// Ugyldig pakke modtaget - kaster den væk
return;
}
// Konstruerer en nøgle til bestemmelse af connection record’en for pakken
Tuple key = new Tuple(connectedClient, header.TransmissionId);
// Henter eksisterende connection record eller opretter en ny
ReliableUdpConnectionRecord record = m_listOfHandlers.GetOrAdd(key, new ReliableUdpConnectionRecord(key, this, header.ReliableUdpMessageType));
// Starter pakken til behandling i tilstandsmaskinen
record.State.ReceivePacket(record, header, bytes);
}
For hver meddelelse, der sendes, oprettes en struktur, som indeholder oplysninger om forbindelsen. Denne struktur kaldes connection record.
Nogle medlemmer af klassen ReliableUdpConnectionRecord:
intern klasse ReliableUdpConnectionRecord : IDisposable
{
// byte array med besked
public byte[] IncomingStream { get; set; }
// reference til tilstanden af den endelige automaton
public ReliableUdpState State { get; set; }
// et par, der entydigt definerer connection record
// i transmissionskontrolblokken
public Tuple Key { get; private set;}
// nedre grænse for modtagevinduet
public int WindowLowerBound;
// størrelse af transmissionsvinduet
public readonly int WindowSize;
// pakkenummer til afsendelse
public int SndNext;
// antal pakker til afsendelse
public int NumberOfPackets;
// transmissionsnummer (det er den anden del af Tuple)
// hvert besked har sin egen
public readonly Int32 TransmissionId;
// fjern IP endpoint – modtageren af beskeden
public readonly IPEndPoint RemoteClient;
// pakkestørrelse for at undgå fragmentering på IP niveau
// må ikke overstige MTU – (IP.Header + UDP.Header + ReliableUDP.Header)
public readonly int BufferSize;
// transmissionskontrolblok
public readonly ReliableUdpConnectionControlBlock Tcb;
// indkapsler resultaterne af den asynkrone operation for BeginSendMessage/EndSendMessage
public readonly AsyncResultSendMessage AsyncResult;
// ikke sende bekræftelsespakker
public bool IsNoAnswerNeeded;
// det sidste korrekt modtagne pakke (sættes altid til det højest nummer)
public int RcvCurrent;
// array med numrene på tabte pakker
public int[] LostPackets { get; private set; }
// er den sidste pakke modtaget? Bruges som bool.
public int IsLastPacketReceived = 0;
//...
}
Dybdegående i koden. Tilstande
Tilstande implementerer den endelige automaton af Reliable UDP-protokollen, hvor den primære behandling af pakker finder sted. Den abstrakte klasse ReliableUdpState giver et interface til tilstanden:

Al logik for protokolens funktionalitet implementeres af de ovenfor præsenterede klasser, sammen med en hjælpekklasse, der tilbyder statiske metoder, såsom opbygning af headeren til ReliableUdp fra connection record.
Metoderne i interfacet, der definerer de grundlæggende algoritmer for protokolens funktionalitet, vil nu blive gennemgået i detaljer.
Metode DisposeByTimeout
Metoden DisposeByTimeout har ansvaret for at frigøre ressourcerne for forbindelsen efter udløb af timeout og for at signalere om succesfuld/ikke-succesfuld levering af beskeden.
ReliableUdpState.DisposeByTimeout:
protected virtual void DisposeByTimeout(object record)
{
ReliableUdpConnectionRecord connectionRecord = (ReliableUdpConnectionRecord) record;
if (record.AsyncResult != null)
{
connectionRecord.AsyncResult.SetAsCompleted(false);
}
connectionRecord.Dispose();
}
Den er kun overridden i tilstanden Completed.
Completed.DisposeByTimeout:
beskyttet override void DisposeByTimeout(object record)
{
ReliableUdpConnectionRecord connectionRecord = (ReliableUdpConnectionRecord) record;
// bekræfter modtagelse af meddelelse
SetAsCompleted(connectionRecord);
}
Metode ProcessPackets
Metoden ProcessPackets håndterer den ekstra behandling af en eller flere pakker. Den kaldes direkte, eller via en timeout timer.
I tilstand – hovedtilstand for modtagelse af pakkerne i beskeden. metoden er blevet overskrevet og håndterer kontrollen af tabte pakker og overgangen til tilstand Completed, i tilfælde af modtagelse af den sidste pakke og bestået kontrol
Assembling.ProcessPackets:
public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
if (connectionRecord.IsDone != 0)
return;
if (!ReliableUdpStateTools.CheckForNoPacketLoss(connectionRecord, connectionRecord.IsLastPacketReceived != 0))
{
// der er tabte pakker, sender anmodninger om dem
foreach (int seqNum in connectionRecord.LostPackets)
{
if (seqNum != 0)
{
ReliableUdpStateTools.SendAskForLostPacket(connectionRecord, seqNum);
}
}
// sætter timeren for anden gang, for at forsøge at sende igen
if (!connectionRecord.TimerSecondTry)
{
connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
connectionRecord.TimerSecondTry = true;
return;
}
// hvis efter to forsøg med WaitForPacketTimer
// vi ikke har fået pakker - starter vi timeren for at lukke forbindelsen
StartCloseWaitTimer(connectionRecord);
}
else if (connectionRecord.IsLastPacketReceived != 0)
// bestået kontrol
{
// sender bekræftelse på modtagelse af datablok
ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
connectionRecord.State = connectionRecord.Tcb.States.Completed;
connectionRecord.State.ProcessPackets(connectionRecord);
// i stedet for øjeblikkelig implementering af ressourcer
// starter vi en timer, i tilfælde af at
// hvis sidste ack ikke når frem til afsenderen og han anmoder om det igen.
// ved timerens udløb - implementerer vi ressourcer
// i tilstanden Completed er timerens metode overskrevet
StartCloseWaitTimer(connectionRecord);
}
// dette er tilfældet, hvor ack for blokken af pakker er gået tabt
else
{
if (!connectionRecord.TimerSecondTry)
{
ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
connectionRecord.TimerSecondTry = true;
return;
}
// starter timeren for at lukke forbindelsen
StartCloseWaitTimer(connectionRecord);
}
}
I tilstand Sendecyklus denne metode kaldes kun ved timeren, og håndterer genudsendelse af den seneste meddelelse, samt aktivering af lukketimer.
SendingCycle.ProcessPackets:
public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
if (connectionRecord.IsDone != 0)
return;
// sender den seneste pakke igen
// (i tilfælde af genskabelse af forbindelsen vil modtagerknuden sende anmodningerne igen, som ikke nåede frem til den)
ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.RetransmissionCreateUdpPayload(connectionRecord, connectionRecord.SndNext - 1));
// aktiverer CloseWait timeren – for at vente på genskabelse af forbindelsen eller dens afslutning
StartCloseWaitTimer(connectionRecord);
}
I tilstand Completed metoden stopper arbejdstimeren og videresender beskeden til abonnenterne.
Completed.ProcessPackets:
public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
if (connectionRecord.WaitForPacketsTimer != null)
connectionRecord.WaitForPacketsTimer.Dispose();
// samler besked og videresender den til abonnenterne
ReliableUdpStateTools.CreateMessageFromMemoryStream(connectionRecord);
}
Metode ReceivePacket
I tilstand – starttilstand for modtageren af beskeden. Hovedformålet med metoden er at bestemme, om den første pakke af meddelelsen faktisk er nået til grænsefladen, samt at samle meddelelsen bestående af en enkelt pakke.
FirstPacketReceived.ReceivePacket:
public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
if (!header.Flags.HasFlag(ReliableUdpHeaderFlags.FirstPacket))
return; // vi drop the packet
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))
{
// send acknowledgment packet
ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
}
SetAsCompleted(connectionRecord);
return;
}
// by design all packet numbers start from 0;
if (header.PacketNumber != 0)
return;
ReliableUdpStateTools.InitIncomingBytesStorage(connectionRecord, header);
ReliableUdpStateTools.WritePacketData(connectionRecord, header, payload);
// calculate the number of packets that should arrive
connectionRecord.NumberOfPackets = (int)Math.Ceiling((double)((double)connectionRecord.IncomingStream.Length / (double)connectionRecord.BufferSize));
// record the number of the last received packet (0)
connectionRecord.RcvCurrent = header.PacketNumber;
// then shifted the reception window by 1
connectionRecord.WindowLowerBound++;
// switch state
connectionRecord.State = connectionRecord.Tcb.States.Assembling;
// if acknowledgment mechanism is not required
// start a timer that will release all structures
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 tilstand Sendecyklus Denne metode er overtaget for at modtage bekræftelser for levering og anmodninger om genoverførsel.
SendingCycle.ReceivePacket:
public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
if (connectionRecord.IsDone != 0)
return;
if (!header.Flags.HasFlag(ReliableUdpHeaderFlags.RequestForPacket))
return;
// beregner den endelige grænse for vinduet
// grænsen for vinduet + 1 bruges til at få bekræftelser på levering
int windowHighestBound = Math.Min((connectionRecord.WindowLowerBound + connectionRecord.WindowSize), (connectionRecord.NumberOfPackets));
// kontrol for at være inden for vinduet
if (header.PacketNumber windowHighestBound)
return;
connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
if (connectionRecord.CloseWaitTimer != null)
connectionRecord.CloseWaitTimer.Change(-1, -1);
// tjek for den sidste pakke:
if (header.PacketNumber == connectionRecord.NumberOfPackets)
{
// transmissionen er afsluttet
Interlocked.Increment(ref connectionRecord.IsDone);
SetAsCompleted(connectionRecord);
return;
}
// dette er et svar på den første pakke med bekræftelse
if ((header.Flags.HasFlag(ReliableUdpHeaderFlags.FirstPacket) && header.PacketNumber == 1))
{
// uden vinduesforskydning
SendPacket(connectionRecord);
}
// der er kommet en bekræftelse på modtagelsen af data blokkene
else if (header.PacketNumber == windowHighestBound)
{
// forskydning af modtage/afsendelsesvinduet
connectionRecord.WindowLowerBound += connectionRecord.WindowSize;
// nulstiller transmissions kontrolarrayet
connectionRecord.WindowControlArray.Nullify();
// sender blokken af pakker
SendPacket(connectionRecord);
}
// dette er en anmodning om genoverførsel - sender den krævede pakke
else
ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.RetransmissionCreateUdpPayload(connectionRecord, header.PacketNumber));
}
I tilstand – hovedtilstand for modtagelse af pakkerne i beskeden. I metoden ReceivePacket udføres det hovedsagelige arbejde med at samle meddelelsen fra de indkommende pakker.
Assembling.ReceivePacket:
public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
if (connectionRecord.IsDone != 0)
return;
// behandling af pakker uden bekræftelsesmekanisme
if (header.Flags.HasFlag(ReliableUdpHeaderFlags.NoAsk))
{
// nulstil timer
connectionRecord.CloseWaitTimer.Change(connectionRecord.LongTimerPeriod, -1);
// gem data
ReliableUdpStateTools.WritePacketData(connectionRecord, header, payload);
// hvis vi modtog en pakke med den sidste flag - afslut
if (header.Flags.HasFlag(ReliableUdpHeaderFlags.LastPacket))
{
connectionRecord.State = connectionRecord.Tcb.States.Completed;
connectionRecord.State.ProcessPackets(connectionRecord);
}
return;
}
// beregning af den endelige vinduesgrænse
int windowHighestBound = Math.Min((connectionRecord.WindowLowerBound + connectionRecord.WindowSize - 1), (connectionRecord.NumberOfPackets - 1));
// aflæggelse af pakker, der ikke falder inden for vinduet
if (header.PacketNumber (windowHighestBound))
return;
// aflæggelse af duplikater
if (connectionRecord.WindowControlArray.Contains(header.PacketNumber))
return;
// gem data
ReliableUdpStateTools.WritePacketData(connectionRecord, header, payload);
// øg pakke tæller
connectionRecord.PacketCounter++;
// gem den aktuelle pakke nummer i vindueskontrol arrayet
connectionRecord.WindowControlArray[header.PacketNumber - connectionRecord.WindowLowerBound] = header.PacketNumber;
// sæt den største ankomne pakke
if (header.PacketNumber > connectionRecord.RcvCurrent)
connectionRecord.RcvCurrent = header.PacketNumber;
// genstart timerne
connectionRecord.TimerSecondTry = false;
connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
if (connectionRecord.CloseWaitTimer != null)
connectionRecord.CloseWaitTimer.Change(-1, -1);
// hvis den sidste pakke er ankommet
if (header.Flags.HasFlag(ReliableUdpHeaderFlags.LastPacket))
{
Interlocked.Increment(ref connectionRecord.IsLastPacketReceived);
}
// hvis vi har modtaget alle pakker i vinduet, nulstill tælleren
// og send en bekræftelsespakke
else if (connectionRecord.PacketCounter == connectionRecord.WindowSize)
{
// nulstill tælleren.
connectionRecord.PacketCounter = 0;
// vi har skubbet sendervindue
connectionRecord.WindowLowerBound += connectionRecord.WindowSize;
// nulstilling af overføringskontrol array
connectionRecord.WindowControlArray.Nullify();
ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
}
// hvis den sidste pakke allerede er modtaget
if (Thread.VolatileRead(ref connectionRecord.IsLastPacketReceived) != 0)
{
// kontroller pakker
ProcessPackets(connectionRecord);
}
}
I tilstand Completed Den eneste opgave for metoden er at sende en gentaget bekræftelse på den succesfulde levering af meddelelsen.
Completed.ReceivePacket:
public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
\/\/ genudsend sidste pakke, fordi
\/\/ den sidste ack ikke nåede senderen
if (header.Flags.HasFlag(ReliableUdpHeaderFlags.LastPacket))
{
ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
}
}
Metode SendPacket
I tilstand FørstePakkesender denne metode sender det første datapakke, eller, hvis beskeden ikke kræver leveringsbekræftelse - hele beskeden.
FirstPacketSending.SendPacket:
public override void SendPacket(ReliableUdpConnectionRecord connectionRecord)
{
connectionRecord.PacketCounter = 0;
connectionRecord.SndNext = 0;
connectionRecord.WindowLowerBound = 0;
\/\/ hvis bekræftelse ikke er nødvendig - sender vi alle pakker
\/\/ og frigiver ressourcer
if (connectionRecord.IsNoAnswerNeeded)
{
\/\/ Her sendes As Is
do
{
ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.CreateUdpPayload(connectionRecord, ReliableUdpStateTools.CreateReliableUdpHeader(connectionRecord)));
connectionRecord.SndNext++;
} while (connectionRecord.SndNext < connectionRecord.NumberOfPackets);
SetAsCompleted(connectionRecord);
return;
}
\/\/ vi opretter pakkeheader og sender den
ReliableUdpHeader header = ReliableUdpStateTools.CreateReliableUdpHeader(connectionRecord);
ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.CreateUdpPayload(connectionRecord, header));
\/\/ øger tælleren
connectionRecord.SndNext++;
\/\/ skubber vinduet frem
connectionRecord.WindowLowerBound++;
connectionRecord.State = connectionRecord.Tcb.States.SendingCycle;
\/\/ Starter timeren
connectionRecord.WaitForPacketsTimer = new Timer(CheckByTimer, connectionRecord, connectionRecord.ShortTimerPeriod, -1);
}
I tilstand Sendecyklus i denne metode sendes en blok af pakker.
SendingCycle.SendPacket:
public override void SendPacket(ReliableUdpConnectionRecord connectionRecord)
{
\/\/ sender blokken af pakker
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++;
}
\/\/ ved store vinduer genstarter vi timeren efter sending
connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
if (connectionRecord.CloseWaitTimer != null)
{
connectionRecord.CloseWaitTimer.Change(-1, -1);
}
}
Dybdegående i koden. Oprettelse og etablering af forbindelser
Nu hvor vi har fået kendskab til de grundlæggende tilstande og metoder, der anvendes til håndtering af tilstande, kan vi se lidt nærmere på nogle eksempler på protokollens funktion.
Dataoverførselsdiagram under normale forhold:
Lad os se nærmere på oprettelsen connection record For connection and sending the first packet. The initiator of the transfer is always the application that calls the API method for sending the message. Next, the StartTransmission method of the transmission control block is engaged, initiating the data transfer for the new message.
Creating an outgoing connection:
private void StartTransmission(ReliableUdpMessage reliableUdpMessage, EndPoint endPoint, AsyncResultSendMessage asyncResult)
{
if (m_isListenerStarted == 0)
{
if (this.LocalEndpoint == null)
{
throw new ArgumentNullException( "", "Du skal bruge en konstruktor med parametre eller starte lytteren før du sender beskeden" );
}
// starting the processing of incoming packets
StartListener(LocalEndpoint);
}
// creating a key for the dictionary based on EndPoint and ReliableUdpHeader.TransmissionId
byte[] transmissionId = new byte[4];
// creating a random number transmissionId
m_randomCrypto.GetBytes(transmissionId);
Tuple key = new Tuple(endPoint, BitConverter.ToInt32(transmissionId, 0));
// creating a new entry for the connection and checking,
// if such a number already exists in our dictionaries
if (!m_listOfHandlers.TryAdd(key, new ReliableUdpConnectionRecord(key, this, reliableUdpMessage, asyncResult)))
{
// if it exists – generate a random number again
m_randomCrypto.GetBytes(transmissionId);
key = new Tuple(endPoint, BitConverter.ToInt32(transmissionId, 0));
if (!m_listOfHandlers.TryAdd(key, new ReliableUdpConnectionRecord(key, this, reliableUdpMessage, asyncResult)))
// if it fails again – generate an exception
throw new ArgumentException("Parret TransmissionId & EndPoint findes allerede i ordbogen");
}
// initiated the state for processing
m_listOfHandlers[key].State.SendPacket(m_listOfHandlers[key]);
}
Sending the first packet (state FirstPacketSending):
public override void SendPacket(ReliableUdpConnectionRecord connectionRecord)
{
connectionRecord.PacketCounter = 0;
connectionRecord.SndNext = 0;
connectionRecord.WindowLowerBound = 0;
// ...
// creating the packet header and sending it
ReliableUdpHeader header = ReliableUdpStateTools.CreateReliableUdpHeader(connectionRecord);
ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.CreateUdpPayload(connectionRecord, header));
// increment the counter
connectionRecord.SndNext++;
// shift the window
connectionRecord.WindowLowerBound++;
// transition to state SendingCycle
connectionRecord.State = connectionRecord.Tcb.States.SendingCycle;
// starting the timer
connectionRecord.WaitForPacketsTimer = new Timer(CheckByTimer, connectionRecord, connectionRecord.ShortTimerPeriod, -1);
}
After sending the first packet, the sender transitions to the state Sendecyklus – waiting for confirmation of packet delivery.
The receiving side, using the EndReceive method, accepts the sent packet, creates a new connection record and passes this packet, with the previously parsed header, to the ReceivePacket processing method. – starttilstand for modtageren af beskeden.
Oprettelse af forbindelse på modtagerens side:
private void EndReceive(IAsyncResult ar)
{
// ...
// pakken er modtaget
// parser headeren på pakken
ReliableUdpHeader header;
if (!ReliableUdpStateTools.ReadReliableUdpHeader(bytes, out header))
{
// modtaget en ugyldig pakke - forkast den
return;
}
// konstruere nøgle for at bestemme connection record’en for pakken
Tuple key = new Tuple(connectedClient, header.TransmissionId);
// få eksisterende connection record eller opret en ny
ReliableUdpConnectionRecord record = m_listOfHandlers.GetOrAdd(key, new ReliableUdpConnectionRecord(key, this, header.ReliableUdpMessageType));
// starter pakken i behandling i den endelige automata
record.State.ReceivePacket(record, header, bytes);
}
Modtagelse af den første pakke og sending af bekræftelse (tilstand FirstPacketReceived):
public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
if (!header.Flags.HasFlag(ReliableUdpHeaderFlags.FirstPacket))
// forkast pakken
return;
// ...
// ifølge design starter alle packet numbers fra 0;
if (header.PacketNumber != 0)
return;
// initialiserer array til at gemme dele af meddelelsen
ReliableUdpStateTools.InitIncomingBytesStorage(connectionRecord, header);
// skriver data pakken til array
ReliableUdpStateTools.WritePacketData(connectionRecord, header, payload);
// tæller antallet af pakker, der skal komme
connectionRecord.NumberOfPackets = (int)Math.Ceiling((double)((double)connectionRecord.IncomingStream.Length / (double)connectionRecord.BufferSize));
// skriver nummeret på den sidste modtagne pakke (0)
connectionRecord.RcvCurrent = header.PacketNumber;
// efterfølgende flytter vi modtagelsesvinduet op med 1
connectionRecord.WindowLowerBound++;
// skifter tilstand
connectionRecord.State = connectionRecord.Tcb.States.Assembling;
if (/*hvis bekræftelsesmekanisme ikke er påkrævet*/)
// ...
else
{
// sender bekræftelse
ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
connectionRecord.WaitForPacketsTimer = new Timer(CheckByTimer, connectionRecord, connectionRecord.ShortTimerPeriod, -1);
}
}
Dybdegående i koden. Lukning af forbindelse ved timeout
Håndtering af timeouts er en vigtig del af Reliable UDP. Lad os se på et eksempel, hvor der skete et nedbrud på en mellemstation, og datalevering i begge retninger blev umulig.
Diagram over lukning af forbindelse ved timeout:
Som det ses i diagrammet, aktiveres arbejdskimmeren hos afsenderen straks efter afsendelse af blokken af pakker. Dette sker i SendPacket-metoden i tilstanden Sendecyklus.
Aktivering af arbejdskimmeren (tilstand SendingCycle):
public override void SendPacket(ReliableUdpConnectionRecord connectionRecord)
{
// sender en blok af pakker
// ...
// genstarter timeren efter afsendelse
connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
if (connectionRecord.CloseWaitTimer != null)
connectionRecord.CloseWaitTimer.Change(-1, -1);
}
Tidsperioderne for timeren fastsættes, når forbindelsen oprettes. Som standard er ShortTimerPeriod 5 sekunder. I dette eksempel er den indstillet til 1,5 sekunder.
For en indgående forbindelse starter timeren, når den sidste modtagne datapakke er kommet, dette sker i metoden ReceivePacket tilstanden. – hovedtilstand for modtagelse af pakkerne i beskeden.
Aktivering af arbejds timeren (tilstand Assembling):
public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
// ...
// genstarter timerne
connectionRecord.TimerSecondTry = false;
connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
if (connectionRecord.CloseWaitTimer != null)
connectionRecord.CloseWaitTimer.Change(-1, -1);
// ...
}
I den indgående forbindelse kom der ikke flere pakker i løbet af ventetiden for arbejds timeren. Timeren blev udløst og kaldte metoden ProcessPackets, hvori der blev opdaget tabte pakker og der blev første gang sendt anmodninger om genlevering.
Udsendelse af anmodninger om genlevering (tilstand Assembling):
public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
// ...
if (/*kontrol for tabte pakker */)
{
// sender anmodninger om genlevering
// indstiller timeren for anden gang, til genforsøg ved overførsel
if (!connectionRecord.TimerSecondTry)
{
connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
connectionRecord.TimerSecondTry = true;
return;
}
// hvis det ikke lykkedes at få pakker efter to forsøg på WaitForPacketTimer
// starter vi timeren for at afslutte forbindelsen
StartCloseWaitTimer(connectionRecord);
}
else if (/*den sidste pakke er modtaget og succesfuld kontrol */)
{
// ...
StartCloseWaitTimer(connectionRecord);
}
// hvis ack på pakkegruppen blev tabt
else
{
if (!connectionRecord.TimerSecondTry)
{
// sender ack igen
connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
connectionRecord.TimerSecondTry = true;
return;
}
// starter timeren for at afslutte forbindelsen
StartCloseWaitTimer(connectionRecord);
}
}
Variablen TimerSecondTry blev indstillet til true. Denne variabel styrer genstarten af arbejds timeren.
På afsenderens side aktiveres arbejds timeren også, og den sidste sendte pakke sendes igen.
Aktivering af timeren til afbrydelse af forbindelsen (tilstand SendingCycle):
public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
// ...
// sender den sidste pakke igen
// ...
// aktiverer CloseWait timeren – for at vente på genoprettelse af forbindelsen eller dens afslutning
StartCloseWaitTimer(connectionRecord);
}
Når en udgående forbindelse er etableret, startes en timer til lukning af forbindelsen.
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);
}
Standard tidsperioden for lukningstimeren er 30 sekunder.
Efter kort tid udløses arbejdstimeren på modtagerens side igen, hvilket resulterer i, at forespørgsler sendes igen, hvorefter lukningstimeren for indgående forbindelse aktiveres.
Når lukningstimerne udløber, frigives alle ressourcer fra begge forbindelsesposter. Afsenderen rapporterer om fejl ved leveringen til den overordnede applikation (se API Reliable UDP).
Frigivelse af ressourcer fra forbindelsesposten:
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);
}
}
Dybdegående i koden. Gendannelse af datatransmission
Diagram for genoprettelse af dataoverføring ved pakke tab:
Som allerede nævnt i forbindelse med lukningen af forbindelsen på timeout, vil der ved udløbet af arbejdstimeren på modtagerens side blive udført en kontrol for tabte pakker. I tilfælde af tab vil der blive oprettet en liste over pakker, der ikke er nået frem til modtageren. Disse nummererede pakker føjes til arrayet LostPackets for den specifikke forbindelse, og der sendes forespørgsler om genlevering.
Afsendelse af forespørgsler om genlevering af pakker (tilstand Assembling):
public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
//...
if (!ReliableUdpStateTools.CheckForNoPacketLoss(connectionRecord, connectionRecord.IsLastPacketReceived != 0))
{
// der er tabte pakker, så vi sender forespørgsler på dem
foreach (int seqNum in connectionRecord.LostPackets)
{
if (seqNum != 0)
{
ReliableUdpStateTools.SendAskForLostPacket(connectionRecord, seqNum);
}
}
// ...
}
}
Afsenderen vil modtage forespørgslen om genlevering og sende de manglende pakker. Det skal bemærkes, at lukningstimeren allerede er startet hos afsenderen, og at den nulstilles ved modtagelse af forespørgslen.
Genafsendelse af tabte pakker (tilstand SendingCycle):
public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
// ...
connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
// nulstiller forbindelsens lukketimer
if (connectionRecord.CloseWaitTimer != null)
connectionRecord.CloseWaitTimer.Change(-1, -1);
// ...
// dette er en anmodning om retransmission – sender den krævede pakke
else
ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.RetransmissionCreateUdpPayload(connectionRecord, header.PacketNumber));
}
Den retransmitterede pakke (packet#3 i diagrammet) modtages af den indkommende forbindelse. Der udføres en kontrol af modtagervinduet, og den normale dataoverførsel genoptages.
Kontrol for at se, om den er i modtagervinduet (tilstand Assembling):
public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
// ...
// øger pakke tælleren
connectionRecord.PacketCounter++;
// registrerer det aktuelle pakke nummer i kontrolarrayet
connectionRecord.WindowControlArray[header.PacketNumber - connectionRecord.WindowLowerBound] = header.PacketNumber;
// sætter den største modtagne pakke
if (header.PacketNumber > connectionRecord.RcvCurrent)
connectionRecord.RcvCurrent = header.PacketNumber;
// genstarter timerne
connectionRecord.TimerSecondTry = false;
connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
if (connectionRecord.CloseWaitTimer != null)
connectionRecord.CloseWaitTimer.Change(-1, -1);
// ...
// hvis vi har modtaget alle pakker i vinduet, nulstiller tælleren
// og sender bekræftelsespakken
else if (connectionRecord.PacketCounter == connectionRecord.WindowSize)
{
// nulstiller tælleren.
connectionRecord.PacketCounter = 0;
// flyttede transmissionsvinduet
connectionRecord.WindowLowerBound += connectionRecord.WindowSize;
// nulstil kontrolarrayet
connectionRecord.WindowControlArray.Nullify();
ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
}
// ...
}
API Reliable UDP
Der er en offentlig klasse Reliable Udp til interaktion med dataoverførselsprotokollen, som er en wrapper over transmissionskontrolblokken. Her er de vigtigste medlemmer af klassen:
offentlig lukket klasse ReliableUdp : IDisposable
{
// henter den lokale endepunkt
offentlig IPEndPoint LocalEndpoint
// opretter en instans af ReliableUdp og starter
// lytter efter indkommende pakker på den angivne IP-adresse
// og port. Værdi 0 for port betyder brug af
// dynamisk tildelt port
offentlig ReliableUdp(IPAddress localAddress, int port = 0)
// abonnement på at modtage indkommende meddelelser
offentlig ReliableUdpSubscribeObject SubscribeOnMessages(ReliableUdpMessageCallback callback, ReliableUdpMessageTypes messageType = ReliableUdpMessageTypes.Any, IPEndPoint ipEndPoint = null)
// afmeld modtagelse af meddelelser
offentlig void Unsubscribe(ReliableUdpSubscribeObject subscribeObject)
// asynkront sende meddelelse
// Bemærk: kompatibilitet med XP og Server 2003 opretholdes, da .NET Framework 4.0 anvendes
offentlig Task SendMessageAsync(ReliableUdpMessage reliableUdpMessage, IPEndPoint remoteEndPoint, CancellationToken cToken)
// begynde asynkron sending af meddelelse
offentlig IAsyncResult BeginSendMessage(ReliableUdpMessage reliableUdpMessage, IPEndPoint remoteEndPoint, AsyncCallback asyncCallback, Object state)
// få resultat af asynkron sending
offentlig bool EndSendMessage(IAsyncResult asyncResult)
// rydde op i ressourcer
offentlig void Dispose()
}
Modtagelse af meddelelser sker ved abonnement. Signaturen for delegaten til callback-metoden:
offentlig delegeret void ReliableUdpMessageCallback( ReliableUdpMessage reliableUdpMessage, IPEndPoint remoteClient );Meddelelse:
offentlig klasse ReliableUdpMessage
{
// meddelelsestype, simpel enum
offentlig ReliableUdpMessageTypes Type { get; privat set; }
// meddelelsesdata
offentlig byte[] Body { get; privat set; }
// hvis sat til true – vil bekræftelsesmekanismen for levering blive deaktiveret
// for at overføre en specifik meddelelse
offentlig bool NoAsk { get; privat set; }
}
For at abonnere på en specifik type meddelelser og/eller fra en specifik afsender bruges to valgfri parametre: ReliableUdpMessageTypes messageType og IPEndPoint ipEndPoint.
Meddelelsestyper:
offentlig enum ReliableUdpMessageTypes : kort
{
// Enhver
Any = 0,
// Anmodning til STUN server
StunRequest = 1,
// Svar fra STUN server
StunResponse = 2,
// Filoverførsel
FileTransfer =3,
// ...
}
Sending af meddelelser sker asynkront, til dette er der realiseret en asynkron programmeringsmodel i protokollen:
offentlig IAsyncResult BeginSendMessage(ReliableUdpMessage reliableUdpMessage, IPEndPoint remoteEndPoint, AsyncCallback asyncCallback, Object state)
Resultatet af sending af meddelelse vil være true – hvis meddelelsen er nået frem til modtageren, og false – hvis forbindelsen blev lukket på grund af timeout:
offentlig bool EndSendMessage(IAsyncResult asyncResult)
Konklusion
Meget er ikke blevet beskrevet i denne artikel. Mekanismer til strømforhandling, håndtering af undtagelser og fejl, implementering af asynkrone metoder til at sende beskeder. Men kernen af protokollen, beskrivelsen af logikken for pakkehåndtering, oprettelsen af forbindelser og håndtering af timeouts, bør blive tydelig for dig.
Den demonstration, der er præsenteret af protokollen for pålidelig levering, er tilstrækkelig robust og fleksibel og opfylder tidligere bestemte krav. Men jeg vil gerne tilføje, at den beskrevne implementering kan forbedres. For eksempel, for at øge båndbredden og dynamisk ændre timerperioder i protokollen, kan mekanismer som sliding window og RTT tilføjes; det vil også være nyttigt at implementere en mekanisme til at bestemme MTU mellem forbindelsespunkterne (men kun i tilfælde af at sende store meddelelser).
Tak for din opmærksomhed, jeg ser frem til dine kommentarer og bemærkninger.
P.S. For dem, der er interesseret i detaljerne, eller bare ønsker at teste protokollen, er her et link til projektet på GitHube:
Nyttige links og artikler
- Specifikation af TCP-protokollen: og
- Specifikation af UDP-protokollen: og
- Diskussion af RUDP-protokollen:
- Reliable Data Protocol: og
- En simpel implementering af leveringsbekræftelse via UDP:
- Artikel, der beskriver mekanismer til at omgå NAT'er:
- Implementering af asynkron programmeringsmodel: og
- Overførsel af den asynkrone programmeringsmodel til en asynkron skabelon baseret på opgaver (APM til TAP):
Opdatering: Tak og for ideen om at tilføje en opgave til grænsefladen. Bibliotekets kompatibilitet med ældre styresystemer forbliver intakt, da .NET Framework 4 understøtter både XP og 2003 server.
Kilde: habr.com
