Internetit ka kohë që ka ndryshuar. Një nga protokollet kryesore të Internetit - UDP, përdoret nga aplikacionet jo vetëm për dërgimin e datagramëve dhe shpërndarjeve me anë të transmetimeve, por gjithashtu për të siguruar lidhje 'peer-to-peer' mes nyjeve të rrjetit. Për shkak të strukturës së tij të thjeshtë, ky protokoll ka përfituar shumë mënyra të paplanifikuara përdorimi, por disavantazhet e protokollit, si mosgarantimi i dërgimit, nuk kanë zhdukur. Në këtë artikull, përshkruhen implementimi i protokollit të dërgimit të garantuar mbi UDP.
Përmbajtja:
Hyrje
Arkitektura fillestare e Internetit parashikonte një hapësirë adresash homogjene, ku çdo nyje kishte një adresë IP globale dhe unike, dhe mund të komunikonte drejtpërdrejt me nyje të tjera. Tani, Interneti, në fakt, ka një arkitekturë tjetër - një fushë të adresave globale IP dhe shumë fusha me adresa private, të fshehura pas pajisjeve NAT.Në një arkitekturë të tillë, vetëm pajisjet që ndodhen në hapësirën adresore globale mund të lidhen lehtësisht me dikë në rrjet, pasi kanë një adresë IP unike, të rutezueshme në të gjithë botën. Një nyje që ndodhet në një rrjet privat mund të lidhë me nyje të tjera në këtë rrjet, si dhe të lidhet me nyje të tjera të njohura mirë në hapësirën adresore globale. Kjo ndërveprim arrin në masë të madhe falë mekanizmit të translacionit të adresave të rrjetit. Pajisjet NAT, për shembull, ruterët Wi-Fi, krijojnë regjistrime speciale në tabelat e translacionit për lidhjet e daljes dhe modifikojnë adresat IP dhe numrat e porteve në paketa. Kjo lejon që të krijohen lidhje të daljes nga një rrjet privat me nyje në hapësirën adresore globale. Por në të njëjtën kohë, pajisjet NAT zakonisht bllokojnë të gjithë trafikun e ardhshëm, nëse nuk vendosen rregulla të veçanta për lidhjet e ardhshme.
Kjo arkitekturë e Internetit është mjaft e duhur për ndërveprimin klient-server, kur klientët mund të ndodhen në rrjete private, ndërsa serverët kanë një adresë globale. Por ajo krijon vështirësi për lidhjen direkte të dy nyjeve midis rrjeteve private të ndryshme. Lidhja direkte e dy nyjeve është e rëndësishme për aplikacionet "peer-to-peer", siç janë transmetimi i zërit (Skype), qasja e largët në kompjuter (TeamViewer), ose lojërat online. Një nga metodat më efikase për të krijuar lidhje "peer-to-peer" midis pajisjeve në rrjete të ndryshme private quhet "hole punching". Kjo teknikë përdoret më shpesh me aplikacione që bazohen në protokollin UDP.
Por nëse aplikacioni juaj kërkon dërgim të garantuar të të dhënave, për shembull, nëse jeni duke transferuar skedarë midis kompjuterëve, atëherë përdorimi i UDP do të sjellë shumë vështirësi në lidhje me faktin se UDP nuk është një protokoll i dërgimit të garantuar dhe nuk siguron dërgesën e paketave në rend, ndryshe nga protokolli TCP.
Në këtë rast, për të siguruar dërgimin e garantuar të paketave, është e nevojshme të implementohet një protokoll i niveleve të aplikacionit që siguron funksionalitetin e nevojshëm dhe punon mbi UDP.
Ky protokoll do të ofrojë mekanizmat e nevojshëm për t'u siguruar që të dhënat të dërgohen saktë dhe në mënyrë të sigurt.
Dua të theksoj se ekziston teknika e TCP hole punching për të krijuar lidhje TCP midis nyjeve në rrjeta të ndryshme private, por për shkak të mungesës së mbështetjes nga shumë pajisje NAT, ajo zakonisht nuk shqyrtohet si një mënyrë kryesore për të lidhur këto nyje.
Më tej në këtë artikull do të shqyrtoj vetëm implementimin e protokollit të dorëzimit të garantuar. Implementimi i teknikës UDP hole punching do të përshkruhet në artikujt e ardhshëm.
Kërkesat për protokollin
- Dorëzimi i besueshëm i paketave, i realizuar nëpërmjet mekanizmit të marrëveshjes pozitive (e njohur si positive acknowledgment)
- Nevojitet një transferim efikas i të dhënave të mëdha, domethënë protokolli duhet të shmangë rikthimet e panevojshme të paketave
- Duhet të ketë mundësinë për të anuluar mekanizmin e konfirmimit të dorëzimit (mundësia për të funksionuar si një protokoll 'i pastër' UDP)
- Mundësia e realizimit të modit të komandës, me konfirmimin e secilës mesazh
- Njësia bazë e transferimit të të dhënave për protokollin duhet të jetë mesazhi
Këto kërkesa përputhen në masë të madhe me kërkesat për Protokollin e Të Dhënave të Besueshme, të përshkruara në dhe , dhe unë u bazova në këto standarde gjatë zhvillimit të këtij protokolli.
Për të kuptuar këto kërkesa, le të shqyrtojmë diagramet temporale të transferimit të të dhënave midis dy nyjeve të rrjetit për protokollet TCP dhe UDP. Le të supozojmë se në të dy rastet humbet një paketë.
Transferimi i të dhënave jo interaktive përmes TCP:
Siç shihet nga diagrami, në rastin e humbjes së paketave, TCP do të zbulojë paketën e humbur dhe do ta njoftojë dërguesin, duke kërkuar numrin e segmentit të humbur.
Transferimi i të dhënave përmes protokollit UDP:
UDP nuk ndërmerr asnjë hap për të zbuluar humbjet. Kontrolli i gabimeve në transferimin nga protokolli UDP është plotësisht në përgjegjësinë e aplikacionit.
Zbulimi i gabimeve në protokollin TCP arrihet përmes krijimit të një lidhjeje me nyjën përfundimtare, ruajtjes së gjendjes së kësaj lidhjeje, tregimit të numrit të bajtëve të dërguar në çdo header pakete, dhe njoftimeve për pranimin përmes numrit të konfirmimit 'acknowledge number'.
Për më tepër, për të përmirësuar performancën (dmth, për dërgimin e më shumë se një segmenti pa marrë konfirmim) protokolli TCP përdor atë që quhet dritarja e transferimit — numrin e bajtëve të dhënave që dërguesi i segmentit pret të marrë.
Për më shumë informacione mbi protokollin TCP, mund të referoheni në , me UDP në , ku ata, në të vërtetë, përcaktohen.
Nga ajo që u tha më sipër, është e qartë se për krijimin e një protokolli të besueshëm për dërgimin e mesazheve mbi UDP (më tej do ta quajmë Reliable UDP), kërkohet implementimi i mekanizmave të ngjashme me ata të TCP për transferimin e të dhënave. Në veçanti:
- të ruajë gjendjen e lidhjes
- të përdorë numërimin e segmenteve
- të përdorë paketa të veçanta të konfirmimit
- të përdorë një mekanizëm të thjeshtuar të dritares për të rritur kapacitetin e protokollit
Shtesë, kërkohet:
- të sinjalizojë fillimin e mesazhit, për të ndarë burimet për lidhjen
- të sinjalizojë përfundimin e mesazhit, për të dërguar mesazhin e pranuar në aplikacionin përkatës dhe për të liruar burimet e protokollit
- të lejojë protokollin për lidhje të caktuara të çaktivizojë mekanizmin e konfirmimit të dërgimit, për të funksionuar si ‘UDP i pastër’
Titulli Reliable UDP
Të kujtojmë se datagrami UDP encapsulohet në një datagram IP. Paketa Reliable UDP, përkatësisht, ‘mbyllet’ në një datagram UDP.
Encapsulimi i titullit të Reliable UDP:
Struktura e titullit të Reliable UDP është mjaft e thjeshtë:

- Flags – flazha kontrolluese të paketës
- MessageType – lloji i mesazhit, përdoret nga aplikacionet përkatëse për t'u abonuar në mesazhe të caktuara
- TransmissionId — numri i dërgimit, përveç adresës dhe portit të pranueseve, përcakton në mënyrë unike lidhjen
- PacketNumber – numri i paketës
- Options – mundësi të tjera të protokollit. Në rastin e paketës së parë përdoret për të specifikuar madhësinë e mesazhit
Flazhet ndahen në:
- FirstPacket — paketa e parë e mesazhit
- NoAsk — mesazhi nuk kërkon aktivizimin e mekanizmit të konfirmimit
- LastPacket — paketa e fundit e mesazhit
- RequestForPacket — paketa e konfirmimit ose kërkesa për një paketë të humbur
Princippet e përgjithshme të funksionimit të protokollit
Duke qenë se Reliable UDP është i orientuar drejt sigurimit të dërgimit të mesazhit midis dy nyjash, ai duhet të jetë në gjendje të vendosë lidhjen me anën tjetër. Për të vendosur lidhjen, faqja dërguese dërgon një paketë me flamurin FirstPacket, përgjigjja ndaj të cilës do të nënkuptojë vendosjen e lidhjes. Të gjitha paketat përgjigjëse, ose ndryshe, paketat e konfirmimit, gjithmonë vendosin vlerën e fushës PacketNumber të jetë një më shumë se vlera më e madhe e PacketNumber të paketave që kanë ardhur me sukses. Në fushën Options për paketën e parë të dërguar shënohet madhësia e mesazhit.
Për të përfunduar lidhjen përdoret një mekanizëm i ngjashëm. Në paketën e fundit të mesazhit vendoset flagu LastPacket. Në paketën përgjigjëse tregohet numri i paketës së fundit + 1, që për anën pranues do të thotë dorëzim të suksesshëm të mesazhit.
Diagrami i vendosjes dhe përfundimit të lidhjes:
Kur lidhja është vendosur, fillon transmetimi i të dhënave. Të dhënat dërgohen me blloqe paketash. Çdo bllok, përveç të fundit, përmban një numër të caktuar paketash. Ai është i barabartë me madhësinë e dritares së pranimit/dërgimit. Blloku i fundit i të dhënave mund të ketë një numër më të vogël paketash. Pas dërgimit të çdo blloku, ana-dërguese pret konfirmimin për dorëzim, ose kërkesën për ridërgimin e paketave të humbura, duke lënë hapur dritaren e pranimit/dërgimit për marrjen e përgjigjeve. Pas marrjes së konfirmimit për dorëzimin e bllokut, dritarja e pranimit/dërgimit lëviz dhe dërgohet blloku tjetër i të dhënave.
Ana-pranues merr paketat. Çdo paketë kontrollohet nëse bie brenda dritares së dërgimit. Paketat që nuk bien në dritare dhe dublikatet filtrohen. Pasi madhësia e dritares është e fiksuar dhe e barabartë për pranuesin dhe dërguesin, në rastin e dorëzimit të bllokut të paketave pa humbje, dritarja lëviz për të pranuar paketat e bllokut të ardhshëm të të dhënave dhe dërgohet konfirmimi për dorëzim. Nëse dritarja nuk mbushet brenda periudhës së vendosur nga timeri i punës, do të fillohet një kontroll për paketat që nuk janë dorëzuar dhe do të dërgohen kërkesa për ridërgim.
Diagrami i ridërgimit:
Të kohët dhe timetat e protokollit
Ekzistojnë disa arsye pse lidhja nuk mund të vendoset. Për shembull, nëse ana pranues është jashtë rrjetit. Në këtë rast, kur tentativa për të vendosur lidhjen bëhet, lidhja do të mbyllet për shkak të skadimit të kohës. Në realizimin e Reliable UDP përdoren dy timerë për vendosjen e skadimeve. I pari, timeri i punës, shërben për të pritur përgjigje nga hosti i largët. Nëse ai aktivizohet në anën dërguese, atëherë përsëritet dërgimi i paketës së fundit të dërguar. Nëse timeri aktivizohet te pranuesi, atëherë bëhet kontroll për paketat e humbura dhe dërgohen kërkesa për ridërgim.
Timeri i dytë është i nevojshëm për të mbyllur lidhjen në rast të mungesës së lidhjes mes nyjave. Për anën dërguese, ai aktivizohet menjëherë pas aktivizimit të timerit të punës dhe pret përgjigje nga nyja e largët. Në rast të mungesës së përgjigjes për një periudhë të caktuar, lidhja përfundon dhe burimet lirohen. Për anën marrëse, timeri i mbylljes së lidhjes aktivizohet pas një aktivizimi të dyfishtë të timerit të punës. Kjo është e nevojshme për të siguruar mbrojtje nga humbja e paketës së konfirmimit. Kur aktivizohet timeri, gjithashtu përfundon lidhja dhe burimet lirohen.
Diagrami i gjendjeve të dërgesës Reliable UDP
Principet e funksionimit të protokollit janë realizuar në një automatik përfundimtar, çdo gjendje e të cilit i përgjigjet një logjike të caktuar për përpunimin e paketimeve.
Diagrami i gjendjeve të UDP të besueshëm:

E Mbyllur nuk është në të vërtetë një gjendje, kjo është pika e fillimit dhe përfundimit për automatin. Për gjendjen E Mbyllur merrni parasysh bllokun e kontrollit të dërgesës, i cili, duke realizuar një server UDP asinkron, përcjell paketat në lidhjet përkatëse dhe aktivizon përpunimin e gjendjeve.
Dërgimi iPaketës së Parë gjendja fillestare në të cilën ndodhet lidhja dërguese kur dërgon një mesazh.
Në këtë gjendje dërgohet paketa e parë për mesazhet normale. Për mesazhet pa konfirmim dërgimi, kjo është gjendja e vetme - në të ndodh dërgimi i gjithë mesazhit.
Cikli i Dërgimit gjendja kryesore për dërgimin e paketimeve të mesazhit.
Kalimi në të nga gjendja Dërgimi iPaketës së Parë ndodh pas dërgimit të paketës së parë të mesazhit. Pikërisht në këtë gjendje arrijnë të gjitha konfirmimet dhe kërkesat për dërgime të përsëritura. Dalja prej saj është e mundur në dy raste - në rast të dorëzimit të suksesshëm të mesazhit ose për shkak të skadimit të kohës.
Paketë e Parë e Marrë gjendja fillestare për marrësin e mesazhit.
Në të kontrollohet saktësia e fillimit të dërgimit, krijohen strukturat e nevojshme dhe dërgohet një konfirmim për pranimin e paketës së parë.
Për një mesazh që përbëhet nga një paketë të vetme dhe dërgohet pa përdorimin e konfirmimit të dorëzimit, kjo është gjendja e vetme. Pas përpunimit të tillë të mesazhit, lidhja përfundon.
Asambleja gjendja kryesore për pranimin e paketimeve të mesazhit.
Në të, regjistrohen paketa në një ruajtje përkohësore, kontrollohet mungesa e humbjeve të paketave, dërgohen konfirmime për pranimin e bllokut të paketave dhe mesazhit të plotë, dhe dërgohen kërkesa për përsëritjen e dërgimit të paketimeve të humbura. Në rastin e pranimit të suksesshëm të mesazhit të tërë – lidhja kalon në gjendjen Përfunduar, përndryshe ekzekutohet dalja përmes skadimit të kohës.
Përfunduar – mbyllja e lidhjes në rastin e pranimit të suksesshëm të mesazhit të tërë.
Kjo gjendje është e nevojshme për ndërtimin e mesazhit dhe për rastin kur konfirmimi për dorëzimin e mesazhit është humbur gjatë rrugës për te dërguesi. Dalja nga kjo gjendje bëhet përmes skadimit të kohës, por lidhja konsiderohet se është mbyllur me sukses.
Më në thellësi në kod. Blloku i kontrollit të dërgimit
Një nga elementët kyç të Reliable UDP – blloku i kontrollit të transmetimit. Detyra e këtij bloku është të mbajë lidhjet aktuale dhe elementët ndihmës, të shpërndajë paketat e marra në lidhjet përkatëse, të ofrojë një ndërfaqe për dërgimin e paketimeve në lidhje dhe të realizojë API-në e protokollit. Blloku i kontrollit të transmetimit pranon paketat nga niveli UDP dhe i drejton ato për përpunim në automatin e fundme. Për pranimin e paketave, në të është realizuar një server asinkron UDP.
Disa anëtarë të klasës ReliableUdpConnectionControlBlock:
internal class ReliableUdpConnectionControlBlock : IDisposable
{
// një varg bajtesh për çelësin e dhënë. Përdoret për të ndërtuar mesazhet hyrëse
public ConcurrentDictionary<Tuple, byte[]> IncomingStreams { get; private set;}
// një varg bajtesh për çelësin e dhënë. Përdoret për të dërguar mesazhet dalëse.
public ConcurrentDictionary<Tuple, byte[]> OutcomingStreams { get; private set; }
// regjistri i lidhjes për çelësin e dhënë.
private readonly ConcurrentDictionary<Tuple, ReliableUdpConnectionRecord> m_listOfHandlers;
// lista e aboneve për mesazhet.
private readonly List m_subscribers;
// soketi lokal
private Socket m_socketIn;
// porta për mesazhet hyrëse
private int m_port;
// adresa IP lokale
private IPAddress m_ipAddress;
// pika lokale e përfundimit
public IPEndPoint LocalEndpoint { get; private set; }
// koleksioni i gjendjeve të para-inizializuara
// të automatit të fundme
public StatesCollection States { get; private set; }
// gjeneratori i numrave të rastësishëm. Përdoret për të krijuar TransmissionId
private readonly RNGCryptoServiceProvider m_randomCrypto;
//...
}
Implementimi i serverit asinkron UDP:
private void Receive()
{
EndPoint connectedClient = new IPEndPoint(IPAddress.Any, 0);
// Krijo një buffer të ri për çdo socket.BeginReceiveFrom
byte[] buffer = new byte[DefaultMaxPacketSize + ReliableUdpHeader.Length];
// Kalo bufferin si parametër për metodën asinkrone
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);
// Paketi u mor, jemi gati të pranojmë të ardhmen
Receive();
// sepse mënyra më e thjeshtë për të zgjidhur çështjen me bufferin - merrni referencë në të
// nga IAsyncResult.AsyncState
byte[] bytes = ((byte[]) ar.AsyncState).Slice(0, bytesRead);
// Marrim headerin e paketës
ReliableUdpHeader header;
if (!ReliableUdpStateTools.ReadReliableUdpHeader(bytes, out header))
{
// Paketi ka ardhur i papërshtatshëm - e heqim atë
return;
}
// Krijojmë një çelës për të identifikuar regjistrin e lidhjes për paketën
Tuple key = new Tuple(connectedClient, header.TransmissionId);
// Marrim regjistrin e lidhjes ekzistues ose krijojmë një të ri
ReliableUdpConnectionRecord record = m_listOfHandlers.GetOrAdd(key, new ReliableUdpConnectionRecord(key, this, header.ReliableUdpMessageType));
// Shtyjmë paketën për përpunim në automatin e përfundimit
record.State.ReceivePacket(record, header, bytes);
}
Për çdo dërgesë mesazhi krijohet një strukturë që përmban informacion në lidhje me lidhjen. Kjo strukturë quhet regjistri i lidhjes.
Disa anëtarë të klasës ReliableUdpConnectionRecord:
klasa interne ReliableUdpConnectionRecord : IDisposable
{
// një masë byte me mesazhin
public byte[] IncomingStream { get; set; }
// lidhja e gjendjes së automatit përfundimtar
public ReliableUdpState State { get; set; }
// një çift, që e përcakton saktësisht rekordin e lidhjes
// në bllokun e kontrollit të transmetimit
public Tuple Key { get; private set;}
// kufiri i poshtëm i dritares së pranimit
public int WindowLowerBound;
// madhësia e dritares së transmetimit
public readonly int WindowSize;
// numri i paketës për dërgim
public int SndNext;
// numri i paketave për dërgim
public int NumberOfPackets;
// numri i transmetimit (këtu është pjesa e dytë e Tuple)
// për çdo mesazh është unik
public readonly Int32 TransmissionId;
// IP endpoint i largët – në thelb marrësi i mesazhit
public readonly IPEndPoint RemoteClient;
// madhësia e paketës, për të shmangur fragmentimin në nivelin IP
// nuk duhet të tejkalojë MTU – (IP.Header + UDP.Header + RelaibleUDP.Header)
public readonly int BufferSize;
// blloku i kontrollit të transmetimit
public readonly ReliableUdpConnectionControlBlock Tcb;
// inkapsulon rezultatet e operacionit asinkron për BeginSendMessage/EndSendMessage
public readonly AsyncResultSendMessage AsyncResult;
// mos dërgoni paketat e konfirmimit
public bool IsNoAnswerNeeded;
// paketa e fundit e pranuar saktësisht (përherë vendoset në numrin më të madh)
public int RcvCurrent;
// masë me numrat e paketave të humbura
public int[] LostPackets { get; private set; }
// a ka ardhur paketa e fundit. Përdoret si bool.
public int IsLastPacketReceived = 0;
//...
}
Më në thellësi në kod. Gjendjet
Gjendjet realizojnë automatin përfundimtar të protokollit Reliable UDP, ku ndodh përpunimi kryesor i paketave. Klasa abstrakte ReliableUdpState ofron një ndërfaqe për gjendjen:

Të gjithë logjikën e punës së protokollit e realizojnë klasat e paraqitura më sipër, së bashku me klasën ndihmëse, që ofron metoda statike, të tilla si ndërtimi i gjysmështyllës ReliableUdp nga rekordet e lidhjes.
Më poshtë do të shqyrtohen në detaje realizimet e metodave të ndërfaqes, që përcaktojnë algoritmet kryesore të punës së protokollit.
Metoda DisposeByTimeout
Metoda DisposeByTimeout merret me lirimin e burimeve të lidhjes pas skadimit të kohës dhe për të sinjalizuar dorëzimin e suksesshëm/jo të suksesshëm të mesazhit.
ReliableUdpState.DisposeByTimeout:
protected virtual void DisposeByTimeout(object record)
{
ReliableUdpConnectionRecord connectionRecord = (ReliableUdpConnectionRecord) record;
if (record.AsyncResult != null)
{
connectionRecord.AsyncResult.SetAsCompleted(false);
}
connectionRecord.Dispose();
}
Ai është mbivendosur vetëm në gjendjen Përfunduar.
Completed.DisposeByTimeout:
protected override void DisposeByTimeout(object record)
{
ReliableUdpConnectionRecord connectionRecord = (ReliableUdpConnectionRecord) record;
// inform of successful message receipt
SetAsCompleted(connectionRecord);
}
Metoda ProcessPackets
Metoda ProcessPackets është përgjegjëse për trajtimin e mëtejshëm të paketës ose paketeve. Thirret direkt ose përmes një timeri të pritjes së paketave.
Në gjendje Asambleja metoda është e ridizajnuar dhe është përgjegjëse për verifikimin e paketave të humbura dhe kalimin në gjendje Përfunduar, në rast se merr paketen e fundit dhe kalon verifikimin e suksesshëm
Assembling.ProcessPackets:
public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
if (connectionRecord.IsDone != 0)
return;
if (!ReliableUdpStateTools.CheckForNoPacketLoss(connectionRecord, connectionRecord.IsLastPacketReceived != 0))
{
// ka paketa të humbura, dërgojmë kërkesa për to
foreach (int seqNum in connectionRecord.LostPackets)
{
if (seqNum != 0)
{
ReliableUdpStateTools.SendAskForLostPacket(connectionRecord, seqNum);
}
}
// vendosim timer për herë të dytë, për përpjekjen e përsëritur të dërgimit
if (!connectionRecord.TimerSecondTry)
{
connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
connectionRecord.TimerSecondTry = true;
return;
}
// nëse pas dy përpjekjeve të WaitForPacketTimer
// nuk arritëm të marrim paketat - fillojmë timerin e mbylljes së lidhjes
StartCloseWaitTimer(connectionRecord);
}
else if (connectionRecord.IsLastPacketReceived != 0)
// verifikim i suksesshëm
{
// dërgojmë konfirmimin e marrjes së blokut të të dhënave
ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
connectionRecord.State = connectionRecord.Tcb.States.Completed;
connectionRecord.State.ProcessPackets(connectionRecord);
// në vend të realizimit të menjëhershëm të burimeve
// fillojmë timerin, në rast se
// nëse ack-i i fundit nuk arrin te dërguesi dhe ai e kërkon përsëri.
// me aktivizimin e timerit - realizojmë burimet
// në gjendjen e Përfunduar, metoda e timerit është e ridizajnuar
StartCloseWaitTimer(connectionRecord);
}
// ky është rasti kur ack-u për blokun e paketave u humb
else
{
if (!connectionRecord.TimerSecondTry)
{
ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
connectionRecord.TimerSecondTry = true;
return;
}
// fillojmë timerin e mbylljes së lidhjes
StartCloseWaitTimer(connectionRecord);
}
}
Në gjendje Cikli i Dërgimit ky metodë thirret vetëm nga timeri, dhe është përgjegjëse për dërgimin e përsëritur të mesazhit të fundit, si dhe për aktivizimin e timerit të mbylljes së lidhjes.
SendingCycle.ProcessPackets:
publikisht override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
if (connectionRecord.IsDone != 0)
return;
// dërgo përsëri paketën e fundit
// (nëse rikuperimi i lidhjes, nyja-marrëse do ta dërgojë përsëri kërkesat, të cilat nuk arritën te ajo)
ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.RetransmissionCreateUdpPayload(connectionRecord, connectionRecord.SndNext - 1));
// aktivizoni timerin CloseWait – për të pritur rikuperimin e lidhjes ose përfundimin e saj
StartCloseWaitTimer(connectionRecord);
}
Në gjendje Përfunduar metoda ndalon timerin e punës dhe i dërgon një mesazh abonentëve.
Completed.ProcessPackets:
publikisht override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
if (connectionRecord.WaitForPacketsTimer != null)
connectionRecord.WaitForPacketsTimer.Dispose();
// mbledhim mesazhin dhe ia dërgojmë abonentëve
ReliableUdpStateTools.CreateMessageFromMemoryStream(connectionRecord);
}
Metoda ReceivePacket
Në gjendje Paketë e Parë e Marrë detyra kryesore e metodës është të përcaktojë nëse paketa e parë e mesazhit vërtet ka arritur në ndërfaqe, si dhe të mbledhë mesazhin që përbëhet nga një paketë të vetme.
FirstPacketReceived.ReceivePacket:
public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
if (!header.Flags.HasFlag(ReliableUdpHeaderFlags.FirstPacket))
// hedhim paketin
return;
// kombinimi i dy flagave - FirstPacket dhe LastPacket - tregon se kemi një mesazh të vetëm
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))
{
// dërgojmë paketën e konfirmimit
ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
}
SetAsCompleted(connectionRecord);
return;
}
// me dizajn të gjitha numrat e paketave fillojnë nga 0;
if (header.PacketNumber != 0)
return;
ReliableUdpStateTools.InitIncomingBytesStorage(connectionRecord, header);
ReliableUdpStateTools.WritePacketData(connectionRecord, header, payload);
// numërojmë numrin e paketave që duhet të vijnë
connectionRecord.NumberOfPackets = (int)Math.Ceiling((double) ((double) connectionRecord.IncomingStream.Length/(double) connectionRecord.BufferSize));
// regjistroni numrin e paketës së fundit të marrë (0)
connectionRecord.RcvCurrent = header.PacketNumber;
// pas kësaj e zhvendosim dritaren e pranimit me 1
connectionRecord.WindowLowerBound++;
// ndërroni gjendjen
connectionRecord.State = connectionRecord.Tcb.States.Assembling;
// nëse nuk kërkohet mekanizmi i konfirmimit
// aktivizoni një timer që do të lirojë të gjitha strukturat
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);
}
}
Në gjendje Cikli i Dërgimit ky metodë është tejkaluar për pranuar konfirmime të dorëzimit dhe kërkesa për ri-dërgim.
SendingCycle.ReceivePacket:
public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
if (connectionRecord.IsDone != 0)
return;
if (!header.Flags.HasFlag(ReliableUdpHeaderFlags.RequestForPacket))
return;
// llogaritja e kufirit të fundit të dritares
// merret kufiri i dritares + 1, për të marrë konfirmimet e dërgimit
int windowHighestBound = Math.Min((connectionRecord.WindowLowerBound + connectionRecord.WindowSize), (connectionRecord.NumberOfPackets));
// kontrolli i përputhjes me dritaren
if (header.PacketNumber windowHighestBound)
return;
connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
if (connectionRecord.CloseWaitTimer != null)
connectionRecord.CloseWaitTimer.Change(-1, -1);
// kontrolloni për paketën e fundit:
if (header.PacketNumber == connectionRecord.NumberOfPackets)
{
// dërgimi përfundoi
Interlocked.Increment(ref connectionRecord.IsDone);
SetAsCompleted(connectionRecord);
return;
}
// kjo është një përgjigje për paketën e parë me konfirmim
if ((header.Flags.HasFlag(ReliableUdpHeaderFlags.FirstPacket) && header.PacketNumber == 1))
{
// pa lëvizur dritaren
SendPacket(connectionRecord);
}
// erdhi konfirmimi për pranimin e bllokut të të dhënave
else if (header.PacketNumber == windowHighestBound)
{
// lëvizim i dritares së pranimit/dërgesës
connectionRecord.WindowLowerBound += connectionRecord.WindowSize;
// zerojmë masivin e kontrollit të dërgesës
connectionRecord.WindowControlArray.Nullify();
// dërgojmë bllokun e paketave
SendPacket(connectionRecord);
}
// kjo është një kërkesë për përsëritjen – dërgojmë paketën e kërkuar
else
ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.RetransmissionCreateUdpPayload(connectionRecord, header.PacketNumber));
}
Në gjendje Asambleja Në metodën ReceivePacket po bëhet puna kryesore e ndërtimit të mesazhit nga paketat që po vijnë.
Assembling.ReceivePacket:
publik e override void MerrniPaketin(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
if (connectionRecord.IsDone != 0)
return;
// përpunimi i paketeve me mekanizmin e konfirmimit të dorëzimit të çaktivizuar
if (header.Flags.HasFlag(ReliableUdpHeaderFlags.NoAsk))
{
// reseto timer-in
connectionRecord.CloseWaitTimer.Change(connectionRecord.LongTimerPeriod, -1);
// regjistrojmë të dhënat
ReliableUdpStateTools.WritePacketData(connectionRecord, header, payload);
// nëse kemi marrë paketën me flagun e fundit - kryejmë
if (header.Flags.HasFlag(ReliableUdpHeaderFlags.LastPacket))
{
connectionRecord.State = connectionRecord.Tcb.States.Completed;
connectionRecord.State.ProcessPackets(connectionRecord);
}
return;
}
// llogaritja e kufirit të fundit të dritares
int windowHighestBound = Math.Min((connectionRecord.WindowLowerBound + connectionRecord.WindowSize - 1), (connectionRecord.NumberOfPackets - 1));
// heqim paketat që nuk bien në dritare
if (header.PacketNumber (windowHighestBound))
return;
// heqim kopjet
if (connectionRecord.WindowControlArray.Contains(header.PacketNumber))
return;
// regjistrojmë të dhënat
ReliableUdpStateTools.WritePacketData(connectionRecord, header, payload);
// rrisim numëruesin e paketeve
connectionRecord.PacketCounter++;
// regjistrojmë në arrayn e kontrollit të dritares numrin aktual të paketës
connectionRecord.WindowControlArray[header.PacketNumber - connectionRecord.WindowLowerBound] = header.PacketNumber;
// vendosim paketën më të madhe të pranuar
if (header.PacketNumber > connectionRecord.RcvCurrent)
connectionRecord.RcvCurrent = header.PacketNumber;
// rivendosim timer-at
connectionRecord.TimerSecondTry = false;
connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
if (connectionRecord.CloseWaitTimer != null)
connectionRecord.CloseWaitTimer.Change(-1, -1);
// nëse ka ardhur paketa e fundit
if (header.Flags.HasFlag(ReliableUdpHeaderFlags.LastPacket))
{
Interlocked.Increment(ref connectionRecord.IsLastPacketReceived);
}
// nëse kemi marrë të gjitha paketat në dritare, atëherë rivendosim numëruesin
// dhe dërgojmë paketën e konfirmimit
else if (connectionRecord.PacketCounter == connectionRecord.WindowSize)
{
// rivendosim numëruesin.
connectionRecord.PacketCounter = 0;
// kemi lëvizur dritaren e dërgimit
connectionRecord.WindowLowerBound += connectionRecord.WindowSize;
// nullojmë arrayn e kontrollit të dërgimit
connectionRecord.WindowControlArray.Nullify();
ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
}
// nëse paketa e fundit tashmë është e pranishme
if (Thread.VolatileRead(ref connectionRecord.IsLastPacketReceived) != 0)
{
// kontrollojmë paketat
ProcessPackets(connectionRecord);
}
}
Në gjendje Përfunduar detyra e vetme e metodës është të dërgojë një konfirmim të ripërsëritur për dorëzimin e suksesshëm të mesazhit.
Përfunduar.MerrniPaketin:
public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
// ri-sendimi i paketit të fundit për shkak se
// që akuzimi i fundit nuk arriti te dërguesi
if (header.Flags.HasFlag(ReliableUdpHeaderFlags.LastPacket))
{
ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
}
}
Metoda SendPacket
Në gjendje Dërgimi iPaketës së Parë ky metodë kryen dërgimin e paketës së parë të të dhënave, ose, nëse mesazhi nuk kërkon konfirmimin e dorëzimit — të gjithë mesazhin.
FirstPacketSending.SendPacket:
public override void SendPacket(ReliableUdpConnectionRecord connectionRecord)
{
connectionRecord.PacketCounter = 0;
connectionRecord.SndNext = 0;
connectionRecord.WindowLowerBound = 0;
// nëse konfirmimi nuk nevojitet - dërgojmë të gjitha paketat
// dhe lirojmë burimet
if (connectionRecord.IsNoAnswerNeeded)
{
// Këtu ndodh dërgimi As Is
do
{
ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.CreateUdpPayload(connectionRecord, ReliableUdpStateTools.CreateReliableUdpHeader(connectionRecord)));
connectionRecord.SndNext++;
} while (connectionRecord.SndNext < connectionRecord.NumberOfPackets);
SetAsCompleted(connectionRecord);
return;
}
// krijojmë header-in e paketës dhe e dërgojmë atë
ReliableUdpHeader header = ReliableUdpStateTools.CreateReliableUdpHeader(connectionRecord);
ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.CreateUdpPayload(connectionRecord, header));
// rritim numëruesin
connectionRecord.SndNext++;
// lëvizim dritaren
connectionRecord.WindowLowerBound++;
connectionRecord.State = connectionRecord.Tcb.States.SendingCycle;
// Nisim timen
connectionRecord.WaitForPacketsTimer = new Timer(CheckByTimer, connectionRecord, connectionRecord.ShortTimerPeriod, -1);
}
Në gjendje Cikli i Dërgimit në këtë metodë ndodh dërgimi i bllokut të paketave.
SendingCycle.SendPacket:
public override void SendPacket(ReliableUdpConnectionRecord connectionRecord)
{
// dërgojmë bllokun e paketave
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++;
}
// në rast të një dritare të madhe të dërgimit, rifillojmë timer-in pas dërgimit
connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
if (connectionRecord.CloseWaitTimer != null)
{
connectionRecord.CloseWaitTimer.Change(-1, -1);
}
}
Më në thellësi në kod. Krijimi dhe vendosja e lidhjeve
Tani, kur u njohëm me shtetet dhe metodat kryesore të përdorura për të trajtuar shtetet, mund të shqyrtojmë pak më në detaj disa shembuj të funksionimit të protokollit.
Diagrami i dërgimit të të dhënave në kushte normale:
Le të shqyrtojmë në detaje krijimin regjistri i lidhjes për lidhjen dhe dërgimin e paketës së parë. Iniciatori i transferimit gjithmonë është aplikacioni që thërret metodën API për dërgimin e mesazhit. Më pas, aktivizohet metoda StartTransmission e bllokut të kontrollit të transferimit, e cila fillon transferimin e të dhënave për një mesazh të ri.
Krijimi i lidhjes së daljes:
private void StartTransmission(ReliableUdpMessage reliableUdpMessage, EndPoint endPoint, AsyncResultSendMessage asyncResult)
{
if (m_isListenerStarted == 0)
{
if (this.LocalEndpoint == null)
{
throw new ArgumentNullException( "", "Duhet të përdorni konstruktorin me parametra ose të filloni dëgjuesin përpara se të dërgoni mesazhin" );
}
// fillojmë përpunimin e paketave hyrëse
StartListener(LocalEndpoint);
}
// krijojmë çelës për fjalorin, në bazë të EndPoint dhe ReliableUdpHeader.TransmissionId
byte[] transmissionId = new byte[4];
// krijojmë një numër rastësor transmissionId
m_randomCrypto.GetBytes(transmissionId);
Tuple key = new Tuple(endPoint, BitConverter.ToInt32(transmissionId, 0));
// krijojmë një regjistër të ri për lidhjen dhe kontrollojmë,
// nëse një numër i tillë tashmë ekziston në fjalorin tonë
if (!m_listOfHandlers.TryAdd(key, new ReliableUdpConnectionRecord(key, this, reliableUdpMessage, asyncResult)))
{
// nëse ekziston – ri-gjenerojmë një numër rastësor
m_randomCrypto.GetBytes(transmissionId);
key = new Tuple(endPoint, BitConverter.ToInt32(transmissionId, 0));
if (!m_listOfHandlers.TryAdd(key, new ReliableUdpConnectionRecord(key, this, reliableUdpMessage, asyncResult)))
// nëse sërish nuk arritëm – gjenerojmë një përjashtim
throw new ArgumentException("Çifti TransmissionId & EndPoint tashmë ekziston në fjalor");
}
// e filluam gjendjen për përpunim
m_listOfHandlers[key].State.SendPacket(m_listOfHandlers[key]);
}
Dërgimi i paketës së parë (gjendja FirstPacketSending):
public override void SendPacket(ReliableUdpConnectionRecord connectionRecord)
{
connectionRecord.PacketCounter = 0;
connectionRecord.SndNext = 0;
connectionRecord.WindowLowerBound = 0;
// ...
// krijojmë krye-paketën dhe e dërgojmë atë
ReliableUdpHeader header = ReliableUdpStateTools.CreateReliableUdpHeader(connectionRecord);
ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.CreateUdpPayload(connectionRecord, header));
// rrisim numëruesin
connectionRecord.SndNext++;
// lëvizim dritaren
connectionRecord.WindowLowerBound++;
// kalojmë në gjendjen SendingCycle
connectionRecord.State = connectionRecord.Tcb.States.SendingCycle;
// Aktivizojmë temperin
connectionRecord.WaitForPacketsTimer = new Timer(CheckByTimer, connectionRecord, connectionRecord.ShortTimerPeriod, -1);
}
Pas dërgimit të paketës së parë, dërguesi kalon në gjendjen Cikli i Dërgimit – duke pritur konfirmimin për dorëzimin e paketës.
Pala pranues, përmes metodës EndReceive, merr paketën e dërguar, krijon një të re regjistri i lidhjes dhe e dërgon këtë paketë, me titullin e paraparë, për përpunim në metodën ReceivePacket në gjendjen Paketë e Parë e Marrë
Krijimi i lidhjes në anën e pranimit:
private void EndReceive(IAsyncResult ar)
{
\/\/ ...
\/\/ paketa është pranuar
\/\/ po analizohet krye pakete
ReliableUdpHeader header;
if (!ReliableUdpStateTools.ReadReliableUdpHeader(bytes, out header))
{
\/\/ paketa ka ardhur e pavlefshme - e heqim
return;
}
\/\/ konstruktimi i çelësit për identifikimin e regjistrit të lidhjes për paketën
Tuple<EndPoint, Int32> key = new Tuple<EndPoint, Int32>(connectedClient, header.TransmissionId);
\/\/ marrim regjistrin ekzistues të lidhjes ose krijojmë të riun
ReliableUdpConnectionRecord record = m_listOfHandlers.GetOrAdd(key, new ReliableUdpConnectionRecord(key, this, header.ReliableUdpMessageType));
\/\/ nisja e paketës në trajtim për automatin finit
record.State.ReceivePacket(record, header, bytes);
}
Pranimi i paketës së parë dhe dërgimi i konfirmimit (gjendja FirstPacketReceived):
public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
if (!header.Flags.HasFlag(ReliableUdpHeaderFlags.FirstPacket))
\/\/ heqim paketën
return;
\/\/ ...
\/\/ sipas dizajnit të gjitha numrat e paketave fillojnë nga 0;
if (header.PacketNumber != 0)
return;
\/\/ inicializojmë një masiv për ruajtjen e pjesëve të mesazhit
ReliableUdpStateTools.InitIncomingBytesStorage(connectionRecord, header);
\/\/ shkruajmë të dhënat e paketës në masiv
ReliableUdpStateTools.WritePacketData(connectionRecord, header, payload);
\/\/ llogarisim numrin e paketave që duhet të arrijnë
connectionRecord.NumberOfPackets = (int)Math.Ceiling((double) ((double) connectionRecord.IncomingStream.Length/(double) connectionRecord.BufferSize));
\/\/ shkruajmë numrin e paketës së fundit të pranuar (0)
connectionRecord.RcvCurrent = header.PacketNumber;
\/\/ pas kësaj ne zhvendosim dritaren e pranimit për 1
connectionRecord.WindowLowerBound++;
\/\/ kalojmë në gjendjen tjetër
connectionRecord.State = connectionRecord.Tcb.States.Assembling;
if (/*nëse nuk kërkohet mekanizmi i konfirmimit*/)
\/\/ ...
else
{
\/\/ dërgojmë konfirmimin
ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
connectionRecord.WaitForPacketsTimer = new Timer(CheckByTimer, connectionRecord, connectionRecord.ShortTimerPeriod, -1);
}
}
Më në thellësi në kod. Mbyllja e lidhjes për shkak të kohës së skadimit
Përpunimi i kohëve të pritjes është një pjesë e rëndësishme e Reliable UDP. Le të shqyrtojmë një shembull ku ka ndodhur një dështim në nyjën ndërmjetëse dhe dërgimi i të dhënave në të dy anët është bërë i pamundur.
Diagrami i mbylljes së lidhjes për shkak të kohës së pritjes:
Siç mund të shihet nga diagrami, timer-i aktiv në dërgues aktivizohet menjëherë pas dërgimit të bllokut të paketave. Kjo ndodh në metodën SendPacket në gjendjen Cikli i Dërgimit.
Aktivizimi i timer-it aktiv (gjendja SendingCycle):
public override void SendPacket(ReliableUdpConnectionRecord connectionRecord)
{
\/\/ dërgojmë bllokun e paketave
\/\/ ...
\/\/ rilansojmë timer-in pas dërgimit
connectionRecord.WaitForPacketsTimer.Change( connectionRecord.ShortTimerPeriod, -1 );
if ( connectionRecord.CloseWaitTimer != null )
connectionRecord.CloseWaitTimer.Change( -1, -1 );
}
Periudat e timerit vendosen në momentin e krijimit të lidhjes. Në mënyrë default, ShortTimerPeriod është 5 sekonda. Në këtë shembull, ai është vendosur në 1,5 sekonda.
Në lidhjen e ardhshme, timeri fillon pas marrjes së paketës së fundit që ka mb arrived, kjo ndodh në metodën ReceivePacket të gjendjes. Asambleja
Aktivizimi i timerit të punës (gjendja Assembling):
public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
// ...
// rivendosim timerët
connectionRecord.TimerSecondTry = false;
connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
if (connectionRecord.CloseWaitTimer != null)
connectionRecord.CloseWaitTimer.Change(-1, -1);
// ...
}
Në lidhjen e ardhshme, gjatë kohës së pritjes për timerin e punës nuk ka mb arrived paketash të tjera. Timeri u aktivizua dhe thirri metodën ProcessPackets, në të cilën u identifikuan paketat e humbura dhe kërkesat për dërgimin e përsëritur u dërguan për herë të parë.
Dërgimi i kërkesave për dërgimin e përsëritur (gjendja Assembling):
public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
// ...
if (/*kontrolli për paketat e humbura */)
{
// dërgojmë kërkesat për dërgimin e përsëritur
// vendosim timerin për herë të dytë, për përpjekjen e dërgimit
if (!connectionRecord.TimerSecondTry)
{
connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
connectionRecord.TimerSecondTry = true;
return;
}
// nëse pas dy përpjekjeve të aktivizimit të WaitForPacketTimer
// nuk arritëm të merrnim paketat - aktivizojmë timerin e mbylljes së lidhjes
StartCloseWaitTimer(connectionRecord);
}
else if (/*paketa e fundit ka mb arrived dhe kontrolli ka qenë i suksesshëm */)
{
// ...
StartCloseWaitTimer(connectionRecord);
}
// nëse ack për blokun e paketave u humb
else
{
if (!connectionRecord.TimerSecondTry)
{
// dërgojmë përsëri ack
connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
connectionRecord.TimerSecondTry = true;
return;
}
// aktivizojmë timerin e mbylljes së lidhjes
StartCloseWaitTimer(connectionRecord);
}
}
Variabla TimerSecondTry u vendos në true. Kjo variablë është përgjegjëse për rivendosjen e përsëritur të timerit të punës.
Nga ana e dërguesit gjithashtu aktivizohet timeri i punës dhe paketa e fundit e dërguar dërgohet përsëri.
Aktivizimi i timerit të mbylljes së lidhjes (gjendja SendingCycle):
public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
// ...
// dërgojmë përsëri paketën e fundit
// ...
// aktivizojmë timerin CloseWait – për të pritur rikuperimin e lidhjes ose përfundimin e saj
StartCloseWaitTimer(connectionRecord);
}
Pas pas ekran, një timer mbylljeje aktivizohet në lidhjen e daljes.
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);
}
Periudha e pritjes për timerin e mbylljes së lidhjes është 30 sekonda si parazgjedhje.
Pas një kohe të shkurtër, timeri funksional anësor aktivizohet sërish, kërkesat dërgohen përsëri, pas së cilës aktivizohet timeri i mbylljes për lidhjen e ardhshme.
Kur timerat e mbylljes aktivizohen, të gjitha burimet e të dyjë regjistrimeve të lidhjes lëshohet. Dërguesi njofton për dështim në dorëzim aplikacionit të sipërm (shih API Reliable UDP).
Shpërndarja e burimeve të regjistrimit të lidhjes:
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);
}
}
Më në thellësi në kod. Rimëkëmbja e dërgesave
Diagrami i rikuperimit të transmetimit të të dhënave në rast humbjeje të paketave:
Siç u diskutua në mbylljen e lidhjes për shkak të skadimit, pas skadimit të timerit punues, do të bëhet një kontroll për paketat e humbura. Në rast të humbjeve të paketave, do të përgatitet një listë me numrat e paketimeve që nuk arritën te marrësi. Këta numra do të regjistrohen në masivin LostPackets të lidhjes përkatëse dhe do të dërgohen kërkesa për dorëzim të përsëritur.
Dërgimi i kërkesave për dorëzim të përsëritur të paketave (gjendja Assembling):
public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
\/\/
if (!ReliableUdpStateTools.CheckForNoPacketLoss(connectionRecord, connectionRecord.IsLastPacketReceived != 0))
{
\/\/
për çdo numër seq në connectionRecord.LostPackets
{
if (seqNum != 0)
{
ReliableUdpStateTools.SendAskForLostPacket(connectionRecord, seqNum);
}
}
\/\/
}
}
Dërguesi do të pranojë kërkesën për dorëzim të përsëritur dhe do të dërgojë paketat e munguar. Duhet theksuar se në këtë moment, te dërguesi tashmë është aktivizuar timeri i mbylljes së lidhjes dhe, pas marrjes së kërkesës, ai riaktivizohet.
Dërgimi i përsëritur i paketave të humbura (gjendja SendingCycle):
public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
\/\/ ...
connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
\/\/ resett the connection close timer
if (connectionRecord.CloseWaitTimer != null)
connectionRecord.CloseWaitTimer.Change(-1, -1);
\/\/ ...
\/\/ this is a retransmission request – we send the required packet
else
ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.RetransmissionCreateUdpPayload(connectionRecord, header.PacketNumber));
}
The retransmitted packet (packet#3 on the diagram) is received by the incoming connection. A check is performed to fill the receive window and normal data transfer is restored.
Check for fitting into the receive window (Assembling state):
public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
\/\/ ...
\/\/ increment the packet counter
connectionRecord.PacketCounter++;
\/\/ record the current packet number in the window control array
connectionRecord.WindowControlArray[header.PacketNumber - connectionRecord.WindowLowerBound] = header.PacketNumber;
\/\/ set the highest received packet
if (header.PacketNumber > connectionRecord.RcvCurrent)
connectionRecord.RcvCurrent = header.PacketNumber;
\/\/ restart timers
connectionRecord.TimerSecondTry = false;
connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
if (connectionRecord.CloseWaitTimer != null)
connectionRecord.CloseWaitTimer.Change(-1, -1);
\/\/ ...
\/\/ if we have received all packets in the window, then reset the counter
\/\/ and send the acknowledgment packet
else if (connectionRecord.PacketCounter == connectionRecord.WindowSize)
{
\/\/ reset the counter.
connectionRecord.PacketCounter = 0;
\/\/ shift the transmission window
connectionRecord.WindowLowerBound += connectionRecord.WindowSize;
\/\/ clear the transmission control array
connectionRecord.WindowControlArray.Nullify();
ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
}
\/\/ ...
}
API Reliable UDP
To interact with the data transmission protocol, there is an open class Reliable Udp, which is a wrapper around the transmission control block. Here are the most important members of the class:
klasa e besueshme e vulosur ReliableUdp : IDisposable
{
// merr pika lokale
publik IPEndPoint LocalEndpoint
// krijon një instancë të ReliableUdp dhe fillon
// dëgjimin e paketave që hyjnë në adresën IP të caktuar
// dhe port. Vlera 0 për portin do të thotë përdorim
// të portit të ndarë dinamikisht
publik ReliableUdp(IPAddress localAddress, int port = 0)
// abonimi për marrjen e mesazheve që hyjnë
publik ReliableUdpSubscribeObject SubscribeOnMessages(ReliableUdpMessageCallback callback, ReliableUdpMessageTypes messageType = ReliableUdpMessageTypes.Any, IPEndPoint ipEndPoint = null)
// shkëputja nga marrja e mesazheve
publik void Unsubscribe(ReliableUdpSubscribeObject subscribeObject)
// dërgo mesazhin asinkronisht
// Shënim: përputhshmëria me XP dhe Server 2003 nuk humbet, pasi përdoret .NET Framework 4.0
publik Task SendMessageAsync(ReliableUdpMessage reliableUdpMessage, IPEndPoint remoteEndPoint, CancellationToken cToken)
// fillo dërgimin asinkron të mesazhit
publik IAsyncResult BeginSendMessage(ReliableUdpMessage reliableUdpMessage, IPEndPoint remoteEndPoint, AsyncCallback asyncCallback, Object state)
// merr rezultatin e dërgimit asinkron
publik bool EndSendMessage(IAsyncResult asyncResult)
// pastroni burimet
publik void Dispose()
}
Marrja e mesazhin bëhet me anë të abonimit. Nënshkrimi i delegatës për metodën e kthimit:
publik delegat void ReliableUdpMessageCallback( ReliableUdpMessage reliableUdpMessage, IPEndPoint remoteClient );Mesazhi:
publik klasë ReliableUdpMessage
{
// lloji i mesazhit, enumerim i thjeshtë
publik ReliableUdpMessageTypes Type { get; private set; }
// të dhënat e mesazhit
publik byte[] Body { get; private set; }
// nëse vendoset në true – mekanizmi i konfirmimit të shërbimit do të çaktivizohet
// për dërgimin e një mesazhi të caktuar
publik bool NoAsk { get; private set; }
}
Për të abonuar në një lloj të caktuar mesazhesh dhe/ose në një dërgues të caktuar, përdoren dy parametra opsionalë: ReliableUdpMessageTypes messageType dhe IPEndPoint ipEndPoint.
Llojet e mesazheve:
publik enum ReliableUdpMessageTypes : short
{
// Çdo gjë
Any = 0,
// Kërkesë për server STUN
StunRequest = 1,
// Përgjigje nga serveri STUN
StunResponse = 2,
// Transferim skedari
FileTransfer =3,
// ...
}
Dërgimi i mesazhit bëhet asinkronisht, për këtë, protokolli implementon një model asinkron të programimit:
publik IAsyncResult BeginSendMessage(ReliableUdpMessage reliableUdpMessage, IPEndPoint remoteEndPoint, AsyncCallback asyncCallback, Object state)
Rezultati i dërgimit të mesazhit do të jetë true – nëse mesazhi arriti me sukses te marrësi dhe false – nëse lidhja u mbyll për shkak të skadimit të kohës:
publik bool EndSendMessage(IAsyncResult asyncResult)
Përfundim
Shumë nuk u përshkrua brenda këtij artikulli. Mekanizmat e pajtimit të rrjedhave, përpunimi i përjashtimeve dhe gabimeve, zbatimi i metodave asinkrone për dërgimin e mesazheve. Por thelbi i protokollit, përshkrimi i logjikës së përpunimit të pakove, vendosja e lidhjes dhe menaxhimi i kohëve të pritjes, duhet të qartësohen për ju.
Versioni i demonstruar i protokollit të dorëzimit të besueshëm është mjaft i qëndrueshëm dhe fleksibël, dhe përputhet me kërkesat e mëparshme të caktuara. Por dua të shtoj se zbatimi i përshkruar mund të përmirësohet. Për shembull, për të rritur kapacitetin dhe për të ndryshuar dinamikisht periudhat e timerëve, protokolli mund të shtojë mekanizma si kjo dritare që lëviz dhe RTT, gjithashtu do të ishte e dobishme të implementohej një mekanizëm përcaktimi MTU midis nyjeve të lidhjes (por vetëm në rastin e dërgimit të mesazheve të mëdha).
Faleminderit për vëmendjen, pres komentet dhe vërejtjet tuaja.
P.S. Për ata që janë të interesuar për detajet ose thjesht duan të testojnë protokollin, lidhja për projektin në GitHub:
Lidhje dhe artikuj të dobishëm
- Specifikimi i protokollit TCP: dhe
- Specifikimi i protokollit UDP: dhe
- Diskutimi i protokollit RUDP:
- Protokolli i të Dhënave të Besueshme: dhe
- Një implementim i thjeshtë i konfirmimit të dorëzimit përmes UDP:
- Artikulli që përshkruan mekanizmat e anashkalimit të NAT-ëve:
- Implementimi i modelit asinkron të programimit: dhe
- Shkëputja e modelit asinkron të programimit në një model asinkron të bazuar në detyra (APM në TAP):
Përditësim: Faleminderit dhe për idenë e shtimit të detyrave në ndërfaqe. Kompatibiliteti i bibliotekës me sistemet e vjetra operative nuk preket, pasi framework 4 mbështet si XP ashtu edhe serverin 2003.
Burimi: habr.com
