Innleiðing áreiðanlegrar Udp samskiptareglur fyrir .Net

Netið hefur breyst fyrir löngu síðan. Ein helsta samskiptareglur internetsins - UDP er notað af forritum, ekki aðeins til að skila gagnaskrám og útsendingum, heldur einnig til að veita "jafningi-til-jafningi" tengingar milli nethnúta. Vegna einfaldrar hönnunar hefur þessi samskiptaregla marga áður ófyrirséða notkun, hins vegar hafa gallar bókunarinnar, svo sem skortur á tryggri afhendingu, hvergi horfið. Þessi grein lýsir innleiðingu tryggðrar afhendingarreglur yfir UDP.
Efnisyfirlit:Færslu
Bókunarkröfur
Áreiðanlegur UDP haus
Almennar meginreglur bókunarinnar
Tímamörk og samskiptatímamælar
Áreiðanleg skýringarmynd UDP flutningsástands
Dýpra í kóðann. gírstýringareining
Dýpra í kóðann. ríki

Dýpra í kóðann. Að búa til og koma á tengslum
Dýpra í kóðann. Lokar tengingunni á tímamörkum
Dýpra í kóðann. Endurheimtir gagnaflutning
Áreiðanlegt UDP API
Ályktun
Gagnlegar tenglar og greinar

Færslu

Upprunalegur arkitektúr internetsins gerði ráð fyrir einsleitu vistfangarými þar sem hver hnútur hafði alþjóðlegt og einstakt IP-tölu og gæti átt bein samskipti við aðra hnúta. Nú hefur internetið í rauninni annan arkitektúr - eitt svæði af alþjóðlegum IP tölum og mörg svæði með einkanetföng falin á bak við NAT tæki.Í þessum arkitektúr geta aðeins tæki í hnattrænu vistfangarými auðveldlega átt samskipti við hvern sem er á netinu vegna þess að þau hafa einstakt IP-tölu sem hægt er að senda á heimsvísu. Hnútur á einkaneti getur tengst öðrum hnútum á sama neti og getur einnig tengst öðrum vel þekktum hnútum í alheimsvistfangarýminu. Þessi víxlverkun næst að mestu leyti vegna þýðingarkerfis netfanga. NAT tæki, eins og Wi-Fi beinar, búa til sérstakar þýðingartöflufærslur fyrir sendar tengingar og breyta IP tölum og gáttanúmerum í pökkum. Þetta gerir útleiðandi tengingar frá einkanetinu til hýsinga í alheimsvistfangarýminu. En á sama tíma loka NAT tæki venjulega alla komandi umferð nema sérstakar reglur um komandi tengingar séu settar.

Þessi arkitektúr internetsins er nógu rétt fyrir samskipti viðskiptavinar og miðlara, þar sem viðskiptavinir geta verið í einkanetum og netþjónar hafa alþjóðlegt heimilisfang. En það skapar erfiðleika fyrir beina tengingu tveggja hnúta á milli ýmsir einkanet. Bein tenging á milli tveggja hnúta er mikilvæg fyrir jafningjaforrit eins og raddsendingu (Skype), fá fjaraðgang að tölvu (TeamViewer) eða netleiki.

Ein áhrifaríkasta aðferðin til að koma á jafningjatengingu milli tækja á mismunandi einkanetum er kallað gata. Þessi tækni er oftast notuð með forritum sem byggjast á UDP samskiptareglum.

En ef forritið þitt krefst tryggrar afhendingar gagna, til dæmis flytur þú skrár á milli tölva, þá mun notkun UDP eiga í miklum erfiðleikum vegna þess að UDP er ekki tryggð afhendingaraðferð og veitir ekki pakkaafhendingu í röð, ólíkt TCP siðareglur.

Í þessu tilviki, til að tryggja trygga pakkaafhendingu, er nauðsynlegt að innleiða samskiptareglur fyrir forritslag sem veitir nauðsynlega virkni og vinnur yfir UDP.

Ég vil taka það strax fram að það er til TCP gatatækni til að koma á TCP tengingum á milli hnúta í mismunandi einkanetum, en vegna skorts á stuðningi við það hjá mörgum NAT tækjum er það venjulega ekki talið aðalleiðin til að tengjast svona hnúta.

Það sem eftir er af þessari grein mun ég einbeita mér aðeins að framkvæmd tryggðrar afhendingarreglur. Útfærslu UDP gatatækninnar verður lýst í eftirfarandi greinum.

Bókunarkröfur

  1. Áreiðanleg pakkaafhending útfærð með jákvæðri endurgjöf (svokölluð jákvæða viðurkenning)
  2. Þörfin fyrir skilvirkan flutning stórra gagna, þ.e. siðareglur verða að forðast óþarfa pakkamiðlun
  3. Það ætti að vera hægt að hætta við afhendingu staðfestingarkerfisins (getan til að virka sem „hrein“ UDP-samskiptareglur)
  4. Geta til að innleiða stjórnunarham, með staðfestingu á hverju skeyti
  5. Grunneining gagnaflutnings yfir samskiptareglur verður að vera skilaboð

Þessar kröfur falla að mestu leyti saman við kröfur um áreiðanlega gagnabókun sem lýst er í 908 и 1151, og ég treysti á þá staðla þegar ég þróaði þessa samskiptareglu.

Til að skilja þessar kröfur skulum við skoða tímasetningu gagnaflutnings milli tveggja nethnúta með því að nota TCP og UDP samskiptareglur. Í báðum tilfellum munum við hafa einn pakka týndan.
Flutningur ógagnvirkra gagna yfir TCP:Innleiðing áreiðanlegrar Udp samskiptareglur fyrir .Net

Eins og þú sérð á skýringarmyndinni, ef um er að ræða pakkatap, mun TCP greina týnda pakkann og tilkynna það til sendanda með því að biðja um númer týnda hlutans.
Gagnaflutningur í gegnum UDP samskiptareglur:Innleiðing áreiðanlegrar Udp samskiptareglur fyrir .Net

UDP tekur ekki neinar tapsuppgötvunarskref. Eftirlit með sendingarvillum í UDP samskiptareglum er alfarið á ábyrgð forritsins.

Villugreining í TCP samskiptareglunum er náð með því að koma á tengingu við endahnút, geyma stöðu þeirrar tengingar, gefa til kynna fjölda bæta sem send eru í hverjum pakkahaus og tilkynna kvittanir með staðfestingarnúmeri.

Að auki, til að bæta árangur (þ.e. senda fleiri en einn hluta án þess að fá staðfestingu), notar TCP samskiptareglur svokallaðan sendingarglugga - fjölda gagnabæta sem sendandi hlutans býst við að fá.

Fyrir frekari upplýsingar um TCP samskiptareglur, sjá 793, frá UDP til 768þar sem þau eru í raun skilgreind.

Af ofangreindu er ljóst að til að búa til áreiðanlega skilaboðaskilareglu yfir UDP (hér eftir nefnt Áreiðanlegt UDP), það er nauðsynlegt til að innleiða gagnaflutningskerfi svipað og TCP. Nefnilega:

  • vista tengingarstöðu
  • nota hlutanúmerun
  • nota sérstaka fermingarpakka
  • notaðu einfaldaða gluggakerfi til að auka samskiptaafköst

Að auki þarftu:

  • gefa til kynna upphaf skilaboða til að úthluta fjármagni fyrir tenginguna
  • gefa til kynna lok skilaboða, til að senda móttekið skilaboð til andstreymisforritsins og losa samskiptareglur
  • leyfa tengingarsértæku samskiptareglunum að slökkva á afhendingu staðfestingarkerfisins til að virka sem "hreint" UDP

Áreiðanlegur UDP haus

Mundu að UDP gagnarit er hjúpað í IP gagnarit. Áreiðanlegur UDP pakkinn er á viðeigandi hátt „vafinn“ inn í UDP gagnaskrá.
Áreiðanleg UDP haus umbúðir:Innleiðing áreiðanlegrar Udp samskiptareglur fyrir .Net

Uppbygging áreiðanlega UDP haussins er frekar einföld:

Innleiðing áreiðanlegrar Udp samskiptareglur fyrir .Net

  • Fánar - pakkaeftirlitsfánar
  • MessageType - skilaboðategund notuð af andstreymisforritum til að gerast áskrifandi að sérstökum skilaboðum
  • TransmissionId - númer sendingarinnar, ásamt heimilisfangi og höfn viðtakanda, auðkennir tenginguna einstaklega
  • PacketNumber - pakkanúmer
  • Valkostir - viðbótarsamskiptavalkostir. Þegar um er að ræða fyrsta pakkann er hann notaður til að gefa til kynna stærð skilaboðanna

Fánar eru sem hér segir:

  • FirstPacket - fyrsti pakkinn í skilaboðunum
  • NoAsk - skilaboðin þurfa ekki staðfestingarkerfi til að vera virkt
  • LastPacket - síðasti pakkinn í skilaboðunum
  • RequestForPacket - staðfestingarpakki eða beiðni um týndan pakka

Almennar meginreglur bókunarinnar

Þar sem áreiðanleg UDP er lögð áhersla á trygga skilaboðasendingu milli tveggja hnúta, verður það að geta komið á tengingu við hina hliðina. Til að koma á tengingu sendir sendandinn pakka með FirstPacket fánanum, svarið við því þýðir að tengingin er komin á. Allir svarpakkar, eða með öðrum orðum, staðfestingarpakkar, stilla alltaf gildi PacketNumber reitsins á eitt meira en stærsta PacketNumber gildi pakka sem hafa tekist vel. Valkostir reiturinn fyrir fyrsta pakkann sem sendur er er stærð skilaboðanna.

Svipað fyrirkomulag er notað til að slíta tengingu. LastPacket fáninn er stilltur á síðasta pakka skilaboðanna. Í svarpakkanum er númer síðasta pakkans + 1 gefið til kynna, sem fyrir móttökuhliðina þýðir árangursríka afhendingu skilaboðanna.
Stofnun tenginga og lúkningarmynd:Innleiðing áreiðanlegrar Udp samskiptareglur fyrir .Net

Þegar tengingunni er komið á hefst gagnaflutningur. Gögn eru send í pakkablokkum. Hver blokk, nema sá síðasti, inniheldur fastan fjölda pakka. Það er jafnt við móttöku/sendingargluggastærð. Síðasti gagnablokkinn gæti verið með færri pakka. Eftir að hafa sent hverja blokk bíður sendandi eftir staðfestingu á afhendingu eða beiðni um að afhenda týnda pakka aftur, og skilur móttöku/sendingargluggann eftir opinn til að fá svör. Eftir að hafa fengið staðfestingu á blokkafhendingu færist móttöku/sendingarglugginn og næsti gagnablokk er sendur.

Móttökulið tekur á móti pakkanum. Hver pakki er athugaður til að sjá hvort hann falli innan sendingargluggans. Pakkar og afrit sem falla ekki inn í gluggann eru síuð út. Vegna þess að Ef stærð gluggans er föst og sú sama fyrir viðtakanda og sendanda, þá er glugginn færður til að taka á móti pökkum af næsta gagnablokk, ef pakkablokk er afhent án taps, og staðfesting á afhendingu er sent. Ef glugginn fyllist ekki innan þess tímabils sem vinnutíminn setur, þá verður athugað hvaða pakkar hafa ekki verið afhentir og beiðnir um endursendingar sendar.
Endursendingarmynd:Innleiðing áreiðanlegrar Udp samskiptareglur fyrir .Net

Tímamörk og samskiptatímamælar

Það eru nokkrar ástæður fyrir því að ekki er hægt að koma á tengingu. Til dæmis ef móttakandi er ótengdur. Í þessu tilviki, þegar reynt er að koma á tengingu, verður tengingunni lokað með tímamörkum. Áreiðanleg UDP útfærsla notar tvo tímamæla til að stilla tímamörk. Sá fyrsti, vinnutíminn, er notaður til að bíða eftir svari frá ytri gestgjafanum. Ef það kviknar á sendandamegin, þá er síðasti sendi pakkinn sendur aftur. Ef tímamælirinn rennur út hjá viðtakanda, þá er athugað með týnda pakka og beiðnir um endursendingu sendar.

Seinni tímamælirinn er nauðsynlegur til að loka tengingunni ef samskiptaleysið á milli hnútanna. Fyrir sendandann byrjar það strax eftir að vinnutímamælirinn rennur út og bíður eftir svari frá ytri hnútnum. Ef ekki er svarað innan tilgreinds tímabils er tengingunni slitið og tilföng losuð. Fyrir móttökuhliðina er tengingarlokunartíminn ræstur eftir að vinnutímamælirinn rennur út tvisvar. Þetta er nauðsynlegt til að tryggja gegn tapi staðfestingarpakkans. Þegar tímamælirinn rennur út er tengingunni einnig slitið og tilföng losuð.

Áreiðanleg skýringarmynd UDP flutningsástands

Meginreglur samskiptareglunnar eru útfærðar í endanlegri stöðuvél, þar sem hvert ríki ber ábyrgð á ákveðinni rökfræði pakkavinnslu.
Áreiðanleg UDP ástandsmynd:

Innleiðing áreiðanlegrar Udp samskiptareglur fyrir .Net

Lokað - er í raun ekki ástand, það er upphafs- og endapunktur fyrir sjálfvirkann. Fyrir ríkið Lokað sendingarstýringarblokk er móttekin, sem, með því að útfæra ósamstilltan UDP miðlara, sendir pakka á viðeigandi tengingar og byrjar ástandsvinnslu.

FirstPacketSending – upphafsástandið þar sem sendan tenging er þegar skilaboðin eru send.

Í þessu ástandi er fyrsti pakkinn fyrir venjuleg skilaboð sendur. Fyrir skilaboð án sendingarstaðfestingar er þetta eina ríkið þar sem öll skilaboðin eru send.

SendingCycle – grunnástand fyrir sendingu skilaboðapakka.

Umskipti til þess frá ríkinu FirstPacketSending framkvæmt eftir að fyrsti pakki skilaboðanna hefur verið sendur. Það er í þessu ástandi sem allar viðurkenningar og beiðnir um endursendingar koma. Hætta er hægt í tveimur tilfellum - ef skilaboðin hafa skilað árangri eða eftir tímamörk.

FirstPacket Received – upphafsástand fyrir viðtakanda skilaboðanna.

Það athugar réttmæti upphafs sendingarinnar, býr til nauðsynlegar mannvirki og sendir staðfestingu á móttöku fyrsta pakkans.

Fyrir skilaboð sem samanstanda af einum pakka og voru send án þess að nota sönnun fyrir afhendingu er þetta eina ríkið. Eftir að hafa unnið slík skilaboð er tengingunni lokað.

Samsetning – grunnástand fyrir móttöku skilaboðapakka.

Það skrifar pakka í bráðabirgðageymslu, athugar með tap á pakka, sendir staðfestingar fyrir afhendingu pakkablokkar og öll skilaboðin og sendir beiðnir um endursendingu á týndum pakka. Ef vel tekst til við móttöku á öllu skilaboðunum fer tengingin í ríkið Lokið, annars rennur út tímamörk.

Lokið – að loka tengingunni ef vel tekst til við móttöku á öllu skilaboðunum.

Þetta ástand er nauðsynlegt fyrir samsetningu skeytisins og fyrir tilvikið þegar staðfesting á afhendingu skeytisins glataðist á leiðinni til sendanda. Þetta ástand er hætt með tímamörkum, en tengingin er talin hafa tekist að loka.

Dýpra í kóðann. gírstýringareining

Einn af lykilþáttum áreiðanlegrar UDP er sendistýringarblokkin. Verkefni þessarar blokkar er að geyma núverandi tengingar og hjálparþætti, dreifa komandi pökkum til samsvarandi tenginga, útvega viðmót til að senda pakka í tengingu og innleiða siðareglur API. Sendingarstýringarblokkin tekur á móti pökkum frá UDP laginu og sendir þá til ríkisvélarinnar til vinnslu. Til að taka á móti pökkum útfærir það ósamstilltan UDP netþjón.
Sumir meðlimir ReliableUdpConnectionControlBlock flokksins:

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

Útfærsla á ósamstilltum UDP netþjóni:

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

Fyrir hvern skilaboðaflutning er búið til uppbygging sem inniheldur upplýsingar um tenginguna. Slík uppbygging er kölluð tengingarskrá.
Sumir meðlimir ReliableUdpConnectionRecord bekknum:

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

Dýpra í kóðann. ríki

Ríki innleiða ástandsvél áreiðanlegrar UDP samskiptareglur, þar sem aðalvinnsla pakka fer fram. Ágripsflokkurinn ReliableUdpState veitir viðmót fyrir ríkið:

Innleiðing áreiðanlegrar Udp samskiptareglur fyrir .Net

Öll rökfræði samskiptareglunnar er útfærð af flokkunum sem kynntir eru hér að ofan, ásamt aukaflokki sem veitir truflanir aðferðir, eins og til dæmis að smíða ReliableUdp hausinn úr tengingarskránni.

Næst munum við íhuga ítarlega útfærslu viðmótsaðferðanna sem ákvarða grunnalgrím samskiptareglunnar.

DisposeByTimeout aðferð

DisposeByTimeout aðferðin er ábyrg fyrir því að losa tengingarauðlindir eftir tímamörk og gefa merki um árangursríka/misheppnaða sendingu skilaboða.
ReliableUdpState.DisposeByTimeout:

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

Það er aðeins hnekkt í ríkinu Lokið.
Completed.DisposeByTimeout:

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

ProcessPackets aðferð

ProcessPackets aðferðin ber ábyrgð á viðbótarvinnslu pakka eða pakka. Hringt beint eða í gegnum pakkabiðtímamæli.

Í ástandi Samsetning aðferðin er hnekkt og ber ábyrgð á því að athuga með týnda pakka og skipta yfir í ástandið Lokið, ef þú færð síðasta pakkann og hefur staðist árangursríka athugun
Samsetning.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);
  }
}

Í ástandi SendingCycle þessi aðferð er aðeins kölluð á tímamæli og er ábyrg fyrir endursendingu síðustu skilaboða, auk þess að virkja tengingarlokunartímamæli.
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);
}

Í ástandi Lokið aðferðin stöðvar tímamælirinn og sendir skilaboðin til áskrifenda.
Lokið.ProcessPackets:

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

ReceivePacket aðferð

Í ástandi FirstPacket Received Aðalverkefni aðferðarinnar er að ákvarða hvort fyrsti skilaboðapakkinn hafi raunverulega borist í viðmótið og einnig að safna skilaboðum sem samanstanda af einum pakka.
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);
  }
}

Í ástandi SendingCycle þessari aðferð er hnekkt til að taka við sendingarviðurkenningum og endursendingarbeiðnum.
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));
}

Í ástandi Samsetning í ReceivePacket aðferðinni fer fram aðalvinnan við að setja saman skilaboð úr pökkum sem berast.
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);
  }
}

Í ástandi Lokið eina verkefni aðferðarinnar er að senda aftur staðfestingu á árangursríkri afhendingu skilaboðanna.
Completed.ReceivePacket:

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

Senda pakkaaðferð

Í ástandi FirstPacketSending þessi aðferð sendir fyrsta gagnapakkann, eða ef skilaboðin krefjast ekki staðfestingar á afhendingu, öll skilaboðin.
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);
}

Í ástandi SendingCycle í þessari aðferð er pakkablokk sendur.
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 );
  }
}

Dýpra í kóðann. Að búa til og koma á tengslum

Nú þegar við höfum séð grunnástandið og aðferðirnar sem notaðar eru til að meðhöndla ríki skulum við brjóta niður nokkur dæmi um hvernig samskiptareglur virka aðeins nánar.
Skýringarmynd gagnaflutnings við venjulegar aðstæður:Innleiðing áreiðanlegrar Udp samskiptareglur fyrir .Net

Íhugaðu sköpunina í smáatriðum tengingarskrá til að tengjast og senda fyrsta pakkann. Flutningurinn er alltaf hafin af forritinu sem kallar á send message API. Næst er StartTransmission aðferð sendingarstýringarblokkarinnar kölluð til, sem byrjar sendingu gagna fyrir nýju skilaboðin.
Að búa til sendan tengingu:

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

Sendir fyrsta pakkann (FirstPacketSending ástand):

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

Eftir að hafa sent fyrsta pakkann fer sendandinn inn í ástandið SendingCycle – bíða eftir staðfestingu á afhendingu pakka.
Móttökuhliðin, með EndReceive aðferðinni, tekur á móti sendum pakka, býr til nýjan tengingarskrá og sendir þennan pakka, með fyrirfram greindum haus, í ReceivePacket aðferð ríkisins til vinnslu FirstPacket Received
Að búa til tengingu á móttökuhlið:

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

Að taka á móti fyrsta pakkanum og senda staðfestingu (FirstPacketReceived ástand):

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

Dýpra í kóðann. Lokar tengingunni á tímamörkum

Meðhöndlun tímafrests er mikilvægur hluti af áreiðanlegum UDP. Skoðum dæmi þar sem millihnút mistókst og gagnasending í báðar áttir varð ómöguleg.
Skýringarmynd til að loka tengingu með tímamörkum:Innleiðing áreiðanlegrar Udp samskiptareglur fyrir .Net

Eins og sést á skýringarmyndinni byrjar vinnutímamælir sendanda strax eftir að pakkablokk er send. Þetta gerist í SendPacket aðferð ríkisins SendingCycle.
Virkja vinnutímamæli (SendingCycle ástand):

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

Tímamælirinn er stilltur þegar tengingin er búin til. Sjálfgefið ShortTimerPeriod er 5 sekúndur. Í dæminu er það stillt á 1,5 sekúndur.

Fyrir komandi tengingu byrjar tímamælirinn eftir að hafa fengið síðasta komandi gagnapakkann, þetta gerist í ReceivePacket aðferð ríkisins Samsetning
Virkja tímamælir (samsetningarástand):

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

Engir fleiri pakkar bárust á komandi tengingu á meðan beðið var eftir vinnutímanum. Tímamælirinn fór af stað og kallaði á ProcessPackets aðferðina, þar sem týndu pakkarnir fundust og endursendingarbeiðnir voru sendar í fyrsta skipti.
Sendir beiðnir um endursendingu (samsetningarástand):

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

TimerSecondTry breytan er stillt á satt. Þessi breyta ber ábyrgð á því að endurræsa vinnutímamælirinn.

Hjá sendanda er vinnutíminn einnig ræstur og síðasti sendur pakkinn er sendur aftur.
Virkjar tengingarlokunartímamæli (SendingCycle ástand):

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

Eftir það byrjar tengingarlokunartíminn í útsendingunni.
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);
}

Tímatími fyrir lokun tengingar er sjálfgefið 30 sekúndur.

Eftir stuttan tíma kviknar aftur tímamælirinn á hlið viðtakanda, beiðnir eru sendar aftur, eftir það fer tengingarlokunartíminn í gang fyrir komandi tengingu

Þegar lokatímamælirinn ræsir losnar allar auðlindir beggja tengingaskránna. Sendandi tilkynnir sendingarbilun til andstreymisforritsins (sjá Áreiðanlegt UDP API).
Að gefa út tilföng fyrir tengingarskrá:

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

Dýpra í kóðann. Endurheimtir gagnaflutning

Endurheimtarmynd gagnaflutnings ef pakka tapast:Innleiðing áreiðanlegrar Udp samskiptareglur fyrir .Net

Eins og áður hefur verið fjallað um í lokun tengingarinnar á tímamörkum, þegar vinnutímamælirinn rennur út, mun móttakandinn athuga hvort týndir eru pakkar. Ef um pakkatap er að ræða verður tekinn saman listi yfir fjölda pakka sem ekki bárust til viðtakanda. Þessi númer eru færð inn í LostPackets fylki tiltekinnar tengingar og beiðnir um endursendingu eru sendar.
Sendir beiðnir um að endurafhenda pakka (samsetningarástand):

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

Sendandi mun samþykkja endursendingarbeiðnina og senda pakkana sem vantar. Þess má geta að á þessu augnabliki hefur sendandinn þegar byrjað að loka tímamæli tengingar og þegar beiðni er móttekin er hún endurstillt.
Endursendu glataða pakka (SendingCycle ástand):

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

Endursendi pakkinn (pakki #3 á skýringarmyndinni) er móttekin af komandi tengingu. Athugað er hvort móttökuglugginn sé fullur og eðlileg gagnasending er endurheimt.
Athugar hvort hitting sé í móttökuglugganum (samsetningarástand):

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

Áreiðanlegt UDP API

Til að hafa samskipti við gagnaflutningssamskiptareglur er opinn áreiðanlegur Udp flokkur, sem er umbúðir yfir flutningsstýringarblokkinni. Hér eru mikilvægustu meðlimir bekkjarins:

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

Skilaboð berast í áskrift. Fulltrúi undirskrift fyrir svarhringingaraðferðina:

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

Skilaboð:

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

Til að gerast áskrifandi að ákveðinni skilaboðategund og/eða tilteknum sendanda eru tvær valfrjálsar breytur notaðar: ReliableUdpMessageTypes messageType og IPEndPoint ipEndPoint.

Tegundir skilaboða:

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

Skilaboðin eru send ósamstillt; fyrir þetta útfærir samskiptareglan ósamstillt forritunarlíkan:

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

Niðurstaðan af því að senda skilaboð mun vera sönn - ef skilaboðin hafa náðst til viðtakanda og ósönn - ef tengingin var lokuð eftir tímamörk:

public bool EndSendMessage(IAsyncResult asyncResult)

Ályktun

Margt hefur ekki verið lýst í þessari grein. Þráðasamsvörun, undantekningar- og villumeðferð, innleiðing á ósamstilltum skilaboðasendingum. En kjarninn í samskiptareglunum, lýsingin á rökfræði fyrir vinnslu pakka, að koma á tengingu og meðhöndla tímamörk, ætti að vera þér ljós.

Sýndar útgáfa af áreiðanlegum afhendingarreglum er nógu öflug og sveigjanleg til að uppfylla áður skilgreindar kröfur. En ég vil bæta því við að hægt er að bæta þá framkvæmd sem lýst er. Til dæmis, til að auka afköst og breyta tímatímum á virkan hátt, er hægt að bæta aðferðum eins og renniglugga og RTT við samskiptareglurnar, það mun einnig vera gagnlegt að innleiða kerfi til að ákvarða MTU milli tengihnúta (en aðeins ef stór skilaboð eru send) .

Þakka þér fyrir athyglina, ég bíð spenntur eftir athugasemdum þínum og athugasemdum.

PS Fyrir þá sem hafa áhuga á smáatriðum eða vilja bara prófa siðareglur, hlekkurinn á verkefnið á GitHube:
Áreiðanlegt UDP verkefni

Gagnlegar tenglar og greinar

  1. TCP samskiptareglur: á ensku и á rússnesku
  2. UDP samskiptareglur: á ensku и á rússnesku
  3. Umræða um RUDP siðareglur: draft-ietf-sigtran-reliable-udp-00
  4. Áreiðanleg gagnabókun: 908 и 1151
  5. Einföld útfærsla á staðfestingu á afhendingu yfir UDP: Taktu fulla stjórn á netkerfinu þínu með .NET og UDP
  6. Grein sem lýsir NAT yfirferðaraðferðum: Jafningi-til-jafningi samskipti yfir netfangaþýðendur
  7. Innleiðing á ósamstilltu forritunarlíkani: Innleiðing á CLR ósamstilltu forritunarlíkani и Hvernig á að innleiða IAsyncResult hönnunarmynstrið
  8. Flytja ósamstillta forritunarlíkanið yfir á verkefnabundið ósamstillt mynstur (APM í TAP):
    TPL og hefðbundin .NET ósamstilltur forritun
    Samvirkni við önnur ósamstillt mynstur og gerðir

Uppfærsla: Þakka þér fyrir borgarstjóri и sidristij fyrir hugmyndina um að bæta verkefni við viðmótið. Samhæfni bókasafnsins við gömul stýrikerfi er ekki brotin, vegna þess að Fjórða ramminn styður bæði XP og 4 miðlara.

Heimild: www.habr.com

Bæta við athugasemd