Uzticama Udp protokola ieviešana .Net

Internets ir mainījies jau sen. Viens no galvenajiem interneta protokoliem - UDP aplikācijas izmanto ne tikai datu diagrammu un raidījumu piegādei, bet arī "peer-to-peer" savienojumu nodrošināšanai starp tīkla mezgliem. Vienkāršā dizaina dēļ šim protokolam ir daudz iepriekš neplānotu lietojumu, tomēr nekur nav pazuduši protokola trūkumi, piemēram, garantētas piegādes trūkums. Šajā rakstā ir aprakstīta garantētās piegādes protokola ieviešana, izmantojot UDP.
Saturs:Ieraksts
Protokola prasības
Uzticama UDP galvene
Protokola vispārīgie principi
Taimauta un protokola taimeri
Uzticama UDP pārraides stāvokļa diagramma
Dziļāk kodā. transmisijas vadības bloks
Dziļāk kodā. štatos

Dziļāk kodā. Savienojumu izveide un nodibināšana
Dziļāk kodā. Savienojuma aizvēršana taimauta laikā
Dziļāk kodā. Datu pārsūtīšanas atjaunošana
Uzticama UDP API
Secinājums
Noderīgas saites un raksti

Ieraksts

Sākotnējā interneta arhitektūrā tika pieņemta viendabīga adrešu telpa, kurā katram mezglam bija globāla un unikāla IP adrese un tas varēja tieši sazināties ar citiem mezgliem. Tagad internetam faktiski ir cita arhitektūra — viena globālo IP adrešu apgabals un daudzas zonas ar privātām adresēm, kas paslēptas aiz NAT ierīcēm.Šajā arhitektūrā tikai ierīces globālajā adrešu telpā var viegli sazināties ar ikvienu tīklā, jo tām ir unikāla, globāli maršrutējama IP adrese. Privātā tīkla mezgls var izveidot savienojumu ar citiem mezgliem tajā pašā tīklā, kā arī var izveidot savienojumu ar citiem labi zināmiem mezgliem globālajā adrešu telpā. Šī mijiedarbība tiek panākta lielā mērā pateicoties tīkla adrešu tulkošanas mehānismam. NAT ierīces, piemēram, Wi-Fi maršrutētāji, izveido īpašus tulkošanas tabulas ierakstus izejošajiem savienojumiem un modificē IP adreses un portu numurus paketēs. Tas ļauj izveidot izejošos savienojumus no privātā tīkla uz resursdatoriem globālajā adrešu telpā. Bet tajā pašā laikā NAT ierīces parasti bloķē visu ienākošo trafiku, ja vien nav iestatīti atsevišķi noteikumi ienākošajiem savienojumiem.

Šī interneta arhitektūra ir pietiekami pareiza klienta-servera komunikācijai, kur klienti var atrasties privātajos tīklos un serveriem ir globāla adrese. Bet tas rada grūtības tiešai savienošanai starp diviem mezgliem dažādi privātie tīkli. Tiešs savienojums starp diviem mezgliem ir svarīgs vienādranga lietojumprogrammām, piemēram, balss pārraidei (Skype), attālinātas piekļuves iegūšanai datoram (TeamViewer) vai tiešsaistes spēlēm.

Viena no efektīvākajām metodēm vienādranga savienojuma izveidošanai starp ierīcēm dažādos privātajos tīklos tiek saukta par caurumu caurumošanu. Šo paņēmienu visbiežāk izmanto lietojumprogrammās, kuru pamatā ir UDP protokols.

Bet, ja jūsu lietojumprogrammai ir nepieciešama garantēta datu piegāde, piemēram, jūs pārsūtāt failus starp datoriem, tad, izmantojot UDP, būs daudz grūtību, jo UDP nav garantēts piegādes protokols un atšķirībā no TCP nenodrošina pakešu piegādi kārtībā. protokols.

Šajā gadījumā, lai nodrošinātu garantētu pakešu piegādi, ir nepieciešams ieviest lietojumprogrammas slāņa protokolu, kas nodrošina nepieciešamo funkcionalitāti un darbojas pār UDP.

Tūlīt gribu atzīmēt, ka pastāv TCP caurumu caurumošanas tehnika TCP savienojumu izveidošanai starp mezgliem dažādos privātajos tīklos, taču, tā kā daudzas NAT ierīces to neatbalsta, tas parasti netiek uzskatīts par galveno savienojuma veidu. tādi mezgli.

Atlikušajā šī raksta daļā es koncentrēšos tikai uz garantētās piegādes protokola ieviešanu. UDP caurumu caurumošanas tehnikas ieviešana tiks aprakstīta turpmākajos rakstos.

Protokola prasības

  1. Uzticama pakešu piegāde, kas ieviesta, izmantojot pozitīvas atgriezeniskās saites mehānismu (tā saukto pozitīvo apstiprinājumu)
  2. Nepieciešamība pēc efektīvas lielo datu pārsūtīšanas, t.i. protokolam jāizvairās no nevajadzīgas pakešu pārsūtīšanas
  3. Jābūt iespējai atcelt piegādes apstiprināšanas mehānismu (spēja darboties kā "tīrs" UDP protokols)
  4. Spēja ieviest komandu režīmu, apstiprinot katru ziņojumu
  5. Datu pārsūtīšanas, izmantojot protokolu, pamatvienībai ir jābūt ziņojumam

Šīs prasības lielā mērā sakrīt ar Reliable Data Protocol prasībām, kas aprakstītas rfc 908 и rfc 1151, un es paļāvos uz šiem standartiem, izstrādājot šo protokolu.

Lai saprastu šīs prasības, apskatīsim datu pārsūtīšanas laiku starp diviem tīkla mezgliem, izmantojot TCP un UDP protokolus. Ļaujiet mums abos gadījumos zaudēt vienu paketi.
Neinteraktīvu datu pārsūtīšana, izmantojot TCP:Uzticama Udp protokola ieviešana .Net

Kā redzams diagrammā, pakešu zuduma gadījumā TCP atklās pazaudēto paketi un ziņos par to sūtītājam, pieprasot zaudētā segmenta numuru.
Datu pārsūtīšana, izmantojot UDP protokolu:Uzticama Udp protokola ieviešana .Net

UDP neveic nekādus zaudējumu noteikšanas pasākumus. Pārsūtīšanas kļūdu kontrole UDP protokolā ir pilnībā lietojumprogrammas atbildība.

Kļūdu noteikšana TCP protokolā tiek panākta, izveidojot savienojumu ar gala mezglu, saglabājot šī savienojuma stāvokli, norādot katrā paketes galvenē nosūtīto baitu skaitu un paziņojot kvītis, izmantojot apstiprinājuma numuru.

Turklāt, lai uzlabotu veiktspēju (t.i., nosūtītu vairāk nekā vienu segmentu, nesaņemot apstiprinājumu), TCP protokols izmanto tā saukto pārraides logu - datu baitu skaitu, ko segmenta sūtītājs sagaida saņemt.

Papildinformāciju par TCP protokolu skatiet rfc 793, no UDP līdz rfc 768kur patiesībā tie ir definēti.

No iepriekš minētā ir skaidrs, ka, lai izveidotu uzticamu ziņojumu piegādes protokolu, izmantojot UDP (turpmāk tekstā Uzticams UDP), nepieciešams ieviest TCP līdzīgus datu pārsūtīšanas mehānismus. Proti:

  • saglabāt savienojuma stāvokli
  • izmantojiet segmentu numerāciju
  • izmantojiet īpašas apstiprinājuma paketes
  • izmantojiet vienkāršotu logu mehānismu, lai palielinātu protokola caurlaidspēju

Turklāt jums ir nepieciešams:

  • signalizē par ziņojuma sākumu, lai piešķirtu resursus savienojumam
  • signalizē par ziņojuma beigas, lai saņemto ziņojumu nosūtītu augšpus lietojumprogrammai un atbrīvotu protokola resursus
  • ļauj savienojumam specifiskam protokolam atspējot piegādes apstiprinājuma mehānismu, lai tas darbotos kā "tīrs" UDP

Uzticama UDP galvene

Atcerieties, ka UDP datagramma ir iekapsulēta IP datagrammā. Uzticamā UDP pakete ir atbilstoši "iesaiņota" UDP datagrammā.
Uzticama UDP galvenes iekapsulēšana:Uzticama Udp protokola ieviešana .Net

Uzticamā UDP galvenes struktūra ir diezgan vienkārša:

Uzticama Udp protokola ieviešana .Net

  • Karogi - paku kontroles karodziņi
  • MessageType — ziņojuma veids, ko izmanto augšupējās lietojumprogrammas, lai abonētu noteiktus ziņojumus
  • TransmissionId - pārraides numurs kopā ar adresāta adresi un portu unikāli identificē savienojumu
  • PacketNumber — paketes numurs
  • Opcijas - papildu protokola opcijas. Pirmās paketes gadījumā to izmanto, lai norādītu ziņojuma lielumu

Karogi ir šādi:

  • FirstPacket - pirmā ziņojuma pakete
  • NoAsk — ziņojumam nav jāiespējo apstiprinājuma mehānisms
  • LastPacket - pēdējā ziņojuma pakete
  • RequestForPacket - apstiprinājuma pakete vai pazaudētas paketes pieprasījums

Protokola vispārīgie principi

Tā kā uzticamais UDP ir vērsts uz garantētu ziņojumu pārraidi starp diviem mezgliem, tam ir jāspēj izveidot savienojumu ar otru pusi. Lai izveidotu savienojumu, sūtītājs nosūta paketi ar FirstPacket karogu, uz kuru atbilde nozīmēs, ka savienojums ir izveidots. Visas atbildes paketes jeb, citiem vārdiem sakot, apstiprinājuma paketes, vienmēr iestata lauka PacketNumber vērtību par vienu vairāk nekā lielāko veiksmīgi saņemto pakešu PacketNumber vērtību. Pirmās nosūtītās paketes lauks Opcijas ir ziņojuma lielums.

Līdzīgs mehānisms tiek izmantots savienojuma pārtraukšanai. LastPacket karodziņš ir iestatīts pēdējā ziņojuma paketē. Atbildes paketē ir norādīts pēdējās paketes numurs + 1, kas saņēmējai pusei nozīmē veiksmīgu ziņojuma piegādi.
Savienojuma izveides un izbeigšanas shēma:Uzticama Udp protokola ieviešana .Net

Kad savienojums ir izveidots, sākas datu pārsūtīšana. Dati tiek pārsūtīti pakešu blokos. Katrs bloks, izņemot pēdējo, satur noteiktu skaitu pakešu. Tas ir vienāds ar saņemšanas/sūtīšanas loga izmēru. Pēdējā datu blokā var būt mazāk pakešu. Pēc katra bloka nosūtīšanas sūtītāja puse gaida piegādes apstiprinājumu vai pieprasījumu atkārtoti piegādāt pazaudētās paketes, atstājot atvērtu saņemšanas/pārsūtīšanas logu, lai saņemtu atbildes. Pēc bloka piegādes apstiprinājuma saņemšanas saņemšanas/pārsūtīšanas logs nobīdās un tiek nosūtīts nākamais datu bloks.

Saņēmēja puse saņem paketes. Katra pakete tiek pārbaudīta, lai redzētu, vai tā ietilpst pārraides logā. Paketes un dublikāti, kas neietilpst logā, tiek filtrēti. Jo Ja loga izmērs ir fiksēts un vienāds saņēmējam un sūtītājam, tad pakešu bloka piegādes gadījumā bez zaudējumiem logs tiek pārbīdīts uz nākamā datu bloka pakešu saņemšanu un tiek saņemts piegādes apstiprinājums. nosūtīts. Ja logs nepiepildīsies darba taimera noteiktajā termiņā, tiks sākta pārbaude par to, kuras paketes nav piegādātas, un tiks nosūtīti pārsūtīšanas pieprasījumi.
Retranslācijas shēma:Uzticama Udp protokola ieviešana .Net

Taimauta un protokola taimeri

Ir vairāki iemesli, kāpēc savienojumu nevar izveidot. Piemēram, ja saņēmēja puse ir bezsaistē. Šādā gadījumā, mēģinot izveidot savienojumu, savienojums tiks slēgts ar taimautu. Uzticamā UDP ieviešana taimauta iestatīšanai izmanto divus taimerus. Pirmais, darba taimeris, tiek izmantots, lai gaidītu atbildi no attālā resursdatora. Ja tas tiek aktivizēts sūtītāja pusē, pēdējā nosūtītā pakete tiek nosūtīta atkārtoti. Ja pie adresāta beidzas taimera termiņš, tiek veikta pazaudēto pakešu pārbaude un tiek nosūtīti atkārtotas piegādes pieprasījumi.

Otrais taimeris ir nepieciešams, lai aizvērtu savienojumu, ja starp mezgliem trūkst sakaru. Sūtītāja pusē tas sākas tūlīt pēc darba taimera termiņa beigām un gaida atbildi no attālā mezgla. Ja norādītajā periodā atbilde netiek saņemta, savienojums tiek pārtraukts un resursi tiek atbrīvoti. Saņēmējai pusei savienojuma aizvēršanas taimeris tiek palaists pēc tam, kad darba taimeris ir beidzies divreiz. Tas ir nepieciešams, lai apdrošinātos pret apstiprinājuma paketes nozaudēšanu. Kad taimeris beidzas, savienojums tiek pārtraukts un resursi tiek atbrīvoti.

Uzticama UDP pārraides stāvokļa diagramma

Protokola principi tiek realizēti galīgo stāvokļu mašīnā, kuras katrs stāvoklis ir atbildīgs par noteiktu pakešu apstrādes loģiku.
Uzticama UDP stāvokļa diagramma:

Uzticama Udp protokola ieviešana .Net

slēgts - patiesībā nav stāvoklis, tas ir automāta sākuma un beigu punkts. Par valsti slēgts tiek saņemts pārraides vadības bloks, kas, realizējot asinhrono UDP serveri, pārsūta paketes uz atbilstošajiem savienojumiem un sāk stāvokļa apstrādi.

FirstPacketSending – sākotnējais stāvoklis, kurā ir izejošais savienojums, kad tiek nosūtīts ziņojums.

Šajā stāvoklī tiek nosūtīta pirmā parasto ziņojumu pakete. Ziņojumiem bez nosūtīšanas apstiprinājuma šis ir vienīgais stāvoklis, kurā tiek nosūtīts viss ziņojums.

Sūtīšanas cikls – pamata stāvoklis ziņojumu pakešu pārsūtīšanai.

Pāreja uz to no valsts FirstPacketSending tiek veikta pēc pirmās ziņojuma paketes nosūtīšanas. Šādā stāvoklī tiek saņemti visi apstiprinājumi un pieprasījumi par atkārtotu pārraidi. Iziet no tā iespējams divos gadījumos – veiksmīgas ziņojuma piegādes gadījumā vai ar taimautu.

FirstPacketReceived – ziņojuma saņēmēja sākotnējais stāvoklis.

Tas pārbauda pārraides sākuma pareizību, izveido nepieciešamās struktūras un nosūta apstiprinājumu par pirmās paketes saņemšanu.

Ziņojumam, kas sastāv no vienas paketes un tika nosūtīts, neizmantojot piegādes apliecinājumu, šis ir vienīgais stāvoklis. Pēc šāda ziņojuma apstrādes savienojums tiek aizvērts.

Montāža – pamata stāvoklis ziņojumu pakešu saņemšanai.

Tas ieraksta paketes pagaidu krātuvē, pārbauda pakešu zudumu, nosūta apstiprinājumu par pakešu bloka un visa ziņojuma piegādi un nosūta pieprasījumus par pazaudēto pakešu atkārtotu piegādi. Veiksmīgas visa ziņojuma saņemšanas gadījumā savienojums nonāk stāvoklī Pabeigts, pretējā gadījumā tiek pārtraukta taimauts.

Pabeigts – savienojuma pārtraukšana veiksmīgas visa ziņojuma saņemšanas gadījumā.

Šis stāvoklis ir nepieciešams ziņojuma salikšanai un gadījumam, kad ziņojuma piegādes apstiprinājums tika pazaudēts ceļā pie sūtītāja. No šī stāvokļa tiek izslēgts taimauts, taču savienojums tiek uzskatīts par veiksmīgi slēgtu.

Dziļāk kodā. transmisijas vadības bloks

Viens no galvenajiem uzticamā UDP elementiem ir pārraides vadības bloks. Šī bloka uzdevums ir saglabāt pašreizējos savienojumus un palīgelementus, izplatīt ienākošās paketes attiecīgajiem savienojumiem, nodrošināt saskarni pakešu nosūtīšanai uz savienojumu un ieviest protokola API. Pārraides vadības bloks saņem paketes no UDP slāņa un pārsūta tās apstrādei uz stāvokļa mašīnu. Lai saņemtu paketes, tas ievieš asinhronu UDP serveri.
Daži ReliableUdpConnectionControlBlock klases dalībnieki:

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

Asinhronā UDP servera ieviešana:

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

Katrai ziņojuma pārsūtīšanai tiek izveidota struktūra, kurā ir informācija par savienojumu. Šādu struktūru sauc savienojuma ieraksts.
Daži klases ReliableUdpConnectionRecord dalībnieki:

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

Dziļāk kodā. štatos

Valstis ievieš Reliable UDP protokola stāvokļa mašīnu, kurā notiek galvenā pakešu apstrāde. Abstraktā klase ReliableUdpState nodrošina interfeisu stāvoklim:

Uzticama Udp protokola ieviešana .Net

Visu protokola loģiku realizē iepriekš norādītās klases kopā ar palīgklasi, kas nodrošina statiskas metodes, piemēram, piemēram, ReliableUdp galvenes konstruēšanu no savienojuma ieraksta.

Tālāk mēs detalizēti apsvērsim interfeisa metožu ieviešanu, kas nosaka protokola pamata algoritmus.

DisposeByTimeout metode

Metode DisposeByTimeout ir atbildīga par savienojuma resursu atbrīvošanu pēc taimauta un signalizāciju par veiksmīgu/neveiksmīgu ziņojumu piegādi.
ReliableUdpState.DisposeByTimeout:

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

Tas ir ignorēts tikai štatā Pabeigts.
Completed.DisposeByTimeout:

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

ProcessPackets metode

ProcessPackets metode ir atbildīga par pakotnes vai paku papildu apstrādi. Zvans tieši vai caur pakešu gaidīšanas taimeri.

Var Montāža metode tiek ignorēta, un tā ir atbildīga par pazaudētu pakešu pārbaudi un pāreju uz stāvokli Pabeigts, pēdējās paketes saņemšanas un veiksmīgas pārbaudes izturēšanas gadījumā
Salikšana. Procesu paketes:

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

Var Sūtīšanas cikls šī metode tiek izsaukta tikai taimeri, un tā ir atbildīga par pēdējā ziņojuma atkārtotu nosūtīšanu, kā arī savienojuma slēgšanas taimera iespējošanu.
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);
}

Var Pabeigts metode aptur taimeri un nosūta ziņojumu abonentiem.
Completed.ProcessPackets:

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

ReceivePacket metode

Var FirstPacketReceived Metodes galvenais uzdevums ir noteikt, vai pirmā ziņojumu pakete patiešām ir nonākusi saskarnē, kā arī savākt ziņojumu, kas sastāv no vienas paketes.
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);
  }
}

Var Sūtīšanas cikls šī metode tiek ignorēta, lai pieņemtu piegādes apstiprinājumus un atkārtotas pārsūtīšanas pieprasījumus.
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));
}

Var Montāža ReceivePacket metodē notiek galvenais ziņojuma salikšanas darbs no ienākošajām paketēm.
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);
  }
}

Var Pabeigts vienīgais metodes uzdevums ir nosūtīt atkārtotu apstiprinājumu par veiksmīgu ziņojuma piegādi.
Completed.ReceivePacket:

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

Pakešu nosūtīšanas metode

Var FirstPacketSending šī metode nosūta pirmo datu paketi vai, ja ziņojumam nav nepieciešams piegādes apstiprinājums, visu ziņojumu.
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);
}

Var Sūtīšanas cikls šajā metodē tiek nosūtīts pakešu bloks.
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 );
  }
}

Dziļāk kodā. Savienojumu izveide un nodibināšana

Tagad, kad esam redzējuši pamata stāvokļus un stāvokļu apstrādei izmantotās metodes, sīkāk izdalīsim dažus protokola darbības piemērus.
Datu pārraides diagramma normālos apstākļos:Uzticama Udp protokola ieviešana .Net

Sīki apsveriet izveidi savienojuma ieraksts lai izveidotu savienojumu un nosūtītu pirmo paketi. Pārsūtīšanu vienmēr ierosina lietojumprogramma, kas izsauc ziņojuma sūtīšanas API. Tālāk tiek izsaukta pārraides vadības bloka metode StartTransmission, kas uzsāk datu pārraidi jaunajam ziņojumam.
Izejošā savienojuma izveide:

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

Pirmās paketes nosūtīšana (FirstPacketSending stāvoklis):

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

Pēc pirmās paketes nosūtīšanas sūtītājs nonāk stāvoklī Sūtīšanas cikls – gaidiet apstiprinājumu par pakas piegādi.
Saņēmēja puse, izmantojot EndReceive metodi, saņem nosūtīto paketi, izveido jaunu savienojuma ieraksts un nodod šo paketi ar iepriekš parsētu galveni apstrādei stāvokļa ReceivePacket metodei FirstPacketReceived
Savienojuma izveide saņēmējā pusē:

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

Pirmās paketes saņemšana un apstiprinājuma nosūtīšana (FirstPacketReceived stāvoklis):

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

Dziļāk kodā. Savienojuma aizvēršana taimauta laikā

Taimauta apstrāde ir svarīga uzticama UDP sastāvdaļa. Apsveriet piemēru, kurā starpmezgls neizdevās un datu piegāde abos virzienos kļuva neiespējama.
Diagramma savienojuma slēgšanai pēc taimauta:Uzticama Udp protokola ieviešana .Net

Kā redzams diagrammā, sūtītāja darba taimeris sākas uzreiz pēc pakešu bloka nosūtīšanas. Tas notiek štata SendPacket metodē Sūtīšanas cikls.
Darba taimera iespējošana (SendingCycle stāvoklis):

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

Taimera periodi tiek iestatīti savienojuma izveides laikā. Noklusējuma ShortTimerPeriod ir 5 sekundes. Piemērā tas ir iestatīts uz 1,5 sekundēm.

Ienākošajam savienojumam taimeris sāk darboties pēc pēdējās ienākošās datu paketes saņemšanas, tas notiek stāvokļa ReceivePacket metodē Montāža
Darba taimera iespējošana (salikšanas stāvoklis):

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

Gaidot darba taimeri, ienākošajā savienojumā vairs netika saņemtas nekādas paketes. Taimeris noslēdzās un izsauca metodi ProcessPackets, kur tika atrastas pazaudētās paketes un pirmo reizi tika nosūtīti atkārtotas piegādes pieprasījumi.
Atkārtotas piegādes pieprasījumu sūtīšana (salikšanas stāvoklis):

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

Mainīgais TimerSecondTry ir iestatīts uz patiess. Šis mainīgais ir atbildīgs par darba taimera restartēšanu.

Sūtītāja pusē tiek iedarbināts arī darba taimeris un tiek atkārtoti nosūtīta pēdējā nosūtītā pakete.
Savienojuma aizvēršanas taimera iespējošana (SendingCycle stāvoklis):

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

Pēc tam izejošajā savienojumā sākas savienojuma aizvēršanas taimeris.
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);
}

Savienojuma slēgšanas taimera taimauta periods pēc noklusējuma ir 30 sekundes.

Pēc neilga laika atkal nostrādā darba taimeris adresāta pusē, atkal tiek sūtīti pieprasījumi, pēc kā sākas savienojuma aizvēršanas taimeris ienākošajam savienojumam

Kad tiek aktivizēti aizvēršanas taimeri, tiek atbrīvoti visi abu savienojuma ierakstu resursi. Sūtītājs ziņo par piegādes kļūmi augšējai lietojumprogrammai (skatiet sadaļu Uzticama UDP API).
Savienojuma ieraksta resursu atbrīvošana:

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

Dziļāk kodā. Datu pārsūtīšanas atjaunošana

Datu pārraides atkopšanas diagramma pakešu zuduma gadījumā:Uzticama Udp protokola ieviešana .Net

Kā jau tika apspriests savienojuma aizvēršanas laikā, kad beidzas darba taimeris, uztvērējs pārbaudīs, vai nav pazaudētas paketes. Pakešu nozaudēšanas gadījumā tiks sastādīts saraksts ar to pakešu skaitu, kuras nesasniedza adresātu. Šie skaitļi tiek ievadīti konkrēta savienojuma masīvā LostPackets un tiek nosūtīti atkārtotas piegādes pieprasījumi.
Pieprasījumu sūtīšana paku atkārtotai piegādei (komplektēšanas stāvoklis):

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

Sūtītājs pieņems atkārtotas piegādes pieprasījumu un nosūtīs trūkstošās paketes. Ir vērts atzīmēt, ka šajā brīdī sūtītājs jau ir palaidis savienojuma aizvēršanas taimeri un, kad tiek saņemts pieprasījums, tas tiek atiestatīts.
Pazaudēto pakešu atkārtota sūtīšana (SendingCycle stāvoklis):

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

Atkārtoti nosūtīto paketi (pakete #3 diagrammā) saņem ienākošais savienojums. Tiek pārbaudīts, vai saņemšanas logs ir pilns un tiek atjaunota normāla datu pārraide.
Trāpījumu pārbaude saņemšanas logā (salikšanas stāvoklis):

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

Uzticama UDP API

Lai mijiedarbotos ar datu pārsūtīšanas protokolu, ir atvērta Reliable Udp klase, kas ir pārsūtīšanas vadības bloka aptinums. Šeit ir vissvarīgākie klases dalībnieki:

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

Ziņas tiek saņemtas abonējot. Delegāta paraksts atzvanīšanas metodei:

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

Ziņa:

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

Lai abonētu noteiktu ziņojuma veidu un/vai noteiktu sūtītāju, tiek izmantoti divi izvēles parametri: ReliableUdpMessageTypes messageType un IPEndPoint ipEndPoint.

Ziņojumu veidi:

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

Ziņojums tiek nosūtīts asinhroni; šim protokols ievieš asinhronas programmēšanas modeli:

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

Ziņojuma nosūtīšanas rezultāts būs patiess - ja ziņojums veiksmīgi sasniedza adresātu, un nepatiess - ja savienojums tika aizvērts ar taimautu:

public bool EndSendMessage(IAsyncResult asyncResult)

Secinājums

Šajā rakstā daudz kas nav aprakstīts. Pavedienu saskaņošanas mehānismi, izņēmumu un kļūdu apstrāde, asinhrono ziņojumu sūtīšanas metožu ieviešana. Bet jums ir jābūt skaidram protokola kodolam, pakešu apstrādes loģikas aprakstam, savienojuma izveidei un taimautu apstrādei.

Uzticamā piegādes protokola demonstrētā versija ir pietiekami izturīga un elastīga, lai atbilstu iepriekš noteiktajām prasībām. Bet es gribu piebilst, ka aprakstīto realizāciju var uzlabot. Piemēram, lai palielinātu caurlaidspēju un dinamiski mainītu taimera periodus, protokolam var pievienot tādus mehānismus kā bīdāms logs un RTT, kā arī būs lietderīgi ieviest mehānismu MTU noteikšanai starp savienojuma mezgliem (bet tikai tad, ja tiek nosūtīti lieli ziņojumi) .

Paldies par uzmanību, gaidīšu jūsu komentārus un komentārus.

PS Tiem, kas interesējas par detaļām vai vienkārši vēlas pārbaudīt protokolu, saite uz projektu vietnē GitHube:
Uzticams UDP projekts

Noderīgas saites un raksti

  1. TCP protokola specifikācija: angļu valodā и krieviski
  2. UDP protokola specifikācija: angļu valodā и krieviski
  3. Diskusija par RUDP protokolu: draft-ietf-sigtran-reliable-udp-00
  4. Uzticams datu protokols: rfc 908 и rfc 1151
  5. Vienkārša piegādes apstiprinājuma ieviešana, izmantojot UDP: Pilnībā kontrolējiet savu tīklu, izmantojot .NET un UDP
  6. Raksts, kurā aprakstīti NAT šķērsošanas mehānismi: Vienādranga saziņa starp tīkla adrešu tulkotājiem
  7. Asinhronās programmēšanas modeļa ieviešana: CLR asinhronās programmēšanas modeļa ieviešana и Kā ieviest IAsyncResult dizaina modeli
  8. Asinhronās programmēšanas modeļa pārnešana uz uzdevumu balstītu asinhrono modeli (APM TAP):
    TPL un tradicionālā .NET asinhronā programmēšana
    Sadarbība ar citiem asinhroniem modeļiem un veidiem

Atjauninājums: Paldies Mayorovp и sidristij par ideju pievienot interfeisam uzdevumu. Bibliotēkas savietojamība ar vecām operētājsistēmām netiek pārkāpta, jo 4. sistēma atbalsta gan XP, gan 2003 serveri.

Avots: www.habr.com

Pievieno komentāru