Implementimi i protokollit të besueshëm Udp për .Net

Interneti ka ndryshuar shumë kohë më parë. Një nga protokollet kryesore të internetit - UDP përdoret nga aplikacionet jo vetëm për të ofruar datagrame dhe transmetime, por edhe për të ofruar lidhje "peer-to-peer" midis nyjeve të rrjetit. Për shkak të dizajnit të tij të thjeshtë, ky protokoll ka shumë përdorime të paplanifikuara më parë, megjithatë, mangësitë e protokollit, si mungesa e dorëzimit të garantuar, nuk janë zhdukur askund. Ky artikull përshkruan zbatimin e protokollit të garantuar të dorëzimit mbi UDP.
Përmbajtja:Hyrje
Kërkesat e protokollit
Titulli i besueshëm i UDP
Parimet e përgjithshme të protokollit
Kohët e fundit dhe kohëmatësit e protokollit
Diagrami i besueshëm i gjendjes së transmetimit të UDP
Më thellë në kod. njësia e kontrollit të transmetimit
Më thellë në kod. shteteve

Më thellë në kod. Krijimi dhe vendosja e lidhjeve
Më thellë në kod. Mbyllja e lidhjes në kohë
Më thellë në kod. Rivendosja e transferimit të të dhënave
API e besueshme UDP
Përfundim
Lidhje dhe artikuj të dobishëm

Hyrje

Arkitektura origjinale e internetit supozoi një hapësirë ​​​​homogjene adresash në të cilën çdo nyje kishte një adresë IP globale dhe unike dhe mund të komunikonte drejtpërdrejt me nyjet e tjera. Tani interneti, në fakt, ka një arkitekturë të ndryshme - një zonë me adresa IP globale dhe shumë zona me adresa private të fshehura pas pajisjeve NAT.Në këtë arkitekturë, vetëm pajisjet në hapësirën globale të adresave mund të komunikojnë lehtësisht me këdo në rrjet, sepse ato kanë një adresë IP unike, globalisht të rutueshme. Një nyje në një rrjet privat mund të lidhet me nyje të tjera në të njëjtin rrjet, dhe gjithashtu mund të lidhet me nyje të tjera të njohura në hapësirën globale të adresave. Ky ndërveprim arrihet kryesisht për shkak të mekanizmit të përkthimit të adresave të rrjetit. Pajisjet NAT, të tilla si ruterat Wi-Fi, krijojnë hyrje të veçanta të tabelës së përkthimit për lidhjet dalëse dhe modifikojnë adresat IP dhe numrat e porteve në pako. Kjo lejon lidhjet dalëse nga rrjeti privat te hostet në hapësirën globale të adresave. Por në të njëjtën kohë, pajisjet NAT zakonisht bllokojnë të gjithë trafikun në hyrje nëse nuk vendosen rregulla të veçanta për lidhjet hyrëse.

Kjo arkitekturë e internetit është mjaft e saktë për komunikimin klient-server, ku klientët mund të jenë në rrjete private, dhe serverët kanë një adresë globale. Por krijon vështirësi për lidhjen e drejtpërdrejtë të dy nyjeve ndërmjet te ndryshme rrjetet private. Një lidhje e drejtpërdrejtë midis dy nyjeve është e rëndësishme për aplikacionet peer-to-peer si transmetimi i zërit (Skype), fitimi i aksesit në distancë në një kompjuter (TeamViewer) ose lojërat në internet.

Një nga metodat më efektive për të krijuar një lidhje peer-to-peer midis pajisjeve në rrjete të ndryshme private quhet vrima. Kjo teknikë përdoret më së shpeshti me aplikacionet e bazuara në protokollin UDP.

Por nëse aplikacioni juaj ka nevojë për shpërndarje të garantuar të të dhënave, për shembull, ju transferoni skedarë midis kompjuterëve, atëherë përdorimi i UDP do të ketë shumë vështirësi për faktin se UDP nuk është një protokoll i garantuar shpërndarjeje dhe nuk ofron shpërndarjen e paketave në rregull, ndryshe nga TCP protokoll.

Në këtë rast, për të siguruar shpërndarjen e garantuar të paketave, kërkohet të zbatohet një protokoll i shtresës së aplikacionit që ofron funksionalitetin e nevojshëm dhe funksionon mbi UDP.

Dua të vërej menjëherë se ekziston një teknikë e vrimës TCP për vendosjen e lidhjeve TCP midis nyjeve në rrjete të ndryshme private, por për shkak të mungesës së mbështetjes për të nga shumë pajisje NAT, zakonisht nuk konsiderohet si mënyra kryesore për t'u lidhur. nyje të tilla.

Për pjesën e mbetur të këtij artikulli, do të fokusohem vetëm në zbatimin e protokollit të garantuar të dorëzimit. Zbatimi i teknikës së shpimit të vrimave UDP do të përshkruhet në artikujt vijues.

Kërkesat e protokollit

  1. Dorëzimi i besueshëm i paketave i zbatuar përmes një mekanizmi reagimi pozitiv (i ashtuquajturi njohje pozitive)
  2. Nevoja për transferim efikas të të dhënave të mëdha, d.m.th. protokolli duhet të shmangë transmetimin e panevojshëm të paketave
  3. Duhet të jetë e mundur të anulohet mekanizmi i konfirmimit të dorëzimit (aftësia për të funksionuar si një protokoll "i pastër" UDP)
  4. Aftësia për të zbatuar modalitetin e komandës, me konfirmimin e çdo mesazhi
  5. Njësia bazë e transferimit të të dhënave përmes protokollit duhet të jetë një mesazh

Këto kërkesa përkojnë kryesisht me kërkesat e Protokollit të të Dhënave të Besueshme të përshkruara në Rfc 908 и Rfc 1151, dhe unë u mbështeta në ato standarde gjatë zhvillimit të këtij protokolli.

Për të kuptuar këto kërkesa, le të shohim kohën e transferimit të të dhënave midis dy nyjeve të rrjetit duke përdorur protokollet TCP dhe UDP. Le që në të dyja rastet do të kemi një pako të humbur.
Transferimi i të dhënave jo-interaktive përmes TCP:Implementimi i protokollit të besueshëm Udp për .Net

Siç mund ta shihni nga diagrami, në rast të humbjes së paketës, TCP do të zbulojë paketën e humbur dhe do ta raportojë atë tek dërguesi duke kërkuar numrin e segmentit të humbur.
Transferimi i të dhënave përmes protokollit UDP:Implementimi i protokollit të besueshëm Udp për .Net

UDP nuk ndërmerr asnjë hap për zbulimin e humbjeve. Kontrolli i gabimeve të transmetimit në protokollin UDP është tërësisht përgjegjësi e aplikacionit.

Zbulimi i gabimeve në protokollin TCP arrihet duke vendosur një lidhje me një nyje fundore, duke ruajtur gjendjen e asaj lidhjeje, duke treguar numrin e bajteve të dërguara në secilën kokë të paketës dhe duke njoftuar pranimet duke përdorur një numër konfirmimi.

Për më tepër, për të përmirësuar performancën (d.m.th. dërgimi i më shumë se një segmenti pa marrë një konfirmim), protokolli TCP përdor të ashtuquajturën dritare të transmetimit - numrin e bajteve të të dhënave që dërguesi i segmentit pret të marrë.

Për më shumë informacion rreth protokollit TCP, shihni Rfc 793, nga PZHU në Rfc 768ku në fakt përcaktohen.

Nga sa më sipër, është e qartë se për të krijuar një protokoll të besueshëm të dorëzimit të mesazhit mbi UDP (në tekstin e mëtejmë: UDP e besueshme), kërkohet të zbatohen mekanizma të transferimit të të dhënave të ngjashme me TCP. Gjegjësisht:

  • ruaj gjendjen e lidhjes
  • përdorni numërimin e segmenteve
  • përdorni paketa speciale konfirmimi
  • përdorni një mekanizëm të thjeshtuar dritareje për të rritur xhiron e protokollit

Për më tepër, ju duhet:

  • sinjalizon fillimin e një mesazhi, për të ndarë burimet për lidhjen
  • sinjalizoni fundin e një mesazhi, për të kaluar mesazhin e marrë në aplikacionin e sipërm dhe për të lëshuar burimet e protokollit
  • lejoni që protokolli specifik i lidhjes të çaktivizojë mekanizmin e konfirmimit të dorëzimit të funksionojë si UDP "e pastër".

Titulli i besueshëm i UDP

Kujtoni që një datagram UDP është i kapsuluar në një datagram IP. Paketa UDP e besueshme është "mbështjellë" në mënyrë të përshtatshme në një datagram UDP.
Kapsulim i besueshëm i kokës UDP:Implementimi i protokollit të besueshëm Udp për .Net

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

Implementimi i protokollit të besueshëm Udp për .Net

  • Flamujt - flamujt e kontrollit të paketës
  • MessageType - lloji i mesazhit që përdoret nga aplikacionet në rrjedhën e sipërme për t'u abonuar në mesazhe specifike
  • TransmissionId - numri i transmetimit, së bashku me adresën dhe portin e marrësit, identifikon në mënyrë unike lidhjen
  • Numri i paketës - numri i paketës
  • Opsionet - opsione shtesë të protokollit. Në rastin e paketës së parë, përdoret për të treguar madhësinë e mesazhit

Flamujt janë si më poshtë:

  • FirstPacket - paketa e parë e mesazhit
  • NoAsk - mesazhi nuk kërkon që të aktivizohet një mekanizëm konfirmimi
  • LastPacket - paketa e fundit e mesazhit
  • RequestForPacket - paketë konfirmimi ose kërkesë për një paketë të humbur

Parimet e përgjithshme të protokollit

Meqenëse UDP e besueshme është e fokusuar në transmetimin e garantuar të mesazhit midis dy nyjeve, ai duhet të jetë në gjendje të krijojë një lidhje me anën tjetër. Për të krijuar një lidhje, dërguesi dërgon një paketë me flamurin FirstPacket, përgjigja ndaj së cilës do të thotë se lidhja është vendosur. Të gjitha paketat e përgjigjes, ose, me fjalë të tjera, paketat e njohjes, gjithmonë vendosin vlerën e fushës Numri i paketës në një më shumë se vlera më e madhe e numrit të paketave të paketave të pranuara me sukses. Fusha e opsioneve për paketën e parë të dërguar është madhësia e mesazhit.

Një mekanizëm i ngjashëm përdoret për të përfunduar një lidhje. Flamuri LastPacket vendoset në paketën e fundit të mesazhit. Në paketën e përgjigjes, tregohet numri i paketës së fundit + 1, që për anën marrëse do të thotë dërgim i suksesshëm i mesazhit.
Diagrami i vendosjes dhe përfundimit të lidhjes:Implementimi i protokollit të besueshëm Udp për .Net

Kur të vendoset lidhja, fillon transferimi i të dhënave. Të dhënat transmetohen në blloqe paketash. Çdo bllok, përveç atij të fundit, përmban një numër fiks paketash. Është e barabartë me madhësinë e dritares së pranimit/transmetimit. Blloku i fundit i të dhënave mund të ketë më pak pako. Pas dërgimit të çdo blloku, pala dërguese pret për një konfirmim të dorëzimit ose një kërkesë për të ridorëzuar paketat e humbura, duke e lënë dritaren e pranimit/transmetimit të hapur për të marrë përgjigjet. Pas marrjes së konfirmimit për dërgimin e bllokut, dritarja e marrjes/transmetimit zhvendoset dhe blloku tjetër i të dhënave dërgohet.

Pala marrëse merr paketat. Çdo paketë kontrollohet për të parë nëse bie brenda dritares së transmetimit. Paketat dhe dublikatat që nuk bien në dritare filtrohen. Sepse Nëse madhësia e dritares është fikse dhe e njëjtë për marrësin dhe dërguesin, atëherë në rastin kur një bllok paketash dorëzohet pa humbje, dritarja zhvendoset në marrjen e paketave të bllokut të ardhshëm të të dhënave dhe konfirmohet dërgesa. dërguar. Nëse dritarja nuk mbushet brenda periudhës së caktuar nga kohëmatësi i punës, atëherë do të fillojë një kontroll mbi të cilin paketat nuk janë dorëzuar dhe do të dërgohen kërkesat për ridorëzimin.
Diagrami i ritransmetimit:Implementimi i protokollit të besueshëm Udp për .Net

Kohët e fundit dhe kohëmatësit e protokollit

Ka disa arsye pse nuk mund të vendoset një lidhje. Për shembull, nëse pala marrëse është jashtë linje. Në këtë rast, kur përpiqeni të krijoni një lidhje, lidhja do të mbyllet me kohë. Zbatimi i besueshëm i UDP përdor dy kohëmatës për të caktuar afatet kohore. E para, kohëmatësi i punës, përdoret për të pritur një përgjigje nga hosti i largët. Nëse ndizet në anën e dërguesit, atëherë paketa e fundit e dërguar dërgohet sërish. Nëse kohëmatësi skadon tek marrësi, atëherë kryhet një kontroll për paketat e humbura dhe dërgohen kërkesat për ridorëzimin.

Kohëmatësi i dytë nevojitet për të mbyllur lidhjen në rast të mungesës së komunikimit midis nyjeve. Për anën e dërguesit, ai fillon menjëherë pas skadimit të kohëmatësit të punës dhe pret një përgjigje nga nyja e largët. Nëse nuk ka përgjigje brenda periudhës së caktuar, lidhja ndërpritet dhe burimet lirohen. Për anën marrëse, kohëmatësi i mbylljes së lidhjes fillon pasi kohëmatësi i punës skadon dy herë. Kjo është e nevojshme për t'u siguruar kundër humbjes së paketës së konfirmimit. Kur kohëmatësi skadon, lidhja gjithashtu ndërpritet dhe burimet lirohen.

Diagrami i besueshëm i gjendjes së transmetimit të UDP

Parimet e protokollit zbatohen në një makinë me gjendje të fundme, secila gjendje e së cilës është përgjegjëse për një logjikë të caktuar të përpunimit të paketave.
Diagrami i besueshëm i gjendjes UDP:

Implementimi i protokollit të besueshëm Udp për .Net

Mbyllur - nuk është në të vërtetë një gjendje, është një pikë fillimi dhe mbarimi për automatikun. Për shtetin Mbyllur merret një bllok i kontrollit të transmetimit, i cili, duke zbatuar një server asinkron UDP, i përcjell paketat në lidhjet e duhura dhe fillon përpunimin e gjendjes.

FirstPacketSending – gjendja fillestare në të cilën është lidhja dalëse kur dërgohet mesazhi.

Në këtë gjendje, dërgohet paketa e parë për mesazhet normale. Për mesazhet pa konfirmim dërgimi, ky është i vetmi shtet ku dërgohet i gjithë mesazhi.

Cikli i dërgimit – gjendja bazë për transmetimin e paketave të mesazheve.

Kalimi në të nga shteti FirstPacketSending kryhet pasi të jetë dërguar paketa e parë e mesazhit. Pikërisht në këtë gjendje vijnë të gjitha mirënjohjet dhe kërkesat për ritransmetime. Dalja nga ajo është e mundur në dy raste - në rast të dërgimit të suksesshëm të mesazhit ose me kohë.

FirstPacketReceived – gjendja fillestare për marrësin e mesazhit.

Ai kontrollon korrektësinë e fillimit të transmetimit, krijon strukturat e nevojshme dhe dërgon një konfirmim të marrjes së paketës së parë.

Për një mesazh që përbëhet nga një paketë e vetme dhe është dërguar pa përdorur dëshminë e dorëzimit, kjo është e vetmja gjendje. Pas përpunimit të një mesazhi të tillë, lidhja mbyllet.

grumbulluar – gjendja bazë për marrjen e paketave të mesazheve.

Ai shkruan paketat në ruajtje të përkohshme, kontrollon për humbjen e paketave, dërgon mirënjohje për dërgimin e një blloku paketash dhe të gjithë mesazhin dhe dërgon kërkesa për rishpërndarjen e paketave të humbura. Në rast të marrjes me sukses të të gjithë mesazhit, lidhja kalon në gjendje Kompletuar, përndryshe, një afat kohor përfundon.

Kompletuar – mbyllja e lidhjes në rast të marrjes me sukses të të gjithë mesazhit.

Kjo gjendje është e nevojshme për montimin e mesazhit dhe për rastin kur konfirmimi i dërgimit të mesazhit humbi gjatë rrugës për tek dërguesi. Kjo gjendje del me një afat kohor, por lidhja konsiderohet e mbyllur me sukses.

Më thellë në kod. njësia e kontrollit të transmetimit

Një nga elementët kryesorë të UDP-së së besueshme është blloku i kontrollit të transmetimit. Detyra e këtij blloku është të ruajë lidhjet aktuale dhe elementët ndihmës, të shpërndajë paketat hyrëse në lidhjet përkatëse, të sigurojë një ndërfaqe për dërgimin e paketave në një lidhje dhe të zbatojë protokollin API. Blloku i kontrollit të transmetimit merr paketa nga shtresa UDP dhe i përcjell ato në makinën shtetërore për përpunim. Për të marrë paketa, ai zbaton një server asinkron UDP.
Disa anëtarë të klasës ReliableUdpConnectionControlBlock:

internal class ReliableUdpConnectionControlBlock : IDisposable
{
  // массив байт для указанного ключа. Используется для сборки входящих сообщений    
  public ConcurrentDictionary<Tuple<EndPoint, Int32>, byte[]> IncomingStreams { get; private set;}
  // массив байт для указанного ключа. Используется для отправки исходящих сообщений.
  public ConcurrentDictionary<Tuple<EndPoint, Int32>, byte[]> OutcomingStreams { get; private set; }
  // connection record для указанного ключа.
  private readonly ConcurrentDictionary<Tuple<EndPoint, Int32>, ReliableUdpConnectionRecord> m_listOfHandlers;
  // список подписчиков на сообщения.
  private readonly List<ReliableUdpSubscribeObject> m_subscribers;    
  // локальный сокет    
  private Socket m_socketIn;
  // порт для входящих сообщений
  private int m_port;
  // локальный IP адрес
  private IPAddress m_ipAddress;    
  // локальная конечная точка    
  public IPEndPoint LocalEndpoint { get; private set; }    
  // коллекция предварительно инициализированных
  // состояний конечного автомата
  public StatesCollection States { get; private set; }
  // генератор случайных чисел. Используется для создания TransmissionId
  private readonly RNGCryptoServiceProvider m_randomCrypto;    	
  //...
}

Zbatimi i serverit asinkron UDP:

private void Receive()
{
  EndPoint connectedClient = new IPEndPoint(IPAddress.Any, 0);
  // создаем новый буфер, для каждого socket.BeginReceiveFrom 
  byte[] buffer = new byte[DefaultMaxPacketSize + ReliableUdpHeader.Length];
  // передаем буфер в качестве параметра для асинхронного метода
  this.m_socketIn.BeginReceiveFrom(buffer, 0, buffer.Length, SocketFlags.None, ref connectedClient, EndReceive, buffer);
}   

private void EndReceive(IAsyncResult ar)
{
  EndPoint connectedClient = new IPEndPoint(IPAddress.Any, 0);
  int bytesRead = this.m_socketIn.EndReceiveFrom(ar, ref connectedClient);
  //пакет получен, готовы принимать следующий        
  Receive();
  // т.к. простейший способ решить вопрос с буфером - получить ссылку на него 
  // из IAsyncResult.AsyncState        
  byte[] bytes = ((byte[]) ar.AsyncState).Slice(0, bytesRead);
  // получаем заголовок пакета        
  ReliableUdpHeader header;
  if (!ReliableUdpStateTools.ReadReliableUdpHeader(bytes, out header))
  {          
    // пришел некорректный пакет - отбрасываем его
    return;
  }
  // конструируем ключ для определения connection record’а для пакета
  Tuple<EndPoint, Int32> key = new Tuple<EndPoint, Int32>(connectedClient, header.TransmissionId);
  // получаем существующую connection record или создаем новую
  ReliableUdpConnectionRecord record = m_listOfHandlers.GetOrAdd(key, new ReliableUdpConnectionRecord(key, this, header.ReliableUdpMessageType));
  // запускаем пакет в обработку в конечный автомат
  record.State.ReceivePacket(record, header, bytes);
}

Për çdo transferim mesazhi, krijohet një strukturë që përmban informacione rreth lidhjes. Një strukturë e tillë quhet regjistrimi i lidhjes.
Disa anëtarë të klasës ReliableUdpConnectionRecord:

internal class ReliableUdpConnectionRecord : IDisposable
{    
  // массив байт с сообщением    
  public byte[] IncomingStream { get; set; }
  // ссылка на состояние конечного автомата    
  public ReliableUdpState State { get; set; }    
  // пара, однозначно определяющая connection record
  // в блоке управления передачей     
  public Tuple<EndPoint, Int32> Key { get; private set;}
  // нижняя граница приемного окна    
  public int WindowLowerBound;
  // размер окна передачи
  public readonly int WindowSize;     
  // номер пакета для отправки
  public int SndNext;
  // количество пакетов для отправки
  public int NumberOfPackets;
  // номер передачи (именно он и есть вторая часть Tuple)
  // для каждого сообщения свой	
  public readonly Int32 TransmissionId;
  // удаленный IP endpoint – собственно получатель сообщения
  public readonly IPEndPoint RemoteClient;
  // размер пакета, во избежание фрагментации на IP уровне
  // не должен превышать MTU – (IP.Header + UDP.Header + RelaibleUDP.Header)
  public readonly int BufferSize;
  // блок управления передачей
  public readonly ReliableUdpConnectionControlBlock Tcb;
  // инкапсулирует результаты асинхронной операции для BeginSendMessage/EndSendMessage
  public readonly AsyncResultSendMessage AsyncResult;
  // не отправлять пакеты подтверждения
  public bool IsNoAnswerNeeded;
  // последний корректно полученный пакет (всегда устанавливается в наибольший номер)
  public int RcvCurrent;
  // массив с номерами потерянных пакетов
  public int[] LostPackets { get; private set; }
  // пришел ли последний пакет. Используется как bool.
  public int IsLastPacketReceived = 0;
  //...
}

Më thellë në kod. shteteve

Shtetet implementojnë makinën shtetërore të protokollit të besueshëm UDP, ku bëhet përpunimi kryesor i paketave. Klasa abstrakte ReliableUdpState ofron një ndërfaqe për gjendjen:

Implementimi i protokollit të besueshëm Udp për .Net

E gjithë logjika e protokollit zbatohet nga klasat e paraqitura më sipër, së bashku me një klasë ndihmëse që ofron metoda statike, të tilla si, për shembull, ndërtimi i kokës ReliableUdp nga regjistrimi i lidhjes.

Më pas, ne do të shqyrtojmë në detaje zbatimin e metodave të ndërfaqes që përcaktojnë algoritmet bazë të protokollit.

Metoda DisposeByTimeout

Metoda DisposeByTimeout është përgjegjëse për lëshimin e burimeve të lidhjes pas një skadimi dhe për sinjalizimin e dërgimit të suksesshëm/të pasuksesshë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();
}

Është anashkaluar vetëm në shtet Kompletuar.
Kompletuar.DisposeByTimeout:

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

Metoda ProcessPackets

Metoda ProcessPackets është përgjegjëse për përpunimin shtesë të një pakete ose paketash. Telefonuar drejtpërdrejt ose nëpërmjet një kohëmatësi të pritjes së paketave.

Në gjendje grumbulluar metoda është anashkaluar dhe është përgjegjëse për kontrollimin e paketave të humbura dhe kalimin në gjendje Kompletuar, në rast të marrjes së paketës së fundit dhe kalimit të një kontrolli të suksesshëm
Assembling.ProcessPackets:

public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
  if (connectionRecord.IsDone != 0)
    return;
  if (!ReliableUdpStateTools.CheckForNoPacketLoss(connectionRecord, connectionRecord.IsLastPacketReceived != 0))
  {
    // есть потерянные пакеты, отсылаем запросы на них
    foreach (int seqNum in connectionRecord.LostPackets)
    {
      if (seqNum != 0)
      {
        ReliableUdpStateTools.SendAskForLostPacket(connectionRecord, seqNum);
      }
    }
    // устанавливаем таймер во второй раз, для повторной попытки передачи
    if (!connectionRecord.TimerSecondTry)
    {
      connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
      connectionRecord.TimerSecondTry = true;
      return;
    }
    // если после двух попыток срабатываний WaitForPacketTimer 
    // не удалось получить пакеты - запускаем таймер завершения соединения
    StartCloseWaitTimer(connectionRecord);
  }
  else if (connectionRecord.IsLastPacketReceived != 0)
  // успешная проверка 
  {
    // высылаем подтверждение о получении блока данных
    ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
    connectionRecord.State = connectionRecord.Tcb.States.Completed;
    connectionRecord.State.ProcessPackets(connectionRecord);
    // вместо моментальной реализации ресурсов
    // запускаем таймер, на случай, если
    // если последний ack не дойдет до отправителя и он запросит его снова.
    // по срабатыванию таймера - реализуем ресурсы
    // в состоянии Completed метод таймера переопределен
    StartCloseWaitTimer(connectionRecord);
  }
  // это случай, когда ack на блок пакетов был потерян
  else
  {
    if (!connectionRecord.TimerSecondTry)
    {
      ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
      connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
      connectionRecord.TimerSecondTry = true;
      return;
    }
    // запускаем таймер завершения соединения
    StartCloseWaitTimer(connectionRecord);
  }
}

Në gjendje Cikli i dërgimit kjo metodë thirret vetëm në një kohëmatës dhe është përgjegjëse për ridërgimin e mesazhit të fundit, si dhe aktivizimin e kohëmatësit të mbylljes së lidhjes.
Paketat SendingCycle.Process:

public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
  if (connectionRecord.IsDone != 0)
    return;        
  // отправляем повторно последний пакет 
  // ( в случае восстановления соединения узел-приемник заново отправит запросы, которые до него не дошли)        
  ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.RetransmissionCreateUdpPayload(connectionRecord, connectionRecord.SndNext - 1));
  // включаем таймер CloseWait – для ожидания восстановления соединения или его завершения
  StartCloseWaitTimer(connectionRecord);
}

Në gjendje Kompletuar metoda ndalon kohëmatësin që funksionon dhe ua dërgon mesazhin abonentëve.
Kompletuar.Paketat e procesit:

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

Metoda ReceivePacket

Në gjendje FirstPacketReceived Detyra kryesore e metodës është të përcaktojë nëse paketa e parë e mesazhit mbërriti në të vërtetë në ndërfaqe, dhe gjithashtu të mbledhë një mesazh të përbërë nga një paketë e vetme.
FirstPacketReceived.ReceivePacket:

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  if (!header.Flags.HasFlag(ReliableUdpHeaderFlags.FirstPacket))
    // отбрасываем пакет
    return;
  // комбинация двух флагов - FirstPacket и LastPacket - говорит что у нас единственное сообщение
  if (header.Flags.HasFlag(ReliableUdpHeaderFlags.FirstPacket) &
      header.Flags.HasFlag(ReliableUdpHeaderFlags.LastPacket))
  {
    ReliableUdpStateTools.CreateMessageFromSinglePacket(connectionRecord, header, payload.Slice(ReliableUdpHeader.Length, payload.Length));
    if (!header.Flags.HasFlag(ReliableUdpHeaderFlags.NoAsk))
    {
      // отправляем пакет подтверждение          
      ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
    }
    SetAsCompleted(connectionRecord);
    return;
  }
  // by design все packet numbers начинаются с 0;
  if (header.PacketNumber != 0)          
    return;
  ReliableUdpStateTools.InitIncomingBytesStorage(connectionRecord, header);
  ReliableUdpStateTools.WritePacketData(connectionRecord, header, payload);
  // считаем кол-во пакетов, которые должны прийти
  connectionRecord.NumberOfPackets = (int)Math.Ceiling((double) ((double) connectionRecord.IncomingStream.Length/(double) connectionRecord.BufferSize));
  // записываем номер последнего полученного пакета (0)
  connectionRecord.RcvCurrent = header.PacketNumber;
  // после сдвинули окно приема на 1
  connectionRecord.WindowLowerBound++;
  // переключаем состояние
  connectionRecord.State = connectionRecord.Tcb.States.Assembling;
  // если не требуется механизм подтверждение
  // запускаем таймер который высвободит все структуры         
  if (header.Flags.HasFlag(ReliableUdpHeaderFlags.NoAsk))
  {
    connectionRecord.CloseWaitTimer = new Timer(DisposeByTimeout, connectionRecord, connectionRecord.ShortTimerPeriod, -1);
  }
  else
  {
    ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
    connectionRecord.WaitForPacketsTimer = new Timer(CheckByTimer, connectionRecord, connectionRecord.ShortTimerPeriod, -1);
  }
}

Në gjendje Cikli i dërgimit kjo metodë anashkalohet për të pranuar konfirmimet e dorëzimit dhe kërkesat për ritransmetim.
SendingCycle.ReceivePacket:

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  if (connectionRecord.IsDone != 0)
    return;
  if (!header.Flags.HasFlag(ReliableUdpHeaderFlags.RequestForPacket))
    return;
  // расчет конечной границы окна
  // берется граница окна + 1, для получения подтверждений доставки
  int windowHighestBound = Math.Min((connectionRecord.WindowLowerBound + connectionRecord.WindowSize), (connectionRecord.NumberOfPackets));
  // проверка на попадание в окно        
  if (header.PacketNumber < connectionRecord.WindowLowerBound || header.PacketNumber > windowHighestBound)
    return;
  connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
  if (connectionRecord.CloseWaitTimer != null)
    connectionRecord.CloseWaitTimer.Change(-1, -1);
  // проверить на последний пакет:
  if (header.PacketNumber == connectionRecord.NumberOfPackets)
  {
    // передача завершена
    Interlocked.Increment(ref connectionRecord.IsDone);
    SetAsCompleted(connectionRecord);
    return;
  }
  // это ответ на первый пакет c подтверждением         
  if ((header.Flags.HasFlag(ReliableUdpHeaderFlags.FirstPacket) && header.PacketNumber == 1))
  {
    // без сдвига окна
    SendPacket(connectionRecord);
  }
  // пришло подтверждение о получении блока данных
  else if (header.PacketNumber == windowHighestBound)
  {
    // сдвигаем окно прием/передачи
    connectionRecord.WindowLowerBound += connectionRecord.WindowSize;
    // обнуляем массив контроля передачи
    connectionRecord.WindowControlArray.Nullify();
    // отправляем блок пакетов
    SendPacket(connectionRecord);
  }
  // это запрос на повторную передачу – отправляем требуемый пакет          
  else
    ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.RetransmissionCreateUdpPayload(connectionRecord, header.PacketNumber));
}

Në gjendje grumbulluar në metodën ReceivePacket, bëhet puna kryesore e montimit të një mesazhi nga paketat hyrëse.
Assembling.ReceivePacket:

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  if (connectionRecord.IsDone != 0)
    return;
  // обработка пакетов с отключенным механизмом подтверждения доставки
  if (header.Flags.HasFlag(ReliableUdpHeaderFlags.NoAsk))
  {
    // сбрасываем таймер
    connectionRecord.CloseWaitTimer.Change(connectionRecord.LongTimerPeriod, -1);
    // записываем данные
    ReliableUdpStateTools.WritePacketData(connectionRecord, header, payload);
    // если получили пакет с последним флагом - делаем завершаем          
    if (header.Flags.HasFlag(ReliableUdpHeaderFlags.LastPacket))
    {
      connectionRecord.State = connectionRecord.Tcb.States.Completed;
      connectionRecord.State.ProcessPackets(connectionRecord);
    }
    return;
  }        
  // расчет конечной границы окна
  int windowHighestBound = Math.Min((connectionRecord.WindowLowerBound + connectionRecord.WindowSize - 1), (connectionRecord.NumberOfPackets - 1));
  // отбрасываем не попадающие в окно пакеты
  if (header.PacketNumber < connectionRecord.WindowLowerBound || header.PacketNumber > (windowHighestBound))
    return;
  // отбрасываем дубликаты
  if (connectionRecord.WindowControlArray.Contains(header.PacketNumber))
    return;
  // записываем данные 
  ReliableUdpStateTools.WritePacketData(connectionRecord, header, payload);
  // увеличиваем счетчик пакетов        
  connectionRecord.PacketCounter++;
  // записываем в массив управления окном текущий номер пакета        
  connectionRecord.WindowControlArray[header.PacketNumber - connectionRecord.WindowLowerBound] = header.PacketNumber;
  // устанавливаем наибольший пришедший пакет        
  if (header.PacketNumber > connectionRecord.RcvCurrent)
    connectionRecord.RcvCurrent = header.PacketNumber;
  // перезапускам таймеры        
  connectionRecord.TimerSecondTry = false;
  connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
  if (connectionRecord.CloseWaitTimer != null)
    connectionRecord.CloseWaitTimer.Change(-1, -1);
  // если пришел последний пакет
  if (header.Flags.HasFlag(ReliableUdpHeaderFlags.LastPacket))
  {
    Interlocked.Increment(ref connectionRecord.IsLastPacketReceived);
  }
  // если нам пришли все пакеты окна, то сбрасываем счетчик
  // и высылаем пакет подтверждение
  else if (connectionRecord.PacketCounter == connectionRecord.WindowSize)
  {
    // сбрасываем счетчик.      
    connectionRecord.PacketCounter = 0;
    // сдвинули окно передачи
    connectionRecord.WindowLowerBound += connectionRecord.WindowSize;
    // обнуление массива управления передачей
    connectionRecord.WindowControlArray.Nullify();
    ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
  }
  // если последний пакет уже имеется        
  if (Thread.VolatileRead(ref connectionRecord.IsLastPacketReceived) != 0)
  {
    // проверяем пакеты          
    ProcessPackets(connectionRecord);
  }
}

Në gjendje Kompletuar detyra e vetme e metodës është të dërgojë një rikonfirmim për dërgimin e suksesshëm të mesazhit.
Kompletuar.Paketa e Marrjes:

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

Metoda e dërgimit të paketës

Në gjendje FirstPacketSending kjo metodë dërgon paketën e parë të të dhënave, ose nëse mesazhi nuk kërkon konfirmim të dorëzimit, të gjithë mesazhin.
FirstPacketSending.SendPacket:

public override void SendPacket(ReliableUdpConnectionRecord connectionRecord)
{
  connectionRecord.PacketCounter = 0;
  connectionRecord.SndNext = 0;
  connectionRecord.WindowLowerBound = 0;       
  // если подтверждения не требуется - отправляем все пакеты
  // и высвобождаем ресурсы
  if (connectionRecord.IsNoAnswerNeeded)
  {
    // Здесь происходит отправка As Is
    do
    {
      ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.CreateUdpPayload(connectionRecord, ReliableUdpStateTools. CreateReliableUdpHeader(connectionRecord)));
      connectionRecord.SndNext++;
    } while (connectionRecord.SndNext < connectionRecord.NumberOfPackets);
    SetAsCompleted(connectionRecord);
    return;
  }
  // создаем заголовок пакета и отправляем его 
  ReliableUdpHeader header = ReliableUdpStateTools.CreateReliableUdpHeader(connectionRecord);
  ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.CreateUdpPayload(connectionRecord, header));
  // увеличиваем счетчик
  connectionRecord.SndNext++;
  // сдвигаем окно
  connectionRecord.WindowLowerBound++;
  connectionRecord.State = connectionRecord.Tcb.States.SendingCycle;
  // Запускаем таймер
  connectionRecord.WaitForPacketsTimer = new Timer(CheckByTimer, connectionRecord, connectionRecord.ShortTimerPeriod, -1);
}

Në gjendje Cikli i dërgimit në këtë metodë dërgohet një bllok paketash.
SendingCycle.SendPacket:

public override void SendPacket(ReliableUdpConnectionRecord connectionRecord)
{      
  // отправляем блок пакетов      
  for (connectionRecord.PacketCounter = 0;
        connectionRecord.PacketCounter < connectionRecord.WindowSize &&
        connectionRecord.SndNext < connectionRecord.NumberOfPackets;
        connectionRecord.PacketCounter++)
  {
    ReliableUdpHeader header = ReliableUdpStateTools.CreateReliableUdpHeader(connectionRecord);
    ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.CreateUdpPayload(connectionRecord, header));
    connectionRecord.SndNext++;
  }
  // на случай большого окна передачи, перезапускаем таймер после отправки
  connectionRecord.WaitForPacketsTimer.Change( connectionRecord.ShortTimerPeriod, -1 );
  if ( connectionRecord.CloseWaitTimer != null )
  {
    connectionRecord.CloseWaitTimer.Change( -1, -1 );
  }
}

Më thellë në kod. Krijimi dhe vendosja e lidhjeve

Tani që kemi parë gjendjet bazë dhe metodat e përdorura për të trajtuar gjendjet, le të zbërthejmë disa shembuj se si funksionon protokolli me pak më shumë detaje.
Diagrami i transmetimit të të dhënave në kushte normale:Implementimi i protokollit të besueshëm Udp për .Net

Konsideroni në detaje krijimin regjistrimi i lidhjes për të lidhur dhe dërguar paketën e parë. Transferimi inicohet gjithmonë nga aplikacioni që thërret API për mesazhin e dërgimit. Më pas, thirret metoda StartTransmission e bllokut të kontrollit të transmetimit, e cila fillon transmetimin e të dhënave për mesazhin e ri.
Krijimi i një lidhjeje dalëse:

private void StartTransmission(ReliableUdpMessage reliableUdpMessage, EndPoint endPoint, AsyncResultSendMessage asyncResult)
{
  if (m_isListenerStarted == 0)
  {
    if (this.LocalEndpoint == null)
    {
      throw new ArgumentNullException( "", "You must use constructor with parameters or start listener before sending message" );
    }
    // запускаем обработку входящих пакетов
    StartListener(LocalEndpoint);
  }
  // создаем ключ для словаря, на основе EndPoint и ReliableUdpHeader.TransmissionId        
  byte[] transmissionId = new byte[4];
  // создаем случайный номер transmissionId        
  m_randomCrypto.GetBytes(transmissionId);
  Tuple<EndPoint, Int32> key = new Tuple<EndPoint, Int32>(endPoint, BitConverter.ToInt32(transmissionId, 0));
  // создаем новую запись для соединения и проверяем, 
  // существует ли уже такой номер в наших словарях
  if (!m_listOfHandlers.TryAdd(key, new ReliableUdpConnectionRecord(key, this, reliableUdpMessage, asyncResult)))
  {
    // если существует – то повторно генерируем случайный номер 
    m_randomCrypto.GetBytes(transmissionId);
    key = new Tuple<EndPoint, Int32>(endPoint, BitConverter.ToInt32(transmissionId, 0));
    if (!m_listOfHandlers.TryAdd(key, new ReliableUdpConnectionRecord(key, this, reliableUdpMessage, asyncResult)))
      // если снова не удалось – генерируем исключение
      throw new ArgumentException("Pair TransmissionId & EndPoint is already exists in the dictionary");
  }
  // запустили состояние в обработку         
  m_listOfHandlers[key].State.SendPacket(m_listOfHandlers[key]);
}

Dërgimi i paketës së parë (gjendja e parë e dërgimit):

public override void SendPacket(ReliableUdpConnectionRecord connectionRecord)
{
  connectionRecord.PacketCounter = 0;
  connectionRecord.SndNext = 0;
  connectionRecord.WindowLowerBound = 0;       
  // ... 
  // создаем заголовок пакета и отправляем его 
  ReliableUdpHeader header = ReliableUdpStateTools.CreateReliableUdpHeader(connectionRecord);
  ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.CreateUdpPayload(connectionRecord, header));
  // увеличиваем счетчик
  connectionRecord.SndNext++;
  // сдвигаем окно
  connectionRecord.WindowLowerBound++;
  // переходим в состояние SendingCycle
  connectionRecord.State = connectionRecord.Tcb.States.SendingCycle;
  // Запускаем таймер
  connectionRecord.WaitForPacketsTimer = new Timer(CheckByTimer, connectionRecord, connectionRecord.ShortTimerPeriod, -1);
}

Pas dërgimit të paketës së parë, dërguesi hyn në gjendje Cikli i dërgimit – prisni konfirmimin e dorëzimit të paketës.
Ana marrëse, duke përdorur metodën EndReceive, merr paketën e dërguar, krijon një të re regjistrimi i lidhjes dhe e kalon këtë paketë, me një kokë të analizuar paraprakisht, në metodën ReceivePacket të gjendjes për përpunim FirstPacketReceived
Krijimi i një lidhjeje në anën marrëse:

private void EndReceive(IAsyncResult ar)
{
  // ...
  // пакет получен
  // парсим заголовок пакета        
  ReliableUdpHeader header;
  if (!ReliableUdpStateTools.ReadReliableUdpHeader(bytes, out header))
  {          
    // пришел некорректный пакет - отбрасываем его
    return;
  }
  // конструируем ключ для определения connection record’а для пакета
  Tuple<EndPoint, Int32> key = new Tuple<EndPoint, Int32>(connectedClient, header.TransmissionId);
  // получаем существующую connection record или создаем новую
  ReliableUdpConnectionRecord record = m_listOfHandlers.GetOrAdd(key, new ReliableUdpConnectionRecord(key, this, header. ReliableUdpMessageType));
  // запускаем пакет в обработку в конечный автомат
  record.State.ReceivePacket(record, header, bytes);
}

Marrja e paketës së parë dhe dërgimi i një konfirmimi (gjendja e parë e PacketReceived):

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  if (!header.Flags.HasFlag(ReliableUdpHeaderFlags.FirstPacket))
    // отбрасываем пакет
    return;
  // ...
  // by design все packet numbers начинаются с 0;
  if (header.PacketNumber != 0)          
    return;
  // инициализируем массив для хранения частей сообщения
  ReliableUdpStateTools.InitIncomingBytesStorage(connectionRecord, header);
  // записываем данные пакет в массив
  ReliableUdpStateTools.WritePacketData(connectionRecord, header, payload);
  // считаем кол-во пакетов, которые должны прийти
  connectionRecord.NumberOfPackets = (int)Math.Ceiling((double) ((double) connectionRecord.IncomingStream.Length/(double) connectionRecord.BufferSize));
  // записываем номер последнего полученного пакета (0)
  connectionRecord.RcvCurrent = header.PacketNumber;
  // после сдвинули окно приема на 1
  connectionRecord.WindowLowerBound++;
  // переключаем состояние
  connectionRecord.State = connectionRecord.Tcb.States.Assembling;  
  if (/*если не требуется механизм подтверждение*/)
  // ...
  else
  {
    // отправляем подтверждение
    ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
    connectionRecord.WaitForPacketsTimer = new Timer(CheckByTimer, connectionRecord, connectionRecord.ShortTimerPeriod, -1);
  }
}

Më thellë në kod. Mbyllja e lidhjes në kohë

Trajtimi i afatit është një pjesë e rëndësishme e UDP e besueshme. Konsideroni një shembull në të cilin një nyje e ndërmjetme dështoi dhe shpërndarja e të dhënave në të dy drejtimet u bë e pamundur.
Diagrami për mbylljen e një lidhjeje me kohëzgjatje:Implementimi i protokollit të besueshëm Udp për .Net

Siç shihet nga diagrami, kohëmatësi i punës së dërguesit fillon menjëherë pas dërgimit të një blloku paketash. Kjo ndodh në metodën SendPacket të gjendjes Cikli i dërgimit.
Aktivizimi i kohëmatësit të punës (gjendja e ciklit të dërgimit):

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

Periudhat e kohëmatësit vendosen kur krijohet lidhja. Periudha e paracaktuar ShortTimer është 5 sekonda. Në shembull, është vendosur në 1,5 sekonda.

Për një lidhje hyrëse, kohëmatësi fillon pas marrjes së paketës së fundit të të dhënave hyrëse, kjo ndodh në metodën ReceivePacket të gjendjes grumbulluar
Aktivizimi i kohëmatësit të punës (gjendja e montimit):

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  // ... 
  // перезапускаем таймеры        
  connectionRecord.TimerSecondTry = false;
  connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
  if (connectionRecord.CloseWaitTimer != null)
    connectionRecord.CloseWaitTimer.Change(-1, -1);
  // ...
}

Nuk ka mbërritur më paketë në lidhjen hyrëse gjatë pritjes së kohëmatësit të punës. Kohëmatësi u fik dhe thirri metodën ProcessPackets, ku u gjetën paketat e humbura dhe u dërguan kërkesat për ridorëzimin për herë të parë.
Dërgimi i kërkesave për ridorëzimin (gjendja e montimit):

public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
  // ...        
  if (/*проверка на потерянные пакеты */)
  {
    // отправляем запросы на повторную доставку
    // устанавливаем таймер во второй раз, для повторной попытки передачи
    if (!connectionRecord.TimerSecondTry)
    {
      connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
    connectionRecord.TimerSecondTry = true;
    return;
    }
  // если после двух попыток срабатываний WaitForPacketTimer 
  // не удалось получить пакеты - запускаем таймер завершения соединения
  StartCloseWaitTimer(connectionRecord);
  }
  else if (/*пришел последний пакет и успешная проверка */)
  {
    // ...
    StartCloseWaitTimer(connectionRecord);
  }
  // если ack на блок пакетов был потерян
  else
  { 
    if (!connectionRecord.TimerSecondTry)
    {
      // повторно отсылаем ack
      connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
      connectionRecord.TimerSecondTry = true;
      return;
    }
    // запускаем таймер завершения соединения
    StartCloseWaitTimer(connectionRecord);
  }
}

Ndryshorja TimerSecondTry është vendosur në i vërtetë. Kjo variabël është përgjegjëse për rinisjen e kohëmatësit të punës.

Nga ana e dërguesit, kohëmatësi i punës gjithashtu aktivizohet dhe paketa e fundit e dërguar dërgohet sërish.
Aktivizimi i kohëmatësit të mbylljes së lidhjes (gjendja e ciklit të dërgimit):

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

Pas kësaj, kohëmatësi i mbylljes së lidhjes fillon në lidhjen dalëse.
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 ndërprerjes së kohëmatësit të mbylljes së lidhjes është 30 sekonda si parazgjedhje.

Pas një kohe të shkurtër, kohëmatësi i punës në anën e marrësit ndizet përsëri, kërkesat dërgohen përsëri, pas së cilës kohëmatësi i mbylljes së lidhjes fillon për lidhjen hyrëse

Kur aktivizohen kohëmatësit e mbylljes, të gjitha burimet e të dy regjistrimeve të lidhjes lëshohen. Dërguesi raporton dështimin e dorëzimit në aplikacionin në rrjedhën e sipërme (shih API UDP e besueshme).
Lëshimi i 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ë thellë në kod. Rivendosja e transferimit të të dhënave

Diagrami i rikuperimit të transmetimit të të dhënave në rast të humbjes së paketës:Implementimi i protokollit të besueshëm Udp për .Net

Siç është diskutuar tashmë në mbylljen e lidhjes në kohë, kur kohëmatësi i punës skadon, marrësi do të kontrollojë për paketa të humbura. Në rast të humbjes së paketës, do të përpilohet një listë e numrit të paketave që nuk kanë arritur te marrësi. Këta numra futen në grupin LostPackets të një lidhjeje specifike dhe dërgohen kërkesat për ridorëzimin.
Dërgimi i kërkesave për ridorëzimin e paketave (gjendja e montimit):

public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
  //...
  if (!ReliableUdpStateTools.CheckForNoPacketLoss(connectionRecord, connectionRecord.IsLastPacketReceived != 0))
  {
    // есть потерянные пакеты, отсылаем запросы на них
    foreach (int seqNum in connectionRecord.LostPackets)
    {
      if (seqNum != 0)
      {
        ReliableUdpStateTools.SendAskForLostPacket(connectionRecord, seqNum);
      }
    }
    // ...
  }
}

Dërguesi do të pranojë kërkesën e ridorëzimit dhe do të dërgojë paketat që mungojnë. Vlen të theksohet se në këtë moment dërguesi ka nisur tashmë kohëmatësin e mbylljes së lidhjes dhe, kur merret një kërkesë, ai rivendoset.
Ridërgimi i paketave të humbura (gjendja e SendingCycle):

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  // ...
  connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
  // сброс таймера закрытия соединения 
  if (connectionRecord.CloseWaitTimer != null)
    connectionRecord.CloseWaitTimer.Change(-1, -1);
  // ...
  // это запрос на повторную передачу – отправляем требуемый пакет          
  else
    ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.RetransmissionCreateUdpPayload(connectionRecord, header.PacketNumber));
}

Paketa e ridërguar (paketa #3 në diagram) merret nga lidhja hyrëse. Bëhet një kontroll për të parë nëse dritarja e marrjes është e plotë dhe nëse transmetimi normal i të dhënave është rikthyer.
Kontrollimi i goditjeve në dritaren e marrjes (gjendja e montimit):

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  // ...
  // увеличиваем счетчик пакетов        
  connectionRecord.PacketCounter++;
  // записываем в массив управления окном текущий номер пакета        
  connectionRecord.WindowControlArray[header.PacketNumber - connectionRecord.WindowLowerBound] = header.PacketNumber;
  // устанавливаем наибольший пришедший пакет        
  if (header.PacketNumber > connectionRecord.RcvCurrent)
    connectionRecord.RcvCurrent = header.PacketNumber;
  // перезапускам таймеры        
  connectionRecord.TimerSecondTry = false;
  connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
  if (connectionRecord.CloseWaitTimer != null)
    connectionRecord.CloseWaitTimer.Change(-1, -1);
  // ...
  // если нам пришли все пакеты окна, то сбрасываем счетчик
  // и высылаем пакет подтверждение
  else if (connectionRecord.PacketCounter == connectionRecord.WindowSize)
  {
    // сбрасываем счетчик.      
    connectionRecord.PacketCounter = 0;
    // сдвинули окно передачи
    connectionRecord.WindowLowerBound += connectionRecord.WindowSize;
    // обнуление массива управления передачей
    connectionRecord.WindowControlArray.Nullify();
    ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
  }
  // ...
}

API e besueshme UDP

Për të bashkëvepruar me protokollin e transferimit të të dhënave, ekziston një klasë e hapur Reliable Udp, e cila është një mbështjellës mbi bllokun e kontrollit të transferimit. Këtu janë anëtarët më të rëndësishëm të klasës:

public sealed class ReliableUdp : IDisposable
{
  // получает локальную конечную точку
  public IPEndPoint LocalEndpoint    
  // создает экземпляр ReliableUdp и запускает
  // прослушивание входящих пакетов на указанном IP адресе
  // и порту. Значение 0 для порта означает использование
  // динамически выделенного порта
  public ReliableUdp(IPAddress localAddress, int port = 0) 
  // подписка на получение входящих сообщений
  public ReliableUdpSubscribeObject SubscribeOnMessages(ReliableUdpMessageCallback callback, ReliableUdpMessageTypes messageType = ReliableUdpMessageTypes.Any, IPEndPoint ipEndPoint = null)    
  // отписка от получения сообщений
  public void Unsubscribe(ReliableUdpSubscribeObject subscribeObject)
  // асинхронно отправить сообщение 
  // Примечание: совместимость с XP и Server 2003 не теряется, т.к. используется .NET Framework 4.0
  public Task<bool> SendMessageAsync(ReliableUdpMessage reliableUdpMessage, IPEndPoint remoteEndPoint, CancellationToken cToken)
  // начать асинхронную отправку сообщения
  public IAsyncResult BeginSendMessage(ReliableUdpMessage reliableUdpMessage, IPEndPoint remoteEndPoint, AsyncCallback asyncCallback, Object state)
  // получить результат асинхронной отправки
  public bool EndSendMessage(IAsyncResult asyncResult)  
  // очистить ресурсы
  public void Dispose()    
}

Mesazhet merren me abonim. Delegimi i nënshkrimit për metodën e kthimit të thirrjes:

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

Mesazh:

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

Për t'u abonuar në një lloj mesazhi specifik dhe/ose një dërgues specifik, përdoren dy parametra opsionalë: ReliableUdpMessageTypes messageType dhe IPEndPoint ipEndPoint.

Llojet e mesazheve:

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

Mesazhi dërgohet në mënyrë asinkrone; për këtë, protokolli zbaton një model programimi asinkron:

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

Rezultati i dërgimit të një mesazhi do të jetë i vërtetë - nëse mesazhi mbërriti me sukses te marrësi dhe i rremë - nëse lidhja u mbyll me skadimin e kohës:

public bool EndSendMessage(IAsyncResult asyncResult)

Përfundim

Shumë nuk është përshkruar në këtë artikull. Mekanizmat e përputhjes së temave, trajtimi i përjashtimeve dhe gabimeve, zbatimi i metodave të dërgimit të mesazheve asinkrone. Por thelbi i protokollit, përshkrimi i logjikës për përpunimin e paketave, vendosjen e një lidhjeje dhe trajtimin e afateve, duhet të jetë i qartë për ju.

Versioni i demonstruar i protokollit të besueshëm të dorëzimit është mjaft i fortë dhe fleksibël për të përmbushur kërkesat e përcaktuara më parë. Por dua të shtoj se zbatimi i përshkruar mund të përmirësohet. Për shembull, për të rritur xhiron dhe ndryshimin dinamik të periudhave të kohëmatësit, mekanizma të tillë si dritarja rrëshqitëse dhe RTT mund të shtohen në protokoll, gjithashtu do të jetë e dobishme të zbatohet një mekanizëm për përcaktimin e MTU midis nyjeve të lidhjes (por vetëm nëse dërgohen mesazhe të mëdha) .

Faleminderit për vëmendjen tuaj, pres me padurim komentet dhe komentet tuaja.

PS Për ata që janë të interesuar për detajet ose thjesht duan të testojnë protokollin, lidhja e projektit në GitHube:
Projekt i besueshëm UDP

Lidhje dhe artikuj të dobishëm

  1. Specifikimi i protokollit TCP: në anglisht и në rusisht
  2. Specifikimi i protokollit UDP: në anglisht и në rusisht
  3. Diskutimi i protokollit RUDP: draft-ietf-sigtran-reliable-udp-00
  4. Protokolli i besueshëm i të dhënave: Rfc 908 и Rfc 1151
  5. Një zbatim i thjeshtë i konfirmimit të dorëzimit mbi UDP: Merrni kontrollin total të rrjeteve tuaja me .NET dhe UDP
  6. Artikulli që përshkruan mekanizmat e kalimit të NAT: Komunikimi Peer-to-Peer nëpërkthyesit e adresave të rrjetit
  7. Zbatimi i modelit të programimit asinkron: Zbatimi i Modelit të Programimit Asinkron CLR и Si të zbatohet modeli i projektimit IAsyncResult
  8. Transferimi i modelit të programimit asinkron në modelin asinkron të bazuar në detyrë (APM në TAP):
    TPL dhe Programimi Asinkron .NET tradicional
    Ndërveproni me modele dhe lloje të tjera asinkrone

Përditësimi: Faleminderit bashkiake и sidristij për idenë e shtimit të një detyre në ndërfaqe. Përputhshmëria e bibliotekës me sistemet e vjetra operative nuk është cenuar, sepse Korniza e 4-të mbështet serverin XP dhe 2003.

Burimi: www.habr.com

Shto një koment