Implementimi i protokollit Reliable UDP për .Net

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
Kërkesat për protokollin
Titulli Reliable UDP
Princippet e përgjithshme të funksionimit të protokollit
Të kohët dhe timetat e protokollit
Diagrami i gjendjeve të dërgesës Reliable UDP
Më në thellësi në kod. Blloku i kontrollit të dërgimit
Më në thellësi në kod. Gjendjet

Më në thellësi në kod. Krijimi dhe vendosja e lidhjeve
Më në thellësi në kod. Mbyllja e lidhjes për shkak të kohës së skadimit
Më në thellësi në kod. Rimëkëmbja e dërgesave
API Reliable UDP
Përfundim
Lidhje dhe artikuj të dobishëm

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

  1. Dorëzimi i besueshëm i paketave, i realizuar nëpërmjet mekanizmit të marrëveshjes pozitive (e njohur si positive acknowledgment)
  2. Nevojitet një transferim efikas i të dhënave të mëdha, domethënë protokolli duhet të shmangë rikthimet e panevojshme të paketave
  3. 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)
  4. Mundësia e realizimit të modit të komandës, me konfirmimin e secilës mesazh
  5. 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ë rfc 908 dhe rfc 1151, 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:Implementimi i protokollit Reliable UDP për .Net

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:Implementimi i protokollit Reliable UDP për .Net

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ë rfc 793, me UDP në rfc 768, 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:Implementimi i protokollit Reliable UDP për .Net

Struktura e titullit të Reliable UDP është mjaft e thjeshtë:

Implementimi i protokollit Reliable UDP për .Net

  • 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:Implementimi i protokollit Reliable UDP për .Net

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:Implementimi i protokollit Reliable UDP për .Net

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:

Implementimi i protokollit Reliable UDP për .Net

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:

Implementimi i protokollit Reliable UDP për .Net

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:Implementimi i protokollit Reliable UDP për .Net

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:Implementimi i protokollit Reliable UDP për .Net

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:Implementimi i protokollit Reliable UDP për .Net

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:
Projekti Reliable UDP

Lidhje dhe artikuj të dobishëm

  1. Specifikimi i protokollit TCP: në anglisht dhe në rusisht
  2. Specifikimi i protokollit UDP: në anglisht dhe në rusisht
  3. Diskutimi i protokollit RUDP: draft-ietf-sigtran-reliable-udp-00
  4. Protokolli i të Dhënave të Besueshme: rfc 908 dhe rfc 1151
  5. Një implementim i thjeshtë i konfirmimit të dorëzimit përmes UDP: Merrni kontroll total mbi rrjetin tuaj me .NET dhe UDP
  6. Artikulli që përshkruan mekanizmat e anashkalimit të NAT-ëve: Komunikimi Peer-to-Peer përmes Translatorëve të Adresave të Rrjetit
  7. Implementimi i modelit asinkron të programimit: Implementimi i Modelit të Programimit Asinkron CLR dhe Si të implementoni modelin e dizajnit IAsyncResult
  8. Shkëputja e modelit asinkron të programimit në një model asinkron të bazuar në detyra (APM në TAP):
    TPL dhe Programimi Tradicional Asinkron në .NET
    Interop me Mekanizmat dhe Llojet e Tjera Asinkrone

Përditësim: Faleminderit mayorovp dhe sidristij 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

Blini hostim të besueshëm për faqe interneti me mbrojtje DDoS, serverë VPS VDS 🔥 Blini hostim të besueshëm për faqe interneti me mbrojtje DDoS, serverë VPS VDS - ProHoster