.Net-erako Reliable Udp protokoloaren ezarpena

Internet aspaldi aldatu da. Interneten protokolo nagusietako bat - UDP aplikazioek erabiltzen dute datagramak eta emisioak emateko ez ezik, sareko nodoen artean "peer-to-peer" konexioak eskaintzeko ere. Bere diseinu sinplea dela eta, protokolo honek aurretik aurreikusi gabeko erabilera asko ditu, hala ere, protokoloaren gabeziak, hala nola, entrega bermatua ez izatea, ez dira inon desagertu. Artikulu honek UDPren bidez bermatutako entrega protokoloaren ezarpena deskribatzen du.
Edukia:Sarrera
Protokoloaren Baldintzak
UDP goiburu fidagarria
Protokoloaren printzipio orokorrak
Denbora-muga eta protokolo-tenporizadoreak
UDP transmisio-egoeren diagrama fidagarria
Kodean sakondu. transmisioa kontrolatzeko unitatea
Kodean sakondu. estatuak

Kodean sakondu. Konexioak sortzea eta ezartzea
Kodean sakondu. Konexioa ixtea denbora-mugan
Kodean sakondu. Datuen transferentzia leheneratzea
UDP API fidagarria
Ondorioa
Esteka eta artikulu erabilgarriak

Sarrera

Interneten jatorrizko arkitekturak helbide-espazio homogeneo bat suposatzen zuen, non nodo bakoitzak IP helbide global eta bakarra zuen eta beste nodo batzuekin zuzenean komunika zezakeen. Orain Internetek, hain zuzen ere, beste arkitektura bat du: IP helbide globalen eremu bat eta NAT gailuen atzean helbide pribatuak dituzten eremu asko.Arkitektura honetan, helbide-espazio globalean dauden gailuak soilik erraz komunikatu daitezke sareko edonorekin, IP helbide bakarra eta globalki bideragarria dutelako. Sare pribatuko nodo bat sare bereko beste nodo batzuetara konekta daiteke, eta helbide-espazio orokorreko beste nodo ezagun batzuetara ere konekta daiteke. Elkarreragin hori sareko helbideen itzulpen mekanismoari esker lortzen da neurri handi batean. NAT gailuek, hala nola, Wi-Fi bideratzaileak, itzulpen-taula-sarrera bereziak sortzen dituzte irteerako konexioetarako eta IP helbideak eta ataka-zenbakiak aldatzen dituzte paketeetan. Horrek sare pribatutik helbide-espazio orokorreko ostalarietara irteerako konexioak ahalbidetzen ditu. Baina, aldi berean, NAT gailuek normalean sarrerako trafiko guztia blokeatzen dute, sarrerako konexioetarako arau bereiziak ezartzen ez badira.

Interneten arkitektura hau nahikoa zuzena da bezero-zerbitzariaren komunikaziorako, non bezeroak sare pribatuetan egon daitezkeen eta zerbitzariek helbide globala duten. Baina zailtasunak sortzen ditu bi nodoen arteko konexio zuzenerako hainbat sare pribatuak. Bi nodoen arteko konexio zuzena garrantzitsua da berdinen arteko aplikazioetarako, hala nola ahots transmisioa (Skype), ordenagailu batera urruneko sarbidea lortzeko (TeamViewer) edo lineako jokoetarako.

Sare pribatu ezberdinetako gailuen arteko peer-to-peer konexioa ezartzeko metodorik eraginkorrenetako bat zulo-zulaketa deritzo. Teknika hau UDP protokoloan oinarritutako aplikazioekin erabiltzen da gehien.

Baina zure aplikazioak datuen bidalketa bermatua eskatzen badu, adibidez, fitxategiak ordenagailuen artean transferitzen badituzu, orduan UDP erabiltzeak zailtasun asko izango ditu UDP ez delako bermatutako bidalketa protokoloa eta ez duelako paketeen entrega ordenan ematen, TCP ez bezala. protokoloa.

Kasu honetan, paketeen bidalketa bermatzeko, beharrezkoa da beharrezko funtzionalitateak eskaintzen dituen eta UDPren gainean funtzionatzen duen aplikazio-geruzako protokolo bat ezartzea.

Berehala adierazi nahi dut TCP zulo-zulo teknika bat dagoela sare pribatu ezberdinetako nodoen artean TCP konexioak ezartzeko, baina NAT gailu askoren laguntza falta dela eta, normalean ez da konektatzeko modu nagusitzat hartzen. halako nodoak.

Artikulu honen gainerako zatian, bermatutako entrega protokoloaren ezarpenean bakarrik zentratuko naiz. UDP zulaketa-teknikaren ezarpena hurrengo artikuluetan deskribatuko da.

Protokoloaren Baldintzak

  1. Pakete bidalketa fidagarria feedback-mekanismo positibo baten bidez ezarrita (aitorpen positiboa deritzona)
  2. Big data eraginkortasunez transferitzeko beharra, hau da. protokoloak beharrezkoak ez diren paketeen transmisioa saihestu behar du
  3. Bidalketa berresteko mekanismoa bertan behera uztea posible izan beharko litzateke (UDP protokolo "puru" gisa funtzionatzeko gaitasuna)
  4. Agindu modua ezartzeko gaitasuna, mezu bakoitzaren berrespenarekin
  5. Protokoloaren bidez datuak transferitzeko oinarrizko unitatea mezu bat izan behar du

Baldintza hauek, neurri handi batean, bat egiten dute atalean deskribatutako Reliable Data Protocol eskakizunekin rfc 908 и rfc 1151, eta estandar horietan oinarritu nintzen protokolo hau garatzerakoan.

Baldintza hauek ulertzeko, ikus ditzagun TCP eta UDP protokoloak erabiliz bi sare-nodoen arteko datu-transferentziaren denbora. Bi kasuetan pakete bat galduko dugu.
Datu interaktiboak ez diren TCP bidez transferitzea:.Net-erako Reliable Udp protokoloaren ezarpena

Diagraman ikus dezakezun bezala, paketea galtzen bada, TCPk galdutako paketea detektatuko du eta igorleari jakinaraziko dio galdutako segmentuaren zenbakia eskatuz.
Datuen transferentzia UDP protokoloaren bidez:.Net-erako Reliable Udp protokoloaren ezarpena

UDP-k ez du galerak detektatzeko urratsik hartzen. UDP protokoloko transmisio-akatsen kontrola aplikazioaren ardura osoa da.

TCP protokoloan akatsak hautematea amaierako nodo batekin konexio bat ezarriz, konexio horren egoera gordez, paketeen goiburu bakoitzean bidalitako byte kopurua adieraziz eta ordainagiriak aitorpen-zenbaki baten bidez jakinaraziz lortzen da.

Gainera, errendimendua hobetzeko (hau da, segmentu bat baino gehiago bidaltzea aitorpenik jaso gabe), TCP protokoloak transmisio-leihoa deritzona erabiltzen du, segmentuaren igorleak jasotzea espero duen datu-kopurua.

TCP protokoloari buruzko informazio gehiago lortzeko, ikus rfc 793, UDP-tik rfc 768non, hain zuzen ere, definitzen diren.

Aurrekoa ikusita, argi dago UDP bidez mezuak bidaltzeko protokolo fidagarri bat sortzeko (aurrerantzean UDP fidagarria), TCPren antzeko datuak transferitzeko mekanismoak ezartzea beharrezkoa da. Alegia:

  • gorde konexio-egoera
  • erabili segmentuen zenbaketa
  • erabili berrespen pakete bereziak
  • erabili leiho-mekanismo sinplifikatu bat protokoloaren errendimendua handitzeko

Gainera, behar duzu:

  • mezu baten hasiera adierazi, konexiorako baliabideak esleitzeko
  • mezu baten amaiera adierazi, jasotako mezua upstream aplikaziora pasatzeko eta protokolo-baliabideak askatzeko
  • baimendu konexio-protokolo espezifikoari bidalketa berresteko mekanismoa UDP "puru" gisa funtziona dezan desgaitzeko

UDP goiburu fidagarria

Gogoratu UDP datagrama bat IP datagrama batean kapsulatzen dela. UDP pakete fidagarria behar bezala "bilduta" dago UDP datagrama batean.
UDP goiburuko enkapsulazio fidagarria:.Net-erako Reliable Udp protokoloaren ezarpena

UDP fidagarriaren goiburuaren egitura nahiko erraza da:

.Net-erako Reliable Udp protokoloaren ezarpena

  • Banderak - paketeak kontrolatzeko banderak
  • MessageType - upstream aplikazioek mezu zehatzetara harpidetzeko erabiltzen duten mezu mota
  • TransmissionId - transmisioaren zenbakiak, hartzailearen helbidea eta ataka batera, konexioa identifikatzen du modu bakarra.
  • PacketNumber - paketearen zenbakia
  • Aukerak - protokolo-aukera gehigarriak. Lehenengo paketearen kasuan, mezuaren tamaina adierazteko erabiltzen da

Banderak hauek dira:

  • FirstPacket - mezuaren lehen paketea
  • NoAsk - mezuak ez du aitorpen mekanismorik gaitu behar
  • LastPacket - mezuaren azken paketea
  • RequestForPacket - berrespen paketea edo galdutako pakete baten eskaera

Protokoloaren printzipio orokorrak

UDP fidagarria bi nodoen arteko mezuen transmisioa bermatuta dagoenez, beste aldearekin konexio bat ezartzeko gai izan behar du. Konexio bat ezartzeko, igorleak pakete bat bidaltzen du FirstPacket banderarekin, eta horren erantzunak konexioa ezarrita dagoela esan nahi du. Erantzun-pakete guztiek edo, beste era batera esanda, aitorpen-paketeek, beti ezartzen dute PacketNumber eremuaren balioa arrakastaz jasotako paketeen PacketNumber balio handiena baino bat gehiago. Bidalitako lehen paketearen Aukerak eremua mezuaren tamaina da.

Konexio bat amaitzeko antzeko mekanismo bat erabiltzen da. LastPacket bandera mezuaren azken paketean ezartzen da. Erantzun paketean, azken paketearen zenbakia + 1 adierazten da, eta horrek hartzailearentzat mezua arrakastaz bidaltzea esan nahi du.
Konexioaren ezarpen eta amaiera eskema:.Net-erako Reliable Udp protokoloaren ezarpena

Konexioa ezartzen denean, datuen transferentzia hasten da. Datuak pakete-blokeetan transmititzen dira. Bloke bakoitzak, azkenak izan ezik, pakete kopuru finko bat dauka. Jaso/bidaltzeko leihoaren tamainaren berdina da. Azken datu-blokeak pakete gutxiago izan ditzake. Bloke bakoitza bidali ondoren, bidaltzaileak bidalketa berrespena edo galdutako paketeak berriro bidaltzeko eskaeraren zain geratzen dira, erantzunak jasotzeko jaso/bidaltzeko leihoa irekita utziz. Blokeen bidalketaren berrespena jaso ondoren, jaso/bidaltzeko leihoa aldatzen da eta hurrengo datu-blokea bidaltzen da.

Alde hartzaileak paketeak jasotzen ditu. Pakete bakoitza egiaztatzen da transmisio-leihoan sartzen den ikusteko. Leihoan erortzen ez diren paketeak eta bikoiztuak iragazten dira. Zeren Leihoaren tamaina finkoa bada eta hartzailearen eta igorlearen berdina bada, pakete-bloke bat galdu gabe entregatzen bada, leihoa mugituko da hurrengo datu-blokearen paketeak jasotzeko eta bidalketa berrespena emango zaio. bidali. Laneko tenporizadoreak ezarritako epean leihoa betetzen ez bada, paketeak entregatu ez diren egiaztatzen hasiko da eta berriro entregatzeko eskaerak bidaliko dira.
Retransmisio diagrama:.Net-erako Reliable Udp protokoloaren ezarpena

Denbora-muga eta protokolo-tenporizadoreak

Hainbat arrazoi daude konexio bat ezartzeko. Adibidez, hartzailea lineaz kanpo badago. Kasu honetan, konexio bat ezartzen saiatzean, konexioa itxiko da denbora-mugarako. UDP fidagarriaren ezarpenak bi tenporizadore erabiltzen ditu denbora-muga ezartzeko. Lehenengoa, laneko tenporizadorea, urruneko ostalariaren erantzunaren zain egoteko erabiltzen da. Igorlearen aldetik tiro egiten badu, bidalitako azken paketea berriro bidaltzen da. Hartzailearengan tenporizadorea iraungitzen bada, galdutako paketeen egiaztapena egiten da eta berriro bidaltzeko eskaerak bidaltzen dira.

Bigarren tenporizadorea behar da konexioa ixteko, nodoen arteko komunikazio faltaren kasuan. Igorlearen aldetik, laneko tenporizadorea iraungi eta berehala hasten da, eta urruneko nodoaren erantzunaren zain dago. Zehaztutako epean erantzunik ez badago, konexioa amaituko da eta baliabideak askatzen dira. Hartzaileari dagokionez, konexioa ixteko tenporizadorea abiarazten da laneko tenporizadorea bi aldiz iraungi ondoren. Hau beharrezkoa da berrespen-paketearen galeraren aurka ziurtatzeko. Tenporizadorea amaitzen denean, konexioa ere amaitzen da eta baliabideak askatzen dira.

UDP transmisio-egoeren diagrama fidagarria

Protokoloaren printzipioak egoera finituko makina batean ezartzen dira, eta egoera bakoitza paketeen prozesatzeko logika jakin baten arduraduna da.
UDP egoera-diagrama fidagarria:

.Net-erako Reliable Udp protokoloaren ezarpena

Itxita - ez da benetan egoera bat, automataren hasiera eta amaiera puntua da. Estatuarentzat Itxita transmisio-kontrol-bloke bat jasotzen da, zeinak, UDP zerbitzari asinkrono bat ezarriz, paketeak dagozkien konexioetara birbidaltzen ditu eta egoera prozesatzen hasten da.

Lehenengo paketea bidaltzea – mezua bidaltzean irteerako konexioa dagoen hasierako egoera.

Egoera horretan, mezu arrunten lehen paketea bidaltzen da. Bidalketa berrespenik gabeko mezuetarako, hau da mezu osoa bidaltzen den egoera bakarra.

SendingCycle – mezu-paketeen transmisiorako oinarrizko egoera.

Estatutik harako trantsizioa Lehenengo paketea bidaltzea mezuaren lehen paketea bidali ondoren egiten da. Egoera horretan datoz birtransmisioen aitorpen eta eskaera guztiak. Bertatik irtetea bi kasutan posible da: mezua arrakastaz bidaltzen bada edo denbora-muga eginda.

Jasotako lehen paketea – mezuaren hartzailearen hasierako egoera.

Transmisioaren hasiera zuzena dela egiaztatzen du, beharrezko egiturak sortzen ditu eta lehen paketea jaso izanaren akta bidaltzen du.

Pakete bakar batez osatuta dagoen eta entrega-froga erabili gabe bidalitako mezu baterako, hau da egoera bakarra. Horrelako mezu bat prozesatu ondoren, konexioa itxi egiten da.

muntaia – Mezu-paketeak jasotzeko oinarrizko egoera.

Paketeak aldi baterako biltegian idazten ditu, paketeen galera egiaztatzen du, pakete bloke bat eta mezu osoa bidaltzeko aitorpenak bidaltzen ditu eta galdutako paketeak berriro bidaltzeko eskaerak bidaltzen ditu. Mezu osoa ongi jasotzen bada, konexioa egoerara sartzen da Amaitutako, bestela, denbora-muga bat irteten da.

Amaitutako – konexioa ixtea mezu osoa ongi jasotzen bada.

Egoera hori beharrezkoa da mezua muntatzeko eta igorlearen bidean mezuaren bidalketa berrespena galdu den kasurako. Egoera honetatik irtengo da denbora-muga baten ondorioz, baina konexioa behar bezala itxi dela uste da.

Kodean sakondu. transmisioa kontrolatzeko unitatea

UDP fidagarriaren funtsezko elementuetako bat transmisioaren kontrol blokea da. Bloke honen zeregina uneko konexioak eta elementu laguntzaileak gordetzea da, sarrerako paketeak dagozkien konexioetara banatzea, paketeak konexio batera bidaltzeko interfaze bat ematea eta protokoloaren APIa ezartzea. Transmisio-kontrol-blokeak UDP geruzatik paketeak jasotzen ditu eta egoera-makinara bidaltzen ditu prozesatzeko. Paketeak jasotzeko, UDP zerbitzari asinkrono bat ezartzen du.
ReliableUdpConnectionControlBlock klaseko kide batzuk:

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;    	
  //...
}

UDP zerbitzari asinkronoaren ezarpena:

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);
}

Mezu transferentzia bakoitzeko, konexioari buruzko informazioa jasotzen duen egitura bat sortzen da. Horrelako egiturari deitzen zaio konexio-erregistroa.
ReliableUdpConnectionRecord klaseko kide batzuk:

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;
  //...
}

Kodean sakondu. estatuak

Estatuek Reliable UDP protokoloaren egoera-makina ezartzen dute, non paketeen prozesamendu nagusia egiten den. ReliableUdpState klase abstraktuak egoerarako interfaze bat eskaintzen du:

.Net-erako Reliable Udp protokoloaren ezarpena

Protokoloaren logika osoa goian aurkeztutako klaseek inplementatzen dute, metodo estatikoak eskaintzen dituen klase laguntzaile batekin batera, adibidez, ReliableUdp goiburua konexio erregistrotik eraikitzea.

Jarraian, xehetasunez aztertuko dugu protokoloaren oinarrizko algoritmoak zehazten dituzten interfaze-metodoen ezarpena.

DisposeByTimeout metodoa

DisposeByTimeout metodoa denbora-muga igaro ondoren konexio-baliabideak askatzeaz eta mezuak bidaltzea arrakastaz/ez arrakastaz seinaleztatzeaz arduratzen da.
ReliableUdpState.DisposeByTimeout:

protected virtual void DisposeByTimeout(object record)
{
  ReliableUdpConnectionRecord connectionRecord = (ReliableUdpConnectionRecord) record;      
  if (record.AsyncResult != null)
  {
    connectionRecord.AsyncResult.SetAsCompleted(false);
  }
  connectionRecord.Dispose();
}

Estatuan bakarrik gainditzen da Amaitutako.
Amaitu.DisposeByTimeout:

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

ProcessPackets metodoa

ProcessPackets metodoa pakete baten edo paketeen prozesamendu gehigarriaz arduratzen da. Zuzenean edo paketeen itxaron-tenporizadore baten bidez deituta.

Gai muntaia metodoa gainidazten da eta galdutako paketeak egiaztatzeaz eta egoerara igarotzeaz arduratzen da Amaitutako, azken paketea jaso eta egiaztapen arrakastatsua gaindituz gero
Muntatzea.ProzesatuPaketeak:

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);
  }
}

Gai SendingCycle metodo hau tenporizadore batean bakarrik deitzen da, eta azken mezua berriro bidaltzeaz arduratzen da, baita konexioa ixteko tenporizadorea gaitzeaz ere.
SendingCycle.ProcessPackets:

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

Gai Amaitutako metodoak martxan jartzen duen tenporizadorea gelditzen du eta mezua bidaltzen die harpidedunei.
Osatua.ProzesatuPaketeak:

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

ReceivePacket metodoa

Gai Jasotako lehen paketea metodoaren zeregin nagusia lehenengo mezu-paketea benetan interfazera iritsi den zehaztea da, eta baita pakete bakar batez osatutako mezu bat biltzea ere.
LehenPaketeaJaso.Paketea:

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);
  }
}

Gai SendingCycle metodo hau gainidazten da entrega-aitorpenak eta birtransmisio-eskaerak onartzeko.
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));
}

Gai muntaia ReceivePacket metodoan, sarrerako paketeetatik mezu bat muntatzeko lan nagusia egiten da.
Muntatzea.JasoPaketea:

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);
  }
}

Gai Amaitutako metodoaren zeregin bakarra mezua arrakastaz entregatu izanaren aitorpena berriro bidaltzea da.
Osatua. Jaso paketea:

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

Bidali pakete metodoa

Gai Lehenengo paketea bidaltzea metodo honek lehen datu paketea bidaltzen du, edo mezuak bidalketa berrespena behar ez badu, mezu osoa.
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);
}

Gai SendingCycle metodo honetan, pakete bloke bat bidaltzen da.
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 );
  }
}

Kodean sakondu. Konexioak sortzea eta ezartzea

Oinarrizko egoerak eta egoerak kudeatzeko erabiltzen diren metodoak ikusi ditugunez, apur ditzagun protokoloak nola funtzionatzen duen jakiteko adibide batzuk zehatzago.
Datuen transmisio-diagrama baldintza normaletan:.Net-erako Reliable Udp protokoloaren ezarpena

Kontuan izan sorrera zehatz-mehatz konexio-erregistroa lehen paketea konektatzeko eta bidaltzeko. Transferentzia beti bidaltzeko mezuen APIa deitzen duen aplikazioak hasten du. Ondoren, transmisioa kontrolatzeko blokearen StartTransmission metodoa deitzen da, mezu berriaren datuen transmisioa hasten duena.
Irteerako konexioa sortzea:

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]);
}

Lehenengo paketea bidaltzen (FirstPacketSending egoera):

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);
}

Lehenengo paketea bidali ondoren, igorlea egoerara sartzen da SendingCycle - itxaron paketearen entregaren berrespena arte.
Alde hartzaileak, EndReceive metodoa erabiliz, bidalitako paketea jasotzen du, berri bat sortzen du konexio-erregistroa eta pakete hau, aurrez analizatutako goiburu batekin, egoerako ReceivePacket metodora pasatzen du prozesatzeko Jasotako lehen paketea
Hartzailearen aldean konexio bat sortzea:

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);
}

Lehenengo paketea jasotzea eta aitorpena bidaltzea (FirstPacketReceived egoera):

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);
  }
}

Kodean sakondu. Konexioa ixtea denbora-mugan

Denbora-muga kudeatzea UDP fidagarriaren zati garrantzitsu bat da. Demagun adibide bat non tarteko nodo batek huts egin zuen eta bi noranzkoetan datuak ematea ezinezkoa bihurtu zen.
Konexio bat denbora-mugaren arabera ixteko diagrama:.Net-erako Reliable Udp protokoloaren ezarpena

Diagraman ikus daitekeenez, igorlearen lan-tenporizadorea pakete bloke bat bidali eta berehala hasten da. Hau estatuko SendPacket metodoan gertatzen da SendingCycle.
Laneko tenporizadorea gaitzea (SendingCycle egoera):

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

Tenporizadore-epeak konexioa sortzen denean ezartzen dira. ShortTimerPeriod lehenetsia 5 segundokoa da. Adibidean, 1,5 segundotan ezartzen da.

Sarrerako konexio baterako, tenporizadorea sartzen den azken datu-paketea jaso ondoren hasten da; hau egoerako ReceivePacket metodoan gertatzen da. muntaia
Laneko tenporizadorea gaitu (muntatze egoera):

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);
  // ...
}

Ez da pakete gehiago iritsi sarrerako konexiora laneko tenporizadorearen zain dagoen bitartean. Tenporizadorea itzali zen eta ProcessPackets metodoa deitu zuen, non galdutako paketeak aurkitu ziren eta berriro entregatzeko eskaerak lehen aldiz bidali ziren.
Berriro entrega-eskaerak bidaltzea (muntatze egoera):

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);
  }
}

TimerSecondTry aldagaia ezarrita dago Egia. Aldagai hau laneko tenporizadorea berrabiarazteaz arduratzen da.

Bidaltzailearen aldetik, laneko tenporizadorea ere abiarazten da eta bidalitako azken paketea berriro bidaltzen da.
Konexioa ixteko tenporizadorea gaitu (SendingCycle egoera):

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

Horren ostean, konexioa ixteko tenporizadorea irteerako konexioan hasten da.
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);
}

Konexioa ixteko tenporizadorearen denbora-muga 30 segundokoa da lehenespenez.

Denbora gutxiren buruan, hartzailearen aldean laneko tenporizadorea berriro piztuko da, eskaerak berriro bidaltzen dira, eta ondoren konexioa ixteko tenporizadorea hasten da sarrerako konexiorako.

Itxi-tenporizadoreak pizten direnean, bi konexio-erregistroetako baliabide guztiak askatzen dira. Bidaltzaileak bidalketa hutsaren berri ematen dio upstream aplikazioari (ikusi UDP API fidagarria).
Konexio-erregistroen baliabideak askatzea:

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);
  }
}

Kodean sakondu. Datuen transferentzia leheneratzea

Datuen transmisioa berreskuratzeko diagrama paketeak galtzen badira:.Net-erako Reliable Udp protokoloaren ezarpena

Konexioa denbora-mugan ixtean aipatu bezala, laneko tenporizadorea amaitzen denean, hartzaileak galdutako paketeak egiaztatuko ditu. Paketeak galtzen badira, hartzailearengana iritsi ez diren paketeen zerrenda osatuko da. Zenbaki hauek konexio zehatz bateko LostPackets array-n sartzen dira eta berriro bidaltzeko eskaerak bidaltzen dira.
Paketeak berriro bidaltzeko eskaerak bidaltzea (muntatze egoera):

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);
      }
    }
    // ...
  }
}

Bidaltzaileak berriro bidaltzeko eskaera onartuko du eta falta diren paketeak bidaliko ditu. Aipatzekoa da momentu honetan igorleak konexioa ixteko tenporizadorea abiarazi duela eta, eskaera jasotzen denean, berrezarri egiten dela.
Galdutako paketeak berriro bidaltzea (SendingCycle egoera):

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));
}

Berriro bidalitako paketea (diagramako 3. paketea) sarrerako konexioak jasotzen du. Jasotzeko leihoa beteta dagoen eta datuen transmisio normala berreskuratzen den egiaztatzen da.
Jasotzeko leihoan arrakastak egiaztatzea (muntatze egoera):

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);
  }
  // ...
}

UDP API fidagarria

Datuak transferitzeko protokoloarekin elkarreragiteko, Reliable Udp klase irekia dago, hau da, transferentzia kontrol blokearen gaineko bilgarri bat. Hona hemen klaseko kide garrantzitsuenak:

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()    
}

Mezuak harpidetza bidez jasotzen dira. Ordezkatu sinadura deia itzultzeko metodorako:

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

Mezua:

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

Mezu mota eta/edo igorle zehatz batera harpidetzeko, aukerako bi parametro erabiltzen dira: ReliableUdpMessageTypes messageType eta IPEndPoint ipEndPoint.

Mezu motak:

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

Mezua modu asinkronoan bidaltzen da; horretarako, protokoloak programazio eredu asinkrono bat ezartzen du:

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

Mezu bat bidaltzearen emaitza egiazkoa izango da (mezua hartzailera ongi iritsi bada eta faltsua) konexioa denbora-mugan itxi bada:

public bool EndSendMessage(IAsyncResult asyncResult)

Ondorioa

Artikulu honetan ez da asko deskribatu. Thread matching mekanismoak, salbuespenak eta akatsak kudeatzea, mezuak bidaltzeko metodo asinkronoak ezartzea. Baina protokoloaren muina, paketeak prozesatzeko logikaren deskribapena, konexioa ezartzea eta denbora-muga kudeatzea, argi izan beharko zenuke.

Bidalketa-protokolo fidagarriaren frogatutako bertsioa nahikoa sendoa eta malgua da aurrez zehaztutako eskakizunak betetzeko. Baina deskribatutako ezarpena hobetu daitekeela gehitu nahi dut. Esaterako, errendimendua handitzeko eta tenporizadore-epeak dinamikoki aldatzeko, leiho irristakorra eta RTT bezalako mekanismoak gehi daitezke protokoloari, konexio-nodoen artean MTU zehazteko mekanismo bat ezartzea ere erabilgarria izango da (baina mezu handiak bidaltzen badira bakarrik) .

Eskerrik asko zure arretagatik, zure iruzkinak eta iruzkinak espero ditut.

PS Xehetasunetan interesa dutenentzat edo protokoloa probatu nahi dutenentzat, GitHube-n proiekturako esteka:
UDP proiektu fidagarria

Esteka eta artikulu erabilgarriak

  1. TCP protokoloaren zehaztapena: ingelesez и errusieraz
  2. UDP protokoloaren zehaztapena: ingelesez и errusieraz
  3. RUDP protokoloaren eztabaida: zirriborroa-ietf-sigtran-reliable-udp-00
  4. Datu fidagarrien protokoloa: rfc 908 и rfc 1151
  5. UDPren bidalketaren berrespenaren ezarpen sinplea: Hartu zure sarearen erabateko kontrola .NET eta UDPrekin
  6. NAT zeharkatzeko mekanismoak deskribatzen dituen artikulua: Peer-to-Peer komunikazioa sareko helbide-itzultzaileen artean
  7. Programazio asinkronoaren ereduaren ezarpena: CLR Programazio Asinkronoaren Eredua ezartzea и Nola inplementatu IAsyncResult diseinu-eredua
  8. Programazio asinkronoaren eredua zereginetan oinarritutako eredu asinkronora (APM TAPen) eramatea:
    TPL eta .NET Programazio Asinkrono Tradizionala
    Beste eredu eta mota asinkrono batzuekin elkarreragintzea

Eguneraketa: Eskerrik asko mayorovp и sidristij interfazeari ataza bat gehitzeko ideiagatik. Liburutegiak sistema eragile zaharrekin duen bateragarritasuna ez da urratzen, zeren 4. esparruak XP eta 2003 zerbitzariak onartzen ditu.

Iturria: www.habr.com

Gehitu iruzkin berria