Pagpapatupad ng Maaasahang Udp protocol para sa .Net

Matagal nang nagbago ang Internet. Isa sa mga pangunahing protocol ng Internet - Ang UDP ay ginagamit ng mga application hindi lamang upang maghatid ng mga datagram at broadcast, ngunit din upang magbigay ng "peer-to-peer" na mga koneksyon sa pagitan ng mga node ng network. Dahil sa simpleng disenyo nito, ang protocol na ito ay may maraming dati nang hindi planadong paggamit, gayunpaman, ang mga pagkukulang ng protocol, tulad ng kakulangan ng garantisadong paghahatid, ay hindi nawala kahit saan. Inilalarawan ng artikulong ito ang pagpapatupad ng garantisadong protocol ng paghahatid sa UDP.
Nilalaman:Pagpasok
Mga Kinakailangan sa Protocol
Maaasahang UDP header
Pangkalahatang mga prinsipyo ng protocol
Mga timeout at protocol timer
Maaasahang diagram ng estado ng paghahatid ng UDP
Mas malalim sa code. yunit ng kontrol sa paghahatid
Mas malalim sa code. estado

Mas malalim sa code. Paglikha at Pagtatatag ng mga Koneksyon
Mas malalim sa code. Isinasara ang koneksyon sa timeout
Mas malalim sa code. Pagpapanumbalik ng paglilipat ng data
Maaasahang UDP API
Konklusyon
Mga kapaki-pakinabang na link at artikulo

Pagpasok

Ang orihinal na arkitektura ng Internet ay ipinapalagay ang isang homogenous na puwang ng address kung saan ang bawat node ay may pandaigdigan at natatanging IP address at maaaring direktang makipag-usap sa iba pang mga node. Ngayon ang Internet, sa katunayan, ay may ibang arkitektura - isang lugar ng mga pandaigdigang IP address at maraming lugar na may mga pribadong address na nakatago sa likod ng mga NAT device.Sa arkitektura na ito, ang mga device lamang sa pandaigdigang espasyo ng address ang madaling makipag-ugnayan sa sinuman sa network dahil mayroon silang natatangi, pandaigdigang rutang IP address. Ang isang node sa isang pribadong network ay maaaring kumonekta sa iba pang mga node sa parehong network, at maaari ring kumonekta sa iba pang mga kilalang node sa global address space. Ang pakikipag-ugnayang ito ay higit na nakakamit dahil sa mekanismo ng pagsasalin ng address ng network. Ang mga NAT device, tulad ng mga Wi-Fi router, ay gumagawa ng mga espesyal na entry sa talahanayan ng pagsasalin para sa mga papalabas na koneksyon at binabago ang mga IP address at numero ng port sa mga packet. Nagbibigay-daan ito sa mga papalabas na koneksyon mula sa pribadong network patungo sa mga host sa pandaigdigang espasyo ng address. Ngunit sa parehong oras, karaniwang hinaharangan ng mga NAT device ang lahat ng papasok na trapiko maliban kung nakatakda ang mga hiwalay na panuntunan para sa mga papasok na koneksyon.

Ang arkitektura ng Internet na ito ay sapat na tama para sa komunikasyon ng client-server, kung saan ang mga kliyente ay maaaring nasa mga pribadong network, at ang mga server ay may pandaigdigang address. Ngunit lumilikha ito ng mga paghihirap para sa direktang koneksyon ng dalawang node sa pagitan iba-iba mga pribadong network. Ang direktang koneksyon sa pagitan ng dalawang node ay mahalaga para sa mga peer-to-peer na application tulad ng voice transmission (Skype), pagkakaroon ng malayuang pag-access sa isang computer (TeamViewer), o online gaming.

Ang isa sa mga pinaka-epektibong paraan para sa pagtatatag ng isang peer-to-peer na koneksyon sa pagitan ng mga device sa iba't ibang pribadong network ay tinatawag na hole punching. Ang diskarteng ito ay pinakakaraniwang ginagamit sa mga application batay sa UDP protocol.

Ngunit kung ang iyong aplikasyon ay nangangailangan ng garantisadong paghahatid ng data, halimbawa, naglilipat ka ng mga file sa pagitan ng mga computer, ang paggamit ng UDP ay magkakaroon ng maraming kahirapan dahil sa katotohanan na ang UDP ay hindi isang garantisadong protocol ng paghahatid at hindi nagbibigay ng paghahatid ng packet sa pagkakasunud-sunod, hindi katulad ng TCP protocol.

Sa kasong ito, para matiyak ang garantisadong paghahatid ng packet, kinakailangan na magpatupad ng application layer protocol na nagbibigay ng kinakailangang functionality at gumagana sa UDP.

Gusto kong tandaan kaagad na mayroong TCP hole punching technique para sa pagtatatag ng mga koneksyon ng TCP sa pagitan ng mga node sa iba't ibang pribadong network, ngunit dahil sa kakulangan ng suporta para dito ng maraming NAT device, kadalasan ay hindi ito itinuturing na pangunahing paraan para kumonekta. gayong mga node.

Para sa natitirang bahagi ng artikulong ito, tututuon lamang ako sa pagpapatupad ng garantisadong protocol ng paghahatid. Ang pagpapatupad ng UDP hole punching technique ay ilalarawan sa mga sumusunod na artikulo.

Mga Kinakailangan sa Protocol

  1. Ang maaasahang paghahatid ng packet ay ipinatupad sa pamamagitan ng isang positibong mekanismo ng feedback (ang tinatawag na positibong pagkilala )
  2. Ang pangangailangan para sa mahusay na paglipat ng malaking data, i.e. dapat iwasan ng protocol ang hindi kinakailangang packet relaying
  3. Posibleng kanselahin ang mekanismo ng pagkumpirma ng paghahatid (ang kakayahang gumana bilang isang "purong" UDP protocol)
  4. Kakayahang ipatupad ang command mode, na may kumpirmasyon ng bawat mensahe
  5. Ang pangunahing yunit ng paglipat ng data sa protocol ay dapat na isang mensahe

Ang mga kinakailangang ito ay higit na tumutugma sa mga kinakailangan sa Maaasahang Data Protocol na inilarawan sa rfc908 и rfc1151, at umasa ako sa mga pamantayang iyon sa pagbuo ng protocol na ito.

Upang maunawaan ang mga kinakailangang ito, tingnan natin ang timing ng paglipat ng data sa pagitan ng dalawang network node gamit ang TCP at UDP na mga protocol. Hayaan sa parehong mga kaso magkakaroon tayo ng isang packet na mawawala.
Paglipat ng hindi interactive na data sa TCP:Pagpapatupad ng Maaasahang Udp protocol para sa .Net

Tulad ng nakikita mo mula sa diagram, sa kaso ng pagkawala ng packet, makikita ng TCP ang nawalang packet at iuulat ito sa nagpadala sa pamamagitan ng pagtatanong ng numero ng nawalang segment.
Paglipat ng data sa pamamagitan ng UDP protocol:Pagpapatupad ng Maaasahang Udp protocol para sa .Net

Ang UDP ay hindi nagsasagawa ng anumang mga hakbang sa pagtuklas ng pagkawala. Ang kontrol sa mga error sa paghahatid sa UDP protocol ay ganap na responsibilidad ng application.

Ang pagtuklas ng error sa TCP protocol ay nakakamit sa pamamagitan ng pagtatatag ng isang koneksyon sa isang end node, pag-iimbak ng estado ng koneksyon na iyon, na nagpapahiwatig ng bilang ng mga byte na ipinadala sa bawat packet header, at pag-abiso sa mga resibo gamit ang isang numero ng pagkilala.

Bukod pa rito, para mapahusay ang performance (ibig sabihin, magpadala ng higit sa isang segment nang hindi nakakatanggap ng pagkilala), ginagamit ng TCP protocol ang tinatawag na transmission window - ang bilang ng mga byte ng data na inaasahang matatanggap ng nagpadala ng segment.

Para sa higit pang impormasyon tungkol sa TCP protocol, tingnan ang rfc793, mula sa UDP hanggang rfc768kung saan, sa katunayan, ang mga ito ay tinukoy.

Mula sa itaas, malinaw na upang lumikha ng isang maaasahang protocol ng paghahatid ng mensahe sa UDP (mula dito ay tinutukoy bilang Maaasahang UDP), kinakailangan na ipatupad ang mga mekanismo ng paglilipat ng data na katulad ng TCP. Namely:

  • i-save ang estado ng koneksyon
  • gumamit ng segment numbering
  • gumamit ng mga espesyal na pakete ng kumpirmasyon
  • gumamit ng pinasimple na mekanismo ng windowing upang madagdagan ang throughput ng protocol

Bilang karagdagan, kailangan mo:

  • hudyat ng pagsisimula ng isang mensahe, upang maglaan ng mga mapagkukunan para sa koneksyon
  • hudyat ng pagtatapos ng isang mensahe, upang ipasa ang natanggap na mensahe sa upstream na application at ilabas ang mga mapagkukunan ng protocol
  • payagan ang protocol na tukoy sa koneksyon na huwag paganahin ang mekanismo ng pagkumpirma ng paghahatid upang gumana bilang "purong" UDP

Maaasahang UDP header

Alalahanin na ang isang UDP datagram ay naka-encapsulate sa isang IP datagram. Ang Maaasahang UDP packet ay angkop na "nakabalot" sa isang UDP datagram.
Maaasahang UDP header encapsulation:Pagpapatupad ng Maaasahang Udp protocol para sa .Net

Ang istraktura ng Maaasahang UDP header ay medyo simple:

Pagpapatupad ng Maaasahang Udp protocol para sa .Net

  • Mga Flag - mga flag ng kontrol ng package
  • MessageType - uri ng mensahe na ginagamit ng mga upstream na application upang mag-subscribe sa mga partikular na mensahe
  • TransmissionId - ang numero ng transmission, kasama ang address at port ng tatanggap, ay natatanging kinikilala ang koneksyon
  • PacketNumber - numero ng packet
  • Mga Opsyon - karagdagang mga opsyon sa protocol. Sa kaso ng unang packet, ito ay ginagamit upang ipahiwatig ang laki ng mensahe

Ang mga flag ay ang mga sumusunod:

  • FirstPacket - ang unang packet ng mensahe
  • NoAsk - ang mensahe ay hindi nangangailangan ng isang mekanismo ng pagkilala upang paganahin
  • LastPacket - ang huling packet ng mensahe
  • RequestForPacket - packet ng kumpirmasyon o kahilingan para sa isang nawalang packet

Pangkalahatang mga prinsipyo ng protocol

Dahil ang Maaasahang UDP ay nakatuon sa garantisadong pagpapadala ng mensahe sa pagitan ng dalawang node, dapat itong makapagtatag ng koneksyon sa kabilang panig. Upang magtatag ng isang koneksyon, ang nagpadala ay nagpapadala ng isang packet na may flag ng FirstPacket, ang tugon kung saan ay nangangahulugan na ang koneksyon ay naitatag. Ang lahat ng mga response packet, o, sa madaling salita, mga acknowledgement packet, ay palaging nagtatakda ng value ng PacketNumber field sa isa higit pa sa pinakamalaking halaga ng PacketNumber ng matagumpay na natanggap na mga packet. Ang field na Mga Opsyon para sa unang packet na ipinadala ay ang laki ng mensahe.

Ang isang katulad na mekanismo ay ginagamit upang wakasan ang isang koneksyon. Ang LastPacket flag ay nakatakda sa huling packet ng mensahe. Sa response packet, ang numero ng huling packet + 1 ay ipinahiwatig, na para sa receiving side ay nangangahulugang matagumpay na paghahatid ng mensahe.
Diagram ng pagtatatag ng koneksyon at pagwawakas:Pagpapatupad ng Maaasahang Udp protocol para sa .Net

Kapag naitatag ang koneksyon, magsisimula ang paglilipat ng data. Ang data ay ipinadala sa mga bloke ng mga packet. Ang bawat bloke, maliban sa huli, ay naglalaman ng isang nakapirming bilang ng mga packet. Ito ay katumbas ng laki ng receive/transmit window. Ang huling bloke ng data ay maaaring may mas kaunting packet. Pagkatapos ipadala ang bawat bloke, ang panig ng pagpapadala ay naghihintay para sa isang kumpirmasyon sa paghahatid o isang kahilingan upang muling maihatid ang mga nawawalang packet, na iniiwan ang receive/transmit window na bukas para makatanggap ng mga tugon. Pagkatapos matanggap ang kumpirmasyon ng block delivery, ang receive/transmit window shifts at ang susunod na block ng data ay ipapadala.

Ang receiving side ay tumatanggap ng mga packet. Ang bawat packet ay sinusuri upang makita kung ito ay nasa loob ng window ng paghahatid. Ang mga packet at duplicate na hindi nahuhulog sa window ay sinasala. kasi Kung ang laki ng window ay naayos at pareho para sa tatanggap at nagpadala, kung gayon sa kaso ng isang bloke ng mga packet na naihatid nang walang pagkawala, ang window ay inilipat upang makatanggap ng mga packet ng susunod na bloke ng data at isang kumpirmasyon sa paghahatid ay ipinadala. Kung ang window ay hindi napuno sa loob ng panahong itinakda ng timer ng trabaho, magsisimula ang isang tseke kung saan ang mga packet ay hindi naihatid at ang mga kahilingan para sa muling paghahatid ay ipapadala.
Diagram ng Muling Pagpapadala:Pagpapatupad ng Maaasahang Udp protocol para sa .Net

Mga timeout at protocol timer

Mayroong ilang mga dahilan kung bakit hindi maitatag ang isang koneksyon. Halimbawa, kung offline ang tatanggap na partido. Sa kasong ito, kapag sinusubukang magtatag ng isang koneksyon, ang koneksyon ay isasara sa pamamagitan ng timeout. Gumagamit ang Maaasahang pagpapatupad ng UDP ng dalawang timer para magtakda ng mga timeout. Ang una, ang gumaganang timer, ay ginagamit upang maghintay ng tugon mula sa remote host. Kung ito ay nagpaputok sa gilid ng nagpadala, ang huling ipinadalang packet ay ipapadala. Kung ang timer ay nag-expire sa tatanggap, pagkatapos ay isang pagsusuri para sa mga nawawalang packet ay isasagawa at ang mga kahilingan para sa muling paghahatid ay ipapadala.

Ang pangalawang timer ay kinakailangan upang isara ang koneksyon sa kaso ng isang kakulangan ng komunikasyon sa pagitan ng mga node. Para sa panig ng nagpadala, magsisimula ito kaagad pagkatapos mag-expire ang gumaganang timer, at naghihintay ng tugon mula sa remote na node. Kung walang tugon sa loob ng tinukoy na panahon, wawakasan ang koneksyon at ilalabas ang mga mapagkukunan. Para sa receiving side, magsisimula ang timer ng pagsasara ng koneksyon pagkatapos mag-expire ng dalawang beses ang timer ng trabaho. Ito ay kinakailangan upang masiguro laban sa pagkawala ng confirmation packet. Kapag ang timer ay nag-expire, ang koneksyon ay tinapos din at ang mga mapagkukunan ay inilabas.

Maaasahang diagram ng estado ng paghahatid ng UDP

Ang mga prinsipyo ng protocol ay ipinatupad sa isang may hangganan na makina ng estado, ang bawat estado kung saan ay responsable para sa isang tiyak na lohika ng pagproseso ng packet.
Maaasahang UDP State Diagram:

Pagpapatupad ng Maaasahang Udp protocol para sa .Net

Sarado - ay hindi talaga isang estado, ito ay isang simula at wakas na punto para sa automat. Para sa estado Sarado isang transmission control block ang natanggap, na, sa pagpapatupad ng isang asynchronous na UDP server, nagpapasa ng mga packet sa naaangkop na mga koneksyon at sinimulan ang pagproseso ng estado.

FirstPacketSending – ang paunang estado kung saan ang papalabas na koneksyon ay kapag ipinadala ang mensahe.

Sa ganitong estado, ang unang packet para sa mga normal na mensahe ay ipinadala. Para sa mga mensaheng walang kumpirmasyon sa pagpapadala, ito lamang ang estado kung saan ipinapadala ang buong mensahe.

SendingCycle – ground state para sa pagpapadala ng mga packet ng mensahe.

Ang paglipat dito mula sa estado FirstPacketSending isinasagawa pagkatapos maipadala ang unang pakete ng mensahe. Nasa ganitong estado na darating ang lahat ng pagkilala at kahilingan para sa muling pagpapadala. Ang pag-alis dito ay posible sa dalawang kaso - sa kaso ng matagumpay na paghahatid ng mensahe o sa pamamagitan ng timeout.

FirstPacketReceived – ang paunang estado para sa tatanggap ng mensahe.

Sinusuri nito ang kawastuhan ng simula ng paghahatid, lumilikha ng mga kinakailangang istruktura, at nagpapadala ng isang pagkilala sa pagtanggap ng unang packet.

Para sa isang mensahe na binubuo ng isang pakete at ipinadala nang hindi gumagamit ng patunay ng paghahatid, ito lamang ang estado. Pagkatapos ng pagproseso ng naturang mensahe, ang koneksyon ay sarado.

Assembling – pangunahing estado para sa pagtanggap ng mga packet ng mensahe.

Nagsusulat ito ng mga packet sa pansamantalang imbakan, nagsusuri para sa pagkawala ng packet, nagpapadala ng mga pagkilala para sa paghahatid ng isang bloke ng mga packet at ng buong mensahe, at nagpapadala ng mga kahilingan para sa muling paghahatid ng mga nawawalang packet. Sa kaso ng matagumpay na pagtanggap ng buong mensahe, ang koneksyon ay mapupunta sa estado Natapos, kung hindi, lalabas ang isang timeout.

Natapos – pagsasara ng koneksyon sa kaso ng matagumpay na pagtanggap ng buong mensahe.

Ang estado na ito ay kinakailangan para sa pagpupulong ng mensahe at para sa kaso kung kailan nawala ang kumpirmasyon ng paghahatid ng mensahe sa daan patungo sa nagpadala. Ang estado na ito ay lumabas sa pamamagitan ng isang timeout, ngunit ang koneksyon ay itinuturing na matagumpay na sarado.

Mas malalim sa code. yunit ng kontrol sa paghahatid

Isa sa mga pangunahing elemento ng Maaasahang UDP ay ang transmission control block. Ang gawain ng block na ito ay mag-imbak ng mga kasalukuyang koneksyon at mga elemento ng auxiliary, ipamahagi ang mga papasok na packet sa mga kaukulang koneksyon, magbigay ng interface para sa pagpapadala ng mga packet sa isang koneksyon, at ipatupad ang protocol API. Ang transmission control block ay tumatanggap ng mga packet mula sa UDP layer at ipinapasa ang mga ito sa state machine para sa pagproseso. Upang makatanggap ng mga packet, nagpapatupad ito ng isang asynchronous na UDP server.
Ilang miyembro ng ReliableUdpConnectionControlBlock class:

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

Pagpapatupad ng asynchronous na UDP server:

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

Para sa bawat paglilipat ng mensahe, isang istraktura ang nilikha na naglalaman ng impormasyon tungkol sa koneksyon. Ang ganitong istraktura ay tinatawag tala ng koneksyon.
Ilang miyembro ng ReliableUdpConnectionRecord na klase:

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

Mas malalim sa code. estado

Ipinapatupad ng mga estado ang makina ng estado ng Maaasahang UDP protocol, kung saan nagaganap ang pangunahing pagproseso ng mga packet. Ang abstract class na ReliableUdpState ay nagbibigay ng isang interface para sa estado:

Pagpapatupad ng Maaasahang Udp protocol para sa .Net

Ang buong lohika ng protocol ay ipinatupad ng mga klase na ipinakita sa itaas, kasama ng isang auxiliary na klase na nagbibigay ng mga static na pamamaraan, tulad ng, halimbawa, pagbuo ng ReliableUdp header mula sa record ng koneksyon.

Susunod, isasaalang-alang namin nang detalyado ang pagpapatupad ng mga pamamaraan ng interface na tumutukoy sa mga pangunahing algorithm ng protocol.

DisposeByTimeout na pamamaraan

Ang DisposeByTimeout na pamamaraan ay responsable para sa pagpapalabas ng mga mapagkukunan ng koneksyon pagkatapos ng isang timeout at para sa pagsenyas ng matagumpay/hindi matagumpay na paghahatid ng mensahe.
ReliableUdpState.DisposeByTimeout:

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

Ito ay na-override lamang sa estado Natapos.
Nakumpleto.DisposeByTimeout:

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

Paraan ng ProcessPackets

Ang pamamaraan ng ProcessPackets ay responsable para sa karagdagang pagproseso ng isang pakete o mga pakete. Direktang tumawag o sa pamamagitan ng isang packet wait timer.

kaya Assembling ang pamamaraan ay na-override at responsable para sa pagsuri para sa mga nawawalang packet at paglipat sa estado Natapos, sa kaso ng pagtanggap ng huling packet at pagpasa ng matagumpay na tseke
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);
  }
}

kaya SendingCycle ang pamamaraang ito ay tinatawag lamang sa isang timer, at responsable para sa muling pagpapadala ng huling mensahe, pati na rin ang pagpapagana ng pagsasara ng timer ng koneksyon.
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);
}

kaya Natapos ang pamamaraan ay huminto sa tumatakbong timer at nagpapadala ng mensahe sa mga subscriber.
Nakumpleto.ProcessPackets:

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

Paraan ng ReceivePacket

kaya FirstPacketReceived ang pangunahing gawain ng pamamaraan ay upang matukoy kung ang unang packet ng mensahe ay talagang dumating sa interface, at din upang mangolekta ng isang mensahe na binubuo ng isang solong packet.
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);
  }
}

kaya SendingCycle na-override ang paraang ito upang tanggapin ang mga pagkilala sa paghahatid at mga kahilingan sa muling pagpapadala.
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));
}

kaya Assembling sa ReceivePacket method, ang pangunahing gawain ng pag-assemble ng mensahe mula sa mga papasok na packet ay nagaganap.
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);
  }
}

kaya Natapos ang tanging gawain ng pamamaraan ay magpadala ng muling pagkilala sa matagumpay na paghahatid ng mensahe.
Nakumpleto.ReceivePacket:

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

Paraan ng Ipadala ang Packet

kaya FirstPacketSending ang pamamaraang ito ay nagpapadala ng unang packet ng data, o kung ang mensahe ay hindi nangangailangan ng kumpirmasyon sa paghahatid, ang buong mensahe.
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);
}

kaya SendingCycle sa paraang ito, isang bloke ng mga packet ang ipinapadala.
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 );
  }
}

Mas malalim sa code. Paglikha at Pagtatatag ng mga Koneksyon

Ngayong nakita na natin ang mga pangunahing estado at ang mga pamamaraang ginagamit upang pangasiwaan ang mga estado, paghiwalayin natin ang ilang mga halimbawa kung paano gumagana ang protocol nang mas detalyado.
Diagram ng paghahatid ng data sa ilalim ng normal na mga kondisyon:Pagpapatupad ng Maaasahang Udp protocol para sa .Net

Isaalang-alang nang detalyado ang paglikha tala ng koneksyon para kumonekta at ipadala ang unang packet. Ang paglilipat ay palaging sinisimulan ng application na tumatawag sa send message API. Susunod, ang StartTransmission na paraan ng transmission control block ay ginagamit, na nagsisimula sa pagpapadala ng data para sa bagong mensahe.
Paglikha ng papalabas na koneksyon:

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

Pagpapadala ng unang packet (FirstPacketSending state):

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

Pagkatapos ipadala ang unang packet, ang nagpadala ay pumasok sa estado SendingCycle – maghintay para sa kumpirmasyon ng paghahatid ng package.
Ang receiving side, gamit ang EndReceive method, ay tumatanggap ng ipinadalang packet, lumilikha ng bago tala ng koneksyon at ipapasa ang packet na ito, na may pre-parsed na header, sa ReceivePacket na paraan ng estado para sa pagproseso FirstPacketReceived
Paglikha ng koneksyon sa receiving side:

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

Pagtanggap ng unang packet at pagpapadala ng acknowledgement (FirstPacketReceived state):

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

Mas malalim sa code. Isinasara ang koneksyon sa timeout

Ang paghawak ng timeout ay isang mahalagang bahagi ng Maaasahang UDP. Isaalang-alang ang isang halimbawa kung saan nabigo ang isang intermediate node at naging imposible ang paghahatid ng data sa parehong direksyon.
Diagram para sa pagsasara ng koneksyon sa pamamagitan ng timeout:Pagpapatupad ng Maaasahang Udp protocol para sa .Net

Tulad ng makikita mula sa diagram, ang timer ng trabaho ng nagpadala ay magsisimula kaagad pagkatapos magpadala ng isang bloke ng mga packet. Nangyayari ito sa paraan ng SendPacket ng estado SendingCycle.
Paganahin ang timer ng trabaho (estado ng SendingCycle):

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

Ang mga tagal ng timer ay itinakda kapag nalikha ang koneksyon. Ang default na ShortTimerPeriod ay 5 segundo. Sa halimbawa, ito ay nakatakda sa 1,5 segundo.

Para sa isang papasok na koneksyon, magsisimula ang timer pagkatapos matanggap ang huling papasok na data packet, nangyayari ito sa ReceivePacket na paraan ng estado. Assembling
Paganahin ang work timer (Assembling state):

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

Wala nang dumating na mga packet sa papasok na koneksyon habang naghihintay sa gumaganang timer. Tumunog ang timer at tinawag ang pamamaraang ProcessPackets, kung saan natagpuan ang mga nawawalang packet at ipinadala ang mga kahilingan sa muling paghahatid sa unang pagkakataon.
Nagpapadala ng mga kahilingan sa muling paghahatid (Status ng pagtitipon):

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

Ang variable ng TimerSecondTry ay nakatakda sa totoo. Ang variable na ito ay responsable para sa pag-restart ng gumaganang timer.

Sa panig ng nagpadala, ang gumaganang timer ay na-trigger din at ang huling ipinadala na packet ay muling ipinadala.
Paganahin ang pagsasara ng timer ng koneksyon (estado ng SendingCycle):

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

Pagkatapos nito, magsisimula ang close timer ng koneksyon sa papalabas na koneksyon.
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);
}

Ang panahon ng timeout ng close timer ng koneksyon ay 30 segundo bilang default.

Pagkaraan ng maikling panahon, muling magpapagana ang gumaganang timer sa gilid ng tatanggap, ipinapadala muli ang mga kahilingan, pagkatapos nito ay magsisimula ang pagsasara ng timer ng koneksyon para sa papasok na koneksyon

Kapag nagpaputok ang mga malapit na timer, ang lahat ng mga mapagkukunan ng parehong mga tala ng koneksyon ay inilabas. Iniuulat ng nagpadala ang pagkabigo sa paghahatid sa upstream na aplikasyon (tingnan ang Maaasahang UDP API).
Naglalabas ng mga mapagkukunan ng record ng koneksyon:

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

Mas malalim sa code. Pagpapanumbalik ng paglilipat ng data

Data transmission recovery diagram sa kaso ng packet loss:Pagpapatupad ng Maaasahang Udp protocol para sa .Net

Tulad ng napag-usapan na sa pagsasara ng koneksyon sa timeout, kapag nag-expire ang gumaganang timer, susuriin ng receiver ang mga nawawalang packet. Sa kaso ng pagkawala ng packet, isang listahan ng bilang ng mga packet na hindi nakarating sa tatanggap ay bubuuin. Ang mga numerong ito ay ipinasok sa hanay ng LostPackets ng isang partikular na koneksyon, at ang mga kahilingan para sa muling paghahatid ay ipinapadala.
Nagpapadala ng mga kahilingan upang muling maihatid ang mga pakete (Status ng pag-assemble):

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

Tatanggapin ng nagpadala ang kahilingan sa muling paghahatid at ipapadala ang mga nawawalang packet. Kapansin-pansin na sa sandaling ito ay sinimulan na ng nagpadala ang malapit na timer ng koneksyon at, kapag natanggap ang isang kahilingan, ito ay na-reset.
Muling pagpapadala ng mga nawawalang packet (estado ng 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));
}

Ang resent packet (packet #3 sa diagram) ay natanggap ng papasok na koneksyon. Ang isang pagsusuri ay ginawa upang makita kung ang window ng pagtanggap ay puno at ang normal na paghahatid ng data ay naibalik.
Sinusuri ang mga hit sa window ng pagtanggap (State ng pag-assemble):

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

Maaasahang UDP API

Upang makipag-ugnayan sa protocol ng paglilipat ng data, mayroong isang bukas na klase ng Maaasahan na Udp, na isang wrapper sa ibabaw ng block ng kontrol sa paglipat. Narito ang pinakamahalagang miyembro ng klase:

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

Ang mga mensahe ay natatanggap sa pamamagitan ng subscription. Italaga ang lagda para sa paraan ng callback:

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

Mensahe:

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

Upang mag-subscribe sa isang partikular na uri ng mensahe at/o isang partikular na nagpadala, dalawang opsyonal na parameter ang ginagamit: ReliableUdpMessageTypes messageType at IPEndPoint ipEndPoint.

Mga uri ng mensahe:

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

Ang mensahe ay ipinadala nang asynchronous; para dito, ang protocol ay nagpapatupad ng isang asynchronous na modelo ng programming:

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

Magiging totoo ang resulta ng pagpapadala ng mensahe - kung matagumpay na naabot ng mensahe ang tatanggap at mali - kung ang koneksyon ay isinara nang mag-timeout:

public bool EndSendMessage(IAsyncResult asyncResult)

Konklusyon

Marami ang hindi inilarawan sa artikulong ito. Mga mekanismo ng pagtutugma ng thread, pagbubukod at paghawak ng error, pagpapatupad ng mga paraan ng pagpapadala ng asynchronous na mensahe. Ngunit ang pangunahing bahagi ng protocol, ang paglalarawan ng lohika para sa pagproseso ng mga packet, pagtatatag ng koneksyon, at paghawak ng mga timeout, ay dapat na malinaw sa iyo.

Ang ipinakitang bersyon ng maaasahang protocol ng paghahatid ay matatag at sapat na kakayahang umangkop upang matugunan ang mga naunang tinukoy na kinakailangan. Ngunit nais kong idagdag na ang inilarawan na pagpapatupad ay maaaring mapabuti. Halimbawa, upang madagdagan ang throughput at dynamic na baguhin ang mga tagal ng timer, ang mga mekanismo tulad ng sliding window at RTT ay maaaring idagdag sa protocol, magiging kapaki-pakinabang din na magpatupad ng mekanismo para sa pagtukoy ng MTU sa pagitan ng mga node ng koneksyon (ngunit kung ang malalaking mensahe ay ipinadala) .

Salamat sa iyong pansin, inaasahan ko ang iyong mga komento at komento.

PS Para sa mga interesado sa mga detalye o gusto lang subukan ang protocol, ang link sa proyekto sa GitHube:
Maaasahang UDP Project

Mga kapaki-pakinabang na link at artikulo

  1. Detalye ng TCP protocol: sa ingles и sa Russian
  2. Detalye ng UDP protocol: sa ingles и sa Russian
  3. Pagtalakay sa RUDP protocol: draft-ietf-sigtran-reliable-udp-00
  4. Maaasahang Data Protocol: rfc908 и rfc1151
  5. Isang simpleng pagpapatupad ng kumpirmasyon sa paghahatid sa UDP: Ganap na Kontrolin ang Iyong Networking Gamit ang .NET At UDP
  6. Artikulo na naglalarawan sa mga mekanismo ng pagtawid ng NAT: Peer-to-Peer Communication sa Mga Tagasalin ng Address ng Network
  7. Pagpapatupad ng asynchronous programming model: Pagpapatupad ng CLR Asynchronous Programming Model и Paano ipatupad ang pattern ng disenyo ng IAsyncResult
  8. Pag-port ng asynchronous programming model sa task-based na asynchronous na pattern (APM sa TAP):
    TPL at Tradisyunal na .NET Asynchronous Programming
    Interop sa Iba Pang Asynchronous na Pattern at Uri

Update: Salamat mayorovp и sidristij para sa ideya ng pagdaragdag ng isang gawain sa interface. Ang pagiging tugma ng library sa mga lumang operating system ay hindi nilalabag, dahil Ang 4th framework ay sumusuporta sa parehong XP at 2003 server.

Pinagmulan: www.habr.com

Magdagdag ng komento