Implementation of the Reliable Udp protocol for .Net

Internet on juba ammu muutunud. Üks peamisi Interneti protokolle – UDP, mida kasutatakse rakendustes mitte ainult andmegrammide ja levitamisringide edastamiseks, vaid ka „peer-to-peer“ ĂŒhenduste tagamiseks vĂ”rgus. Oma lihtsa struktuuri tĂ”ttu on sellel protokollil ilmunud palju mitteplaneeritud kasutusvĂ”imalusi, kuid protokolli puudused, nagu garantii puudumine andmete edastamisel, ei ole kuhugi kadunud. Selles artiklis kĂ€sitletakse garantii tagamise protokolli rakendamist UDP ĂŒle.
Sisukord:Sissejuhatus
Protokolli nÔuded
Reliable UDP pealkiri
Protokolli ĂŒldpĂ”himĂ”tted
Protokolli ajutised katkestused ja taimerid
Reliable UDP edastuse olekute diagramm
SĂŒveneme koodi. Edastuse juhtimisplokk
SĂŒveneme koodi. Oleku jĂ€lgimine

SĂŒveneme koodi. Ühenduste loomine ja seadistamine
SĂŒveneme koodi. Ühenduse sulgemine ajutise katkestuse tĂ”ttu
SĂŒveneme koodi. Andmeedastuse taastamine
Reliable UDP API
KokkuvÔte
Kasulikud lingid ja artiklid

Sissejuhatus

Algne Interneti arhitektuur eeldas ĂŒhtlast aadressiruumi, kus igal sĂ”lmel oli globaalne ja ainulaadne IP-aadress ning ta sai otse suhelda teiste sĂ”lmede vahel. Praegu on Internetil tegelikult erinev arhitektuur – ĂŒks globaalne IP-aadresside ruum ja palju privaatsete aadressidega piirkondi, mis on NAT-seadmete tagant peidetud.Selles arhitektuuris saavad globaalses aadressiruumis olevad seadmed hĂ”lpsasti suhelda kellegagi Internetis, kuna neil on ainulaadne, globaalne marsruutitav IP-aadress. Erasektoris asuv sĂ”lm saab ĂŒhendada teiste sĂ”lmedega samas vĂ”rgus, samuti olla ĂŒhenduses teiste hĂ€sti tuntud sĂ”lmedega globaalses aadressiruumis. Selline interaktsioon on vĂ”imalik suuresti tĂ€nu vĂ”rguaadresside konversioonimehhanismile. NAT-seadmed, nĂ€iteks Wi-Fi marsruuterid, loovad spetsiaalsed kirjed edastamislaudadesse vĂ€ljaminevate ĂŒhenduste jaoks ja muudavad IP-aadresse ja sadamate numbreid paketis. See vĂ”imaldab moodustada erasektorist vĂ€ljamineva ĂŒhenduse globaalses aadressiruumis olevate sĂ”lmedega. Samas blokeerivad NAT-seadmed tavaliselt kogu sissetuleva liikluse, vĂ€lja arvatud juhul, kui sissetulevatele ĂŒhendustele on kehtestatud erireeglid.

Selline Interneti arhitektuur on ĂŒsna Ă”ige kliendi-serveri interaktsioonide jaoks, kus kliendid vĂ”ivad asuda erasektorites, samas kui serveritel on globaalne aadress. Kuid see tekitab raskusi kahe sĂ”lme otseseks ĂŒhendamiseks erinevate erasektorite vahel. Kahe sĂ”lme vaheline otsene ĂŒhendus on oluline „peer-to-peer” rakenduste jaoks, nagu hÀÀlkĂ”ne edastamine (Skype), kaugarvutile ligipÀÀsemine (TeamViewer) vĂ”i veebimĂ€ngud.

Üks tĂ”husamaid meetodeid peer-to-peer ĂŒhenduse loomisel erinevates erasektorites asuvate seadmete vahel nimetatakse „hole punching”. Seda tehnikat kasutatakse kĂ”ige sagedamini UDP-protokolliga rakendustes.

Kuid kui teie rakendus nÔuab andmete garanteeritud kohaletoimetamist, nÀiteks kui edastate faile arvutite vahel, siis esinevad UDP kasutamisel mitmed raskused, kuna UDP ei ole garanteeritud kohaletoimetamise protokoll ega tagada pakettide kohaletoimetamist jÀrjekorras, erinevalt TCP protokollist.

Sellisel juhul, et tagada pakettide garanteeritud kohaletoimetamine, on vajalik rakendada rakendustaseme protokoll, mis tagab vajaliku funktsionaalsuse ja töötab UDP peal.

Esimene asi, mida ma tahan mĂ€rkida, on see, et eksisteerib TCP hole punching tehnik, et luua TCP ĂŒhendusi erinevates privaatvĂ”rkudes, kuid paljude NAT-seadmete toetuse puudumise tĂ”ttu ei kĂ€sitleta seda tavaliselt peamise ĂŒhendusviisina nende sĂ”lmede vahel.

KÀesolevas artiklis kÀsitlen ainult garantii edastusprotokolli rakendust. UDP hole punching tehnika rakendamine kÀsitletakse jÀrgmistes artiklites.

Protokolli nÔuded

  1. UsaldusvÀÀrne pakettide edastus, mis on ellu viidud positiivse tagasiside mehhanismi kaudu (nn positiivne kinnitamine)
  2. On vajalik suuri andmeid tÔhusalt edastada, st protokoll peab vÀltima liigseid pakettide edastamisi.
  3. Peab olema vĂ”imalus tĂŒhistada edastuse kinnitamise mehhanism (vĂ”imalus funktsioneerida kui „puhas” UDP protokoll)
  4. VĂ”ime rakendada kĂ€skude reĆŸiimi, kinnitades iga sĂ”numi.
  5. Protokolli andmete peamiseks edastamise ĂŒksuseks peab olema sĂ”num.

Need nÔudmised kattuvad suuresti Reliable Data Protocoli nÔudmistega, mis on kirjeldatud rfc 908 ja rfc 1151, ja ma tuginesin nende standardite kohaselt selle protokolli arendamisel.

Nende nĂ”udmiste mĂ”istmiseks vaatame ajajooni andmete edastamise vahel kahe vĂ”rgu sĂ”lme vahel TCP ja UDP protokollide kaudu. Oletame, et mĂ”lemas olukorras kaotatakse ĂŒks pakett.
Eksitamatute andmete edastamine TCP kaudu:Implementation of the Reliable Udp protocol for .Net

Nagu diagrammist nÀha, tuvastab TCP pakettide kaotuse korral kaotatud paketi ja teavitab sellest saatjat, paludes kaotatud segmentide numbrit.
Andmete edastamine UDP protokolli kaudu:Implementation of the Reliable Udp protocol for .Net

UDP ei tee kaotuse tuvastamiseks mingeid samme. Veakontroll edastamisel UDP protokollis lasub tÀielikult rakendusel.

TCP protokolli veateave saavutatakse ĂŒhenduse loomise kaudu lĂ”pp-punktiga, selle ĂŒhenduse oleku hoidmise kaudu, iga paketi pĂ€ises saadetud baitide numbri nĂ€itamisega ja vastuvĂ”tmise teavitamisega kinnitamise numbri "kinnitada number" abil.

Lisaks kasutab TCP protokoll liikluse tĂ”hususe suurendamiseks (st rohkem kui ĂŒhe segmendi saatmiseks ilma kinnituse vastuvĂ”tmiseta) nn edastamisaken - andmete baitide arv, mida segmentide saatja ootab vastu vĂ”tmiseks.

TCP protokolliga saab lÀhemalt tutvuda rfc 793, UDP-ga rfc 768, kus need tÔepoolest mÀÀratakse.

Kuna eelnevalt kirjeldatud, on selge, et usaldusvÀÀrse sĂ”numi saatmise protokolli loomine UDP-i ĂŒle (edaspidi nimetame seda Reliable UDP), nĂ”uab TCP-sarnaste andmeedastusmehhanismide rakendamist. Nimelt:

  • sĂ€ilitada ĂŒhenduse olek
  • kasutada segmentide nummerdamist
  • kasutada spetsiaalseid kinnituspakette
  • kasutada lihtsustatud aknamehhanismi protokolli lĂ€bilaskevĂ”ime suurendamiseks

Lisaks on vajalik:

  • anda mĂ€rku sĂ”numite algusest, et eraldada ressursid ĂŒhenduse jaoks
  • anda mĂ€rku sĂ”numite lĂ”petamisest, et edastada saadud sĂ”num kĂ”rgemale rakendusele ja vabastada protokolli ressursid
  • lubada protokollil konkreetsetes ĂŒhendustes kinnitusmehhanismi vĂ€lja lĂŒlitada, et toimida nagu "puhtam" UDP

Reliable UDP pealkiri

Tuletame meelde, et UDP datagramm kapseldatakse IP datagrammi. Vastav Reliable UDP pakett "ĂŒmber pakitakse" UDP datagrammi.
Reliable UDP pÀise kapseldamine:Implementation of the Reliable Udp protocol for .Net

Reliable UDP pÀis on piisavalt lihtne:

Implementation of the Reliable Udp protocol for .Net

  • Flags – paketi juhtimislipud
  • MessageType – sĂ”numi tĂŒĂŒp, mida kasutatakse kĂ”rgemates rakendustes teatud sĂ”numite tellimiseks
  • TransmissionId – edastuse number, mis koos adressaadi ja sadamaga mÀÀratleb ĂŒhenduse unikaalselt
  • PacketNumber – paketi number
  • Options – protokolli lisavalikud. Esimese paketi puhul kasutatakse sĂ”numi suuruse nĂ€itamiseks

Flags vÔivad olla jÀrgmised:

  • FirstPacket — sĂ”numi esimene pakett
  • NoAsk — sĂ”num ei vaja kinnitusmehhanismi kasutuselevĂ”ttu
  • LastPacket — sĂ”numi viimane pakett
  • RequestForPacket — kinnituspakett vĂ”i kaotatud paketi nĂ”ue

Protokolli ĂŒldpĂ”himĂ”tted

Kuna Reliable UDP on suunatud sĂ”numi garantii andmisele kahe sĂ”lme vahel, peab see suutma luua ĂŒhenduse teise poolega. Ühenduse loomiseks saadab saatja paketi, millel on FirstPacket lipp, millele vastamine tĂ€hendab ĂŒhenduse loomist. KĂ”ik vastuspaketid vĂ”i teisisĂ”nu, kinnituspaketid, mÀÀravad alati PacketNumber vĂ€ljale vÀÀrtuse, mis on ĂŒks suurem kui suurim PacketNumber edukalt saabunud pakettidel. Esimese saadetud paketi Options vĂ€ljale kirjutatakse sĂ”numi suurus.

Selle ĂŒhenduse lĂ”petamiseks kasutatakse sarnast mehhanismi. Viimases sĂ”numipakendis on seatud lipp LastPacket. Vastupakendis on nĂ€idatud viimase pakendi number + 1, mis tĂ€hendab vastuvĂ”tva poole jaoks sĂ”numi edukat kohaletoimetamist.
Diagramm ĂŒhenduse loomise ja lĂ”petamise kohta:Implementation of the Reliable Udp protocol for .Net

Kui ĂŒhendus on loodud, algab andmete edastamine. Andmed edastatakse pakettide plokkidena. Igas plokis, vĂ€lja arvatud viimane, on fikseeritud arv pakette. See on vĂ”rdne vastuvĂ”tu/edastuse akna suurusega. Viimasel andmeplokil vĂ”ib olla vĂ€hem pakette. Iga ploki saatmise jĂ€rel ootab saatja teate kohalejĂ”udmise kinnitust vĂ”i nĂ”uet kaotatud paketide uuesti edastamiseks, jĂ€ttes vastuste vastuvĂ”tmiseks vastuvĂ”tu/edastuse akna avatuks. PĂ€rast edastusploki kohaletoimetamise kinnituse saamist nihkub vastuvĂ”tu/edastuse aken ja saadetakse jĂ€rgmine andmeplokk.

VastuvÔtja pool saab pakette. Iga pakett kontrollitakse, kas see kuulub edastamise aknasse. Akna vÀlja langenud paketid ja kopeeritud paketid tÔrjutakse. Kuna akna suurus on rangelt fikseeritud ja sama nii vastuvÔtja kui saatja jaoks, siis juhul, kui pakettide plokk on edastatud kaotusteta, nihkub aken jÀrgmise andmeploki pakettide vastuvÔtmiseks ning saadetakse kohaletoimetamise kinnitus. Kui aken ei tÀitu kindlaksmÀÀratud tööajast, siis kÀivitatakse kontroll, millised paketid pole kohale jÔudnud, ja saadetakse taotlused nende uuesti edastamiseks.
Diagramm uuesti edastamiseks:Implementation of the Reliable Udp protocol for .Net

Protokolli ajutised katkestused ja taimerid

On mitmeid pĂ”hjuseid, miks ĂŒhendust ei pruugi luua. NĂ€iteks kui vastuvĂ”tva poole vĂ”rk on maas. Sellisel juhul, kui proovitakse luua ĂŒhendust, suletakse see ajapiirangust tingituna. Reliable UDP teostuses kasutatakse ajapiirangute seadmiseks kahte taimerit. Esimene, töötaimer, teenib vastuse ootamiseks eemalolevalt hostilt. Kui see aktiveeritakse saatja poolel, saadetakse viimane saadetud pakett uuesti. Kui aga taimer aktiveeritakse vastuvĂ”tja poolel, kontrollitakse kadunud pakette ja saadetakse taotlused nende uuesti edastamiseks.

Teine taimer on vajalik ĂŒhenduse lĂ”petamiseks juhul, kui sĂ”lmede vahel pole ĂŒhendust. Saatja poolel kĂ€ivitub see kohe pĂ€rast töötaimeri aktiveerimist ning ootab vastust eemalolevalt sĂ”lmelt. Kui vastust ei tule ettenĂ€htud aja jooksul, lĂ”petatakse ĂŒhendus ja vabastatakse ressursid. VastuvĂ”tja poolel kĂ€ivitub ĂŒhenduse lĂ”petamise taimer pĂ€rast töötaimeri kahekordset aktiveerimist. See on vajalik, et kaitsta kinnituse paketi kadumise eest. Taimeri aktiveerumisel lĂ”petatakse samuti ĂŒhendus ja vabastatakse ressursid.

Reliable UDP edastuse olekute diagramm

Protokolli tööpÔhimÔtted on rakendatud lÔppautomaatides, mille iga olek vastutab teatud pakettide töötlemise loogika eest.
Reliable UDP olekudiagramm:

Implementation of the Reliable Udp protocol for .Net

Suletud – ei ole tegelikult olek, see on automaadi algus- ja lĂ”pp-punkt. Oleku Suletud vĂ”tab juhtimisplokk, mis, rakendades asĂŒnkroonset UDP serverit, suunab paketid vastavatesse ĂŒhendustesse ja kĂ€ivitab olekute töötlemise.

EsimesePaketiSaatmine – algolek, kus asub vĂ€ljuv ĂŒhendus, kui saadetakse sĂ”num.

Selles olekus saadetakse esimene pakett tavaliste sĂ”numite jaoks. Kinnituse saatmist mittesisaldavate sĂ”numite puhul on see ainus olek – selles toimib kogu sĂ”numi saatmine.

SaatmisTsĂŒkkel – peamine olek sĂ”numi pakettide edastamiseks.

Üleminek sellesse olekusse toimub seisundist EsimesePaketiSaatmine pĂ€rast esimese paketiga sĂ”numi saatmist. Just sellesse olekusse jĂ”uavad kĂ”ik kinnitused ja taotlused edasiste edastuste kohta. Sellest olekust vĂ”ib vĂ€lja minna kahes olukorras – sĂ”numi edukal kohaletoimetamisel vĂ”i ajautuse tĂ”ttu.

EsimesePaketiSaamine – algolek sĂ”numi saajale.

Selles kontrollitakse edastamise alguse Ôigust, luuakse vajalikud struktuurid ja saadetakse kinnitamine esimese paketi vastuvÔtmise kohta.

Üheainsa paketiga sĂ”numi puhul, mis saadeti ilma kohaletoimetamise kinnituse kasutamiseta – see on ainus olek. PĂ€rast sellise sĂ”numi töötlemist lĂ”petatakse ĂŒhendus.

KokkuPanemine – peamine olek sĂ”numi pakettide vastuvĂ”tmiseks.

Selle salvestatakse pakette ajutisse mĂ€llu, kontrollitakse kaotsimineku puudumist, saadetakse kinnitused pakettide bloki ja sĂ”numi edastamise kohta ning saadetakse taotlused kaotatud pakettide uuesti edastamiseks. Kui kogu sĂ”num on edukalt saadud, lĂ€heb ĂŒhendus olekusse Completed, vastasel juhul toimub ajapiirangust vĂ€ljumine.

Completed – ĂŒhenduse lĂ”petamine kogu sĂ”numi edukal saamisel.

See olek on vajalik sĂ”numi koostamiseks ja juhul, kui sĂ”numi edastamise kinnitus on saatjale teel kaduma lĂ€inud. Selle oleku lĂ”petamine toimub ajapiirangust, kuid ĂŒhendust peetakse edukalt suletuks.

SĂŒveneme koodi. Edastuse juhtimisplokk

Üks olulisemaid elemente Reliable UDP-s on edastuskontrolli plokk. Selle ploki ĂŒlesanne on sĂ€ilitada praeguseid ĂŒhendusi ja abielemente, jagada saabunud pakette vastavatesse ĂŒhendustesse, anda liidese pakettide saatmiseks ning rakendada protokolli API-d. Edastuskontrolli plokk vĂ”tab pakette UDP tasemelt ja suunab need töötlemiseks lĂ”puma automaadile. Pakettide vastuvĂ”tmiseks on seal rakendatud asĂŒnkroonne UDP server.
MÔned ReliableUdpConnectionControlBlocki klassi liikmed:

internal class ReliableUdpConnectionControlBlock : IDisposable
{
  // baidi massiiv spetsifitseeritud vÔtme jaoks. Kasutatakse sisenevate sÔnumite koostamiseks
  public ConcurrentDictionary<Tuple<EndPoint, Int32>, byte[]> IncomingStreams { get; private set;}
  // baidi massiiv spetsifitseeritud vÔtme jaoks. Kasutatakse vÀliste sÔnumite saatmiseks.
  public ConcurrentDictionary<Tuple<EndPoint, Int32>, byte[]> OutcomingStreams { get; private set; }
  // ĂŒhenduse kirje spetsifitseeritud vĂ”tme jaoks.
  private readonly ConcurrentDictionary<Tuple<EndPoint, Int32>, ReliableUdpConnectionRecord> m_listOfHandlers;
  // sÔnumite tellijate loend.
  private readonly List<ReliableUdpSubscribeObject> m_subscribers;
  // kohalik soket
  private Socket m_socketIn;
  // port sisenevate sÔnumite jaoks
  private int m_port;
  // kohalik IP-aadress
  private IPAddress m_ipAddress;
  // kohalik lÔpp-punkt
  public IPEndPoint LocalEndpoint { get; private set; }
  // eelnevalt initsialiseeritud
  // automaadi olekute kogu
  public StatesCollection States { get; private set; }
  // juhuslike numbrite generaator. Kasutatakse TransmissionId loomisel
  private readonly RNGCryptoServiceProvider m_randomCrypto;
  	//...
}

AsĂŒnkroonse UDP serveri rakendamine:

private void Receive()
{
  EndPoint connectedClient = new IPEndPoint(IPAddress.Any, 0);
  // Loome uus puhver iga socket.BeginReceiveFrom jaoks 
  byte[] buffer = new byte[DefaultMaxPacketSize + ReliableUdpHeader.Length];
  // Edastame puhversisendi anonĂŒĂŒmse meetodi parameetrina
  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);
  // Paket on saadud, valmis jÀrgmist vastu vÔtma        
  Receive();
  // Kuna lihtsaim viis puhversisendi kĂŒsimuse lahendamiseks - saada sellele viide 
  // IAsyncResult.AsyncState kaudu        
  byte[] bytes = ((byte[]) ar.AsyncState).Slice(0, bytesRead);
  // Saame paketi pÀise        
  ReliableUdpHeader header;
  if (!ReliableUdpStateTools.ReadReliableUdpHeader(bytes, out header))
  {          
    // Saime vale paketi - loobume sellest
    return;
  }
  // Konstruktsioon vÔtme jaoks, et mÀÀrata paketi jaoks connection record
  Tuple key = new Tuple(connectedClient, header.TransmissionId);
  // Saame olemasoleva connection recordi vÔi loome uue
  ReliableUdpConnectionRecord record = m_listOfHandlers.GetOrAdd(key, new ReliableUdpConnectionRecord(key, this, header.ReliableUdpMessageType));
  // KÀivitame paketi töötlemise lÔppautomaatis
  record.State.ReceivePacket(record, header, bytes);
}

Iga sĂ”numi edastamiseks luuakse struktuur, mis sisaldab teavet ĂŒhenduse kohta. Seda struktuuri nimetatakse connection record.
MÔned ReliableUdpConnectionRecord klassi liikmed:

internal class ReliableUdpConnectionRecord : IDisposable
{    
  // baitide massiiv sÔnumiga    
  public byte[] IncomingStream { get; set; }
  // viide lÔppautomaatide olekule    
  public ReliableUdpState State { get; set; }    
  // paar, mis mÀÀratleb ĂŒhenduse kirje ĂŒheselt
  // edastamise juhtimise plokis     
  public Tuple Key { get; private set;}
  // vastuvÔtva akna alumine piir    
  public int WindowLowerBound;
  // edastusakna suurus
  public readonly int WindowSize;     
  // edastatav paketi number
  public int SndNext;
  // edastatavate paketide arv
  public int NumberOfPackets;
  // edastamise number (see on teine osa Tuple'st)
  // iga sÔnumi jaoks oma	
  public readonly Int32 TransmissionId;
  // kaugklient – sĂ”numi saaja
  public readonly IPEndPoint RemoteClient;
  // paketi suurus, et vĂ€ltida tĂŒkkideks minekut IP tasemel
  // ei tohi ĂŒletada MTU – (IP.Header + UDP.Header + RelaibleUDP.Header)
  public readonly int BufferSize;
  // edastamise juhtimise plokk
  public readonly ReliableUdpConnectionControlBlock Tcb;
  // kapseldab asĂŒnkroonse toimingu tulemusi BeginSendMessage/EndSendMessage jaoks
  public readonly AsyncResultSendMessage AsyncResult;
  // mitte saata kinnituse pakette
  public bool IsNoAnswerNeeded;
  // viimane Ôigesti saadud pakett (alati seadistatakse suurim number)
  public int RcvCurrent;
  // massiiv kadunud pakettide numbritega
  public int[] LostPackets { get; private set; }
  // kas viimane pakett on saabunud. Kasutatakse nagu bool.
  public int IsLastPacketReceived = 0;
  //...
}

SĂŒveneme koodi. Oleku jĂ€lgimine

Olekud rakendavad Reliable UDP protokolli lÔppautomaatide, kus toimub pakettide pÔhitöötlemine. Abstraktne klass ReliableUdpState pakub liidest oleku jaoks:

Implementation of the Reliable Udp protocol for .Net

Protokolli kogu loogika töötab ĂŒlaltoodud klasside kaudu, koos abiklassiga, mis pakub staatilisi meetodeid, nagu nĂ€iteks ReliableUdp pealkirja koostamine ĂŒhenduse kirjest.

Edasi arutatakse liidese meetodite rakendusi, mis mÀÀratlevad protokolli pÔhialgoritmid.

Meetod DisposeByTimeout

Meetod DisposeByTimeout vastutab ĂŒhenduse ressursside vabastamise eest pĂ€rast ajutöö lĂ”ppemist ning edastamise edukuse/ebaĂ”nnestumise signalisimise eest.
ReliableUdpState.DisposeByTimeout:

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

See on ĂŒle mÀÀratud ainult olekus Completed.
Completed.DisposeByTimeout:

protection void DisposeByTimeout(object record)
{
  ReliableUdpConnectionRecord connectionRecord = (ReliableUdpConnectionRecord) record;
  // teavitame, et sÔnum on edukalt vastuvÔetud
  SetAsCompleted(connectionRecord);
}

Meetod ProcessPackets

Meetod ProcessPackets vastutab ĂŒksiku paketi vĂ”i paketihulga tĂ€iendava töötlemise eest. Kutsutakse vĂ€lja otse vĂ”i paketide ooteaja kaudu.

olekus KokkuPanemine meetod on ĂŒle mÀÀratud ja vastutab kaotatud paketide kontrollimise ning olekusse ĂŒlemineku eest Completed, juhul kui viimane pakett on vastu vĂ”etud ja edukas kontroll on lĂ€bitud
Assembling.ProcessPackets:

public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
  if (connectionRecord.IsDone != 0)
    return;
  if (!ReliableUdpStateTools.CheckForNoPacketLoss(connectionRecord, connectionRecord.IsLastPacketReceived != 0))
  {
    // kaotatud paketid olemas, saadame nende kohta pÀringud
    foreach (int seqNum in connectionRecord.LostPackets)
    {
      if (seqNum != 0)
      {
        ReliableUdpStateTools.SendAskForLostPacket(connectionRecord, seqNum);
      }
    }
    // seadistame taimeri teist korda edastamise kordamiseks
    if (!connectionRecord.TimerSecondTry)
    {
      connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
      connectionRecord.TimerSecondTry = true;
      return;
    }
    // kui kahe WaitForPacketTimer aktiveerimise katse jÀrel
    // ei Ă”nnestunud pakette kĂ€tte saada - kĂ€ivitame ĂŒhenduse sulgemise taimeri
    StartCloseWaitTimer(connectionRecord);
  }
  else if (connectionRecord.IsLastPacketReceived != 0)
  // edukas kontroll
  {
    // saadame andmepaketi kÀtte saamise kinnituse
    ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
    connectionRecord.State = connectionRecord.Tcb.States.Completed;
    connectionRecord.State.ProcessPackets(connectionRecord);
    // asemel kohese ressursside elluviimisega
    // kÀivitame taimeri, juhuks kui
    // kui viimane kinnitamine ei jĂ”ua saatjani ja see kĂŒsib seda uuesti.
    // taimeri aktiveerimisel - ellu viime ressursid
    // olekus Completed on taimeri meetod ĂŒle mÀÀratud
    StartCloseWaitTimer(connectionRecord);
  }
  // see on olukord, kus kinnitamine mitme paketiga kadus
  else
  {
    if (!connectionRecord.TimerSecondTry)
    {
      ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
      connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
      connectionRecord.TimerSecondTry = true;
      return;
    }
    // kĂ€ivitame ĂŒhenduse sulgemise taimeri
    StartCloseWaitTimer(connectionRecord);
  }
}

olekus SaatmisTsĂŒkkel see meetod kutsutakse vĂ€lja ainult taimeri kaudu ja vastutab viimase sĂ”numi uuesti saatmise ning ĂŒhenduse sulgemise taimeri kĂ€ivitamise eest.
SendingCycle.ProcessPackets:

public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
  if (connectionRecord.IsDone != 0)
    return;
  
  // saatke uuesti viimane pakett 
  // (ĂŒhenduse taastamisel saadab vastuvĂ”tja uuesti kĂŒsitlused, mis temani ei jĂ”udnud) 
  ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.RetransmissionCreateUdpPayload(connectionRecord, connectionRecord.SndNext - 1));
  // aktiveerime CloseWait taimeri – ĂŒhenduse taastamise vĂ”i lĂ”petamise ootamiseks
  StartCloseWaitTimer(connectionRecord);
}

olekus Completed meetod peatab töötaimeri ja edastab sÔnumi tellijatele.
Completed.ProcessPackets:

public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
  if (connectionRecord.WaitForPacketsTimer != null)
    connectionRecord.WaitForPacketsTimer.Dispose();
  // kogume sÔnumi ja edastame selle tellijatele
  ReliableUdpStateTools.CreateMessageFromMemoryStream(connectionRecord);
}

Meetod ReceivePacket

olekus EsimesePaketiSaamine meetodi pÔhieesmÀrk on mÀÀrata, kas tÔeliselt esimene sÔnumi pakett jÔudis liidesesse, samuti koguda sÔnum, mis koosneb ainusest paketist.
FirstPacketReceived.ReceivePacket:

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  if (!header.Flags.HasFlag(ReliableUdpHeaderFlags.FirstPacket))
    // viska pakett
    return;
  // kahe lipu kombinatsioon - FirstPacket ja LastPacket - ĂŒtleb, et meil on ainus sĂ”num
  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))
    {
      // saadame kinnituspaketi          
      ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
    }
    SetAsCompleted(connectionRecord);
    return;
  }
  // disaini tÔttu algavad kÔik paketinumbrid 0-st;
  if (header.PacketNumber != 0)          
    return;
  ReliableUdpStateTools.InitIncomingBytesStorage(connectionRecord, header);
  ReliableUdpStateTools.WritePacketData(connectionRecord, header, payload);
  // loeme kokku pakettide arvu, mis peavad saabuma
  connectionRecord.NumberOfPackets = (int)Math.Ceiling((double) ((double) connectionRecord.IncomingStream.Length/(double) connectionRecord.BufferSize));
  // salvestame viimase saadud paketi numbri (0)
  connectionRecord.RcvCurrent = header.PacketNumber;
  // seejÀrel liigutame vastuvÔtu akna 1 vÔrra edasi
  connectionRecord.WindowLowerBound++;
  // vahetame olekut
  connectionRecord.State = connectionRecord.Tcb.States.Assembling;
  // kui pole vaja kinnitusmehhanismi
  // kÀivitame taimeri, mis vabastab kÔik struktuurid         
  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);
  }
}

olekus SaatmisTsĂŒkkel See meetod on ĂŒle kirjutatud kohaletoimetamise kinnituste ja kordusnĂ”uete vastuvĂ”tmiseks.
SendingCycle.ReceivePacket:

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  if (connectionRecord.IsDone != 0)
    return;
  if (!header.Flags.HasFlag(ReliableUdpHeaderFlags.RequestForPacket))
    return;
  // lÔpp-aken piiri arvutamine
  // akna piir + 1, et saada vastuvÔtmise kinnitusi
  int windowHighestBound = Math.Min((connectionRecord.WindowLowerBound + connectionRecord.WindowSize), (connectionRecord.NumberOfPackets));
  // kontrollimine aknasse mahtumise osas
  if (header.PacketNumber  windowHighestBound)
    return;
  connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
  if (connectionRecord.CloseWaitTimer != null)
    connectionRecord.CloseWaitTimer.Change(-1, -1);
  // viimane pakett kontrollimine:
  if (header.PacketNumber == connectionRecord.NumberOfPackets)
  {
    // edastus on lÔpetatud
    Interlocked.Increment(ref connectionRecord.IsDone);
    SetAsCompleted(connectionRecord);
    return;
  }
  // see on esimese paketi vastus koos kinnitusega
  if ((header.Flags.HasFlag(ReliableUdpHeaderFlags.FirstPacket) && header.PacketNumber == 1))
  {
    // ilma akna nihketa
    SendPacket(connectionRecord);
  }
  // kinnituse saamine andmeploki vastuvÔtmise kohta
  else if (header.PacketNumber == windowHighestBound)
  {
    // nihutame vastuvÔtu/edastuse akent
    connectionRecord.WindowLowerBound += connectionRecord.WindowSize;
    // nullime edastuse kontrolli massiivi
    connectionRecord.WindowControlArray.Nullify();
    // saadame pakettide ploki
    SendPacket(connectionRecord);
  }
  // see on taotlus korduvaks edastamiseks – saadame vajaliku paketi
  else
    ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.RetransmissionCreateUdpPayload(connectionRecord, header.PacketNumber));
}

olekus KokkuPanemine Meetodis ReceivePacket toimub peamine töö, kogum sÔnumit saabuvate pakettide pÔhjal.
Assembling.ReceivePacket:

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  if (connectionRecord.IsDone != 0)
    return;
  // pakettide töötlemine ilma kinnitusmehhanismi
  if (header.Flags.HasFlag(ReliableUdpHeaderFlags.NoAsk))
  {
    // taastame taimeri
    connectionRecord.CloseWaitTimer.Change(connectionRecord.LongTimerPeriod, -1);
    // salvestame andmed
    ReliableUdpStateTools.WritePacketData(connectionRecord, header, payload);
    // kui saime paketi viimase lipuga - lÔpetame
    if (header.Flags.HasFlag(ReliableUdpHeaderFlags.LastPacket))
    {
      connectionRecord.State = connectionRecord.Tcb.States.Completed;
      connectionRecord.State.ProcessPackets(connectionRecord);
    }
    return;
  }
  // akna lÔpppiiri arvutamine
  int windowHighestBound = Math.Min((connectionRecord.WindowLowerBound + connectionRecord.WindowSize - 1), (connectionRecord.NumberOfPackets - 1));
  // kÔrvaldame aknasse mitte kuuluvad paketid
  if (header.PacketNumber  (windowHighestBound))
    return;
  // eemaldame dubleeritud paketid
  if (connectionRecord.WindowControlArray.Contains(header.PacketNumber))
    return;
  // salvestame andmed 
  ReliableUdpStateTools.WritePacketData(connectionRecord, header, payload);
  // suurendame paketihulka
  connectionRecord.PacketCounter++;
  // salvestame akna juhtimise massiivi praeguse paketi numbri
  connectionRecord.WindowControlArray[header.PacketNumber - connectionRecord.WindowLowerBound] = header.PacketNumber;
  // seadistame suurima saabunud paketi
  if (header.PacketNumber > connectionRecord.RcvCurrent)
    connectionRecord.RcvCurrent = header.PacketNumber;
  // taastame taimerid
  connectionRecord.TimerSecondTry = false;
  connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
  if (connectionRecord.CloseWaitTimer != null)
    connectionRecord.CloseWaitTimer.Change(-1, -1);
  // kui viimane pakett on saabunud
  if (header.Flags.HasFlag(ReliableUdpHeaderFlags.LastPacket))
  {
    Interlocked.Increment(ref connectionRecord.IsLastPacketReceived);
  }
  // kui saime kÔik akna paketid, siis taastame loenduri
  // ja saadame kinnituspaketi
  else if (connectionRecord.PacketCounter == connectionRecord.WindowSize)
  {
    // taastame loenduri.
    connectionRecord.PacketCounter = 0;
    // nihutasime ĂŒlekande akent
    connectionRecord.WindowLowerBound += connectionRecord.WindowSize;
    // nulime edastamise juhtimise massiivi
    connectionRecord.WindowControlArray.Nullify();
    ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
  }
  // kui viimane pakett on juba olemas
  if (Thread.VolatileRead(ref connectionRecord.IsLastPacketReceived) != 0)
  {
    // kontrollime pakette
    ProcessPackets(connectionRecord);
  }
}

olekus Completed meetodi ainus ĂŒlesanne on saata korduv kinnitus sĂ”numi eduka kohaletoimetamise kohta.
Completed.ReceivePacket:

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  // viimase paketi uuesti saatmine, kuna
  // viimane ack ei jÔudnud saatjasse
  if (header.Flags.HasFlag(ReliableUdpHeaderFlags.LastPacket))
  {
    ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
  }
}

Meetod SendPacket

olekus EsimesePaketiSaatmine see meetod saadab esimese andmepaketi vÔi, kui sÔnum ei vaja kohaletoimetamise kinnitust, siis kogu sÔnumi.
FirstPacketSending.SendPacket:

public override void SendPacket(ReliableUdpConnectionRecord connectionRecord)
{
  connectionRecord.PacketCounter = 0;
  connectionRecord.SndNext = 0;
  connectionRecord.WindowLowerBound = 0;
  // kui kinnitust ei nÔuta - saadame kÔik paketid
  // ja vabastame ressursid
  if (connectionRecord.IsNoAnswerNeeded)
  {
    // Siin toimub saatmine As Is
    do
    {
      ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.CreateUdpPayload(connectionRecord, ReliableUdpStateTools.CreateReliableUdpHeader(connectionRecord)));
      connectionRecord.SndNext++;
    } while (connectionRecord.SndNext < connectionRecord.NumberOfPackets);
    SetAsCompleted(connectionRecord);
    return;
  }
  // loome paketi pealkirja ja saadame selle
  ReliableUdpHeader header = ReliableUdpStateTools.CreateReliableUdpHeader(connectionRecord);
  ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.CreateUdpPayload(connectionRecord, header));
  // suurendame loenduri
  connectionRecord.SndNext++;
  // nihutame akent
  connectionRecord.WindowLowerBound++;
  connectionRecord.State = connectionRecord.Tcb.States.SendingCycle;
  // KĂ€ivitame taimeri
  connectionRecord.WaitForPacketsTimer = new Timer(CheckByTimer, connectionRecord, connectionRecord.ShortTimerPeriod, -1);
}

olekus SaatmisTsĂŒkkel selles meetodis toimub pakettide bloki saatmine.
SendingCycle.SendPacket:

public override void SendPacket(ReliableUdpConnectionRecord connectionRecord)
{      
  // saadame pakettide ploki      
  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++;
  }
  // suure edastusakna korral taaskÀivitame taimeri pÀrast saatmist
  connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
  if (connectionRecord.CloseWaitTimer != null)
  {
    connectionRecord.CloseWaitTimer.Change(-1, -1);
  }
}

SĂŒveneme koodi. Ühenduste loomine ja seadistamine

NĂŒĂŒd, kui me oleme tuttavad pĂ”hiseisundite ja meetoditega, mida kasutatakse seisundite töötlemiseks, saame natuke pĂ”hjalikumalt analĂŒĂŒsida mĂ”nda protokolli töö nĂ€idet.
Andmete edastamise diagramm normaalses keskkonnas:Implementation of the Reliable Udp protocol for .Net

Vaatame lĂ€hemalt loomist connection record ĂŒhendamiseks ja esimese paketi saatmiseks. Algatajana tegutseb alati rakendus, mis kutsub API meetodit sĂ”numi saatmiseks. SeejĂ€rel rakendatakse edastamise juhtimisploki meetodit StartTransmission, mis kĂ€ivitab uue sĂ”numi andmete edastamise.
VĂ€lise ĂŒhenduse loomine:

private void StartTransmission(ReliableUdpMessage reliableUdpMessage, EndPoint endPoint, AsyncResultSendMessage asyncResult)
{
  if (m_isListenerStarted == 0)
  {
    if (this.LocalEndpoint == null)
    {
      throw new ArgumentNullException( "", "Peate kasutama parameetritega konstruktori vÔi kÀivitama kuulamise enne sÔnumi saatmist" );
    }
    // kÀivitame sissetulevate paketide töötlemise
    StartListener(LocalEndpoint);
  }
  // loome vÔti sÔnastikule, pÔhinedes EndPoint ja ReliableUdpHeader.TransmissionId
  byte[] transmissionId = new byte[4];
  // loome juhusliku numbri transmissionId
  m_randomCrypto.GetBytes(transmissionId);
  Tuple key = new Tuple(endPoint, BitConverter.ToInt32(transmissionId, 0));
  // loome uue kirje ĂŒhenduse jaoks ja kontrollime, 
  // kas selline number juba eksisteerib meie sÔnastikes
  if (!m_listOfHandlers.TryAdd(key, new ReliableUdpConnectionRecord(key, this, reliableUdpMessage, asyncResult)))
  {
    // kui eksisteerib – genereerime juhusliku numbri uuesti
    m_randomCrypto.GetBytes(transmissionId);
    key = new Tuple(endPoint, BitConverter.ToInt32(transmissionId, 0));
    if (!m_listOfHandlers.TryAdd(key, new ReliableUdpConnectionRecord(key, this, reliableUdpMessage, asyncResult)))
      // kui uuesti ei Ă”nnestunud – genereerime erandi
      throw new ArgumentException("Paari TransmissionId & EndPoint eksisteerib juba sÔnastikus");
  }
  // oleme kÀivitanud oleku töötlemiseks
  m_listOfHandlers[key].State.SendPacket(m_listOfHandlers[key]);
}

Esimese paketi saatmine (olek FirstPacketSending):

public override void SendPacket(ReliableUdpConnectionRecord connectionRecord)
{
  connectionRecord.PacketCounter = 0;
  connectionRecord.SndNext = 0;
  connectionRecord.WindowLowerBound = 0;       
  // ... 
  // loome paketi pÀise ja saadame selle
  ReliableUdpHeader header = ReliableUdpStateTools.CreateReliableUdpHeader(connectionRecord);
  ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.CreateUdpPayload(connectionRecord, header));
  // suurendame loendurit
  connectionRecord.SndNext++;
  // nihutame akent
  connectionRecord.WindowLowerBound++;
  // liigutame olekusse SendingCycle
  connectionRecord.State = connectionRecord.Tcb.States.SendingCycle;
  // KĂ€ivitame taimeri
  connectionRecord.WaitForPacketsTimer = new Timer(CheckByTimer, connectionRecord, connectionRecord.ShortTimerPeriod, -1);
}

PĂ€rast esimese paketi saatmist liigub saatja olekusse SaatmisTsĂŒkkel – oodake paketi kohaletoimetamise kinnitust.
Saajapoolne osapool, kasutades meetodit EndReceive, vÔtab vastu saadetud paketi, loob uue connection record ja edastab selle paketi, eelnevalt parsitud pÀisega, ReceivePacket meetodi töötlemiseks. EsimesePaketiSaamine
Ühenduse loomine vastuvĂ”tvas osas:

private void EndReceive(IAsyncResult ar)
{
  // ...
  // pakett on saadud
  // parsim paketipea
  ReliableUdpHeader header;
  if (!ReliableUdpStateTools.ReadReliableUdpHeader(bytes, out header))
  {          
    // saabus ebakorrekne pakett - loobume sellest
    return;
  }
  // koostame vĂ”tme, et mÀÀrata paketi ĂŒhenduse rekord
  Tuple key = new Tuple(connectedClient, header.TransmissionId);
  // saame olemasoleva ĂŒhenduse rekordi vĂ”i loome uue
  ReliableUdpConnectionRecord record = m_listOfHandlers.GetOrAdd(key, new ReliableUdpConnectionRecord(key, this, header.ReliableUdpMessageType));
  // kÀivitame paketi töötlemise lÔpliku automaadi
  record.State.ReceivePacket(record, header, bytes);
}

Esimese paketi vastuvÔtt ja kinnituse saatmine (olek FirstPacketReceived):

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  if (!header.Flags.HasFlag(ReliableUdpHeaderFlags.FirstPacket))
    // loobume paketist
    return;
  // ...
  // disainist tulenevalt kÔik paketide numbrid algavad 0-st;
  if (header.PacketNumber != 0)
    return;
  // initsialiseerime massiivi sÔnumi osade hoidmiseks
  ReliableUdpStateTools.InitIncomingBytesStorage(connectionRecord, header);
  // salvestame andmed paketisse massiivi
  ReliableUdpStateTools.WritePacketData(connectionRecord, header, payload);
  // arvutame, kui palju pakette peab saabuma
  connectionRecord.NumberOfPackets = (int)Math.Ceiling((double)((double)connectionRecord.IncomingStream.Length / (double)connectionRecord.BufferSize));
  // salvestame viimase saadud paketi numbri (0)
  connectionRecord.RcvCurrent = header.PacketNumber;
  // nihutame vastuvÔtuaken 1 vÔrra
  connectionRecord.WindowLowerBound++;
  // lĂŒlitame oleku
  connectionRecord.State = connectionRecord.Tcb.States.Assembling;
  if (/* kui kinnituse mehhanismi ei nÔuta */)
  // ...
  else
  {
    // saadame kinnituse
    ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
    connectionRecord.WaitForPacketsTimer = new Timer(CheckByTimer, connectionRecord, connectionRecord.ShortTimerPeriod, -1);
  }
}

SĂŒveneme koodi. Ühenduse sulgemine ajutise katkestuse tĂ”ttu

AjaĂŒletuste kĂ€sitlemine on oluline osa Reliable UDP-st. Vaatame nĂ€idet, kus vahepealses sĂ”lmes juhtus rike ja andmete edastamine mĂ”lemas suunas muutus vĂ”imatuks.
Ajastumise pĂ”hjal ĂŒhenduse sulgemise diagramm:Implementation of the Reliable Udp protocol for .Net

Nagu diagrammist nĂ€ha, aktiveeritakse saatjal tööaeg pĂ€rast pakettide ploki saatmist. See toimub SendPacket meetodis. SaatmisTsĂŒkkel.
Tööaja aktiveerimine (olek SendingCycle):

public override void SendPacket(ReliableUdpConnectionRecord connectionRecord)
{      
  // saadame pakettide ploki
  // ...
  // taaskÀivitame taimeri pÀrast saatmist
  connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
  if (connectionRecord.CloseWaitTimer != null)
    connectionRecord.CloseWaitTimer.Change(-1, -1);
}

Ajaintervalli mÀÀratakse yhteyden luontivaiheessa. Oletuksena ShortTimerPeriod on 5 sekuntia. EsimerkissÀ se on asetettu 1,5 sekuntiin.

SisÀisessÀ yhteydessÀ ajastin kÀynnistyy, kun viimeinen saapunut datapaketti on vastaanotettu, tÀmÀ tapahtuu ReceivePacket- menetelmÀssÀ. KokkuPanemine
Työajastimen aktivointi (tila Assembling):

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  // ... 
  // nollataan ajastimet
  connectionRecord.TimerSecondTry = false;
  connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
  if (connectionRecord.CloseWaitTimer != null)
    connectionRecord.CloseWaitTimer.Change(-1, -1);
  // ... 
}

SisÀisessÀ yhteydessÀ työajastimen odotusaikana ei tullut lisÀÀ paketteja. Ajastin aktivoitui ja kutsui menetelmÀÀ ProcessPackets, jossa havaittiin kadonneita paketteja ja ensimmÀistÀ kertaa lÀhetettiin pyyntöjÀ niiden uudelleenlÀhettÀmiseksi.
UudelleenlÀhetyspyyntöjen lÀhettÀminen (tila Assembling):

public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
  // ...
  if (/*tarkistus kadonneista paketeista */)
  {
    // lÀhetetÀÀn uudelleenlÀhetyspyyntöjÀ
    // asetetaan ajastin toista kertaa, lÀhetysyrittÀjÀlle
    if (!connectionRecord.TimerSecondTry)
    {
      connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
      connectionRecord.TimerSecondTry = true;
      return;
    }
    // jos kahden WaitForPacketTimer -yrityksen jÀlkeen
    // paketteja ei saatu - kÀynnistetÀÀn yhteyden sulkemisajastin
    StartCloseWaitTimer(connectionRecord);
  }
  else if (/*viimeinen paketti saapui ja tarkistus onnistui */)
  {
    // ...
    StartCloseWaitTimer(connectionRecord);
  }
  // jos ack pakettijoukolle menetettiin
  else
  {
    if (!connectionRecord.TimerSecondTry)
    {
      // uudelleen lÀhetetÀÀn ack
      connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
      connectionRecord.TimerSecondTry = true;
      return;
    }
    // kÀynnistetÀÀn yhteyden sulkemisajastin
    StartCloseWaitTimer(connectionRecord);
  }
}

Muuttuja TimerSecondTry asetettiin true. TÀmÀ muuttuja vastaa työajastimen uudelleen kÀynnistÀmisestÀ.

LÀhettÀjÀn puolella aktivoituu myös työajastin ja viimeinen lÀhetetty paketti lÀhetetÀÀn uudelleen.
Yhteyden sulkemisajastimen aktivointi (tila SendingCycle):

public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
  // ...        
  // uudelleen lÀhetetÀÀn viimeinen paketti 
  // ...        
  // kÀynnistetÀÀn CloseWait-ajastin - odotetaan yhteyden uudelleen muodostamista tai sen sulkemista
  StartCloseWaitTimer(connectionRecord);
}

PĂ€rast seda kĂ€ivitub lĂ€hteĂŒhenduses ĂŒhenduse sulgemise taimer.
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);
}

Ühenduse sulgemise taimeri ooteaeg on vaikimisi 30 sekundit.

Peagi kĂ€ivitub vastuvĂ”tja poolel uuesti töötaimer, saadetakse uuesti pĂ€ringud ning seejĂ€rel kĂ€ivitub sissetuleva ĂŒhenduse sulgemise taimer.

Taimerite sulgemisel vabastatakse kĂ”igi ĂŒhenduse recordide ressursid. Saaja teatab edutuks jÀÀmisest ĂŒlempoolsele rakendusele (vt API Reliable UDP).
Ühenduse recordi ressursside vabastamine:

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

SĂŒveneme koodi. Andmeedastuse taastamine

Paketikaotuse taastamise diagramm:Implementation of the Reliable Udp protocol for .Net

Nagu juba arutatud ĂŒhenduse sulgemisel ajaĂŒlesande jĂ€rgi, toimub vastuvĂ”tja poolel töötaimeri lĂ”ppemisel kaotatud pakettide kontroll. Kui pakette on kadunud, koostatakse nimekiri numbritest, mis ei ole vastuvĂ”tjani jĂ”udnud. Need numbrid kantakse konkreetse ĂŒhenduse LostPackets massiivi ja saadetakse taotlused kordusete saatmiseks.
Kordusete saatmise taotluste saatmine (olek Assembling):

public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
  //...
  if (!ReliableUdpStateTools.CheckForNoPacketLoss(connectionRecord, connectionRecord.IsLastPacketReceived != 0))
  {
    // kadunud paketid, saadame nende kohta taotlused
    foreach (int seqNum in connectionRecord.LostPackets)
    {
      if (seqNum != 0)
      {
        ReliableUdpStateTools.SendAskForLostPacket(connectionRecord, seqNum);
      }
    }
    // ...
  }
}

Saaja vĂ”tab vastu kordusetaotuse ja saadab puuduolevad paketid. Oluline on mĂ€rkida, et sel hetkel on saatjal juba kĂ€ivitatud ĂŒhenduse sulgemise taimer, mis taotluse saabumisel nullitakse.
Kaotatud pakettide kordus saatmine (olek SendingCycle):

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  // ...
  connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
  // lĂ€htestatud ĂŒhenduse sulgemise taimer 
  if (connectionRecord.CloseWaitTimer != null)
    connectionRecord.CloseWaitTimer.Change(-1, -1);
  // ...
  // see on taotlus paketiretranspordiks – saadame vajaliku paketi
  else
    ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.RetransmissionCreateUdpPayload(connectionRecord, header.PacketNumber));
}

Ümber saadetud pakett (packet#3 diagrammil) vĂ”etakse vastu sissetuleva ĂŒhenduse poolt. Kontrollitakse vastuvĂ”tuakna tĂ€ituvust ja tavapĂ€rane andmete edastus taastatakse.
Kontroll vastuvÔtuakna kohta (seisund Assembling):

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  // ...
  // suurendame paketisarvu        
  connectionRecord.PacketCounter++;
  // salvestame juhtimisse nn akna praeguse pakettnumbri        
  connectionRecord.WindowControlArray[header.PacketNumber - connectionRecord.WindowLowerBound] = header.PacketNumber;
  // mÀÀrame suurima vastuvÔetud paketi        
  if (header.PacketNumber > connectionRecord.RcvCurrent)
    connectionRecord.RcvCurrent = header.PacketNumber;
  // taimerite taaskÀivitamine        
  connectionRecord.TimerSecondTry = false;
  connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
  if (connectionRecord.CloseWaitTimer != null)
    connectionRecord.CloseWaitTimer.Change(-1, -1);
  // ...
  // kui kÔik akna paketid on vastuvÔetud, siis lÀhtestame arvu
  // ja saadame kinnituse paketi
  else if (connectionRecord.PacketCounter == connectionRecord.WindowSize)
  {
    // lÀhtestame arvu.
    connectionRecord.PacketCounter = 0;
    // edastusakna nihutamine
    connectionRecord.WindowLowerBound += connectionRecord.WindowSize;
    // edastamise juhtimisse arvu nullimist
    connectionRecord.WindowControlArray.Nullify();
    ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
  }
  // ...
}

Reliable UDP API

Andmeedastusprotokolli kasutamiseks on olemas avatud klass Reliable Udp, mis on juhtimisploki ĂŒmbris. Siin on klassi kĂ”ige olulisemad liikmed:

public sealed class ReliableUdp : IDisposable
{
  // saadab kohalik lÔpp-punkt
  public IPEndPoint LocalEndpoint    
  // loob ReliableUdp eksemplari ja kÀivitab
  // sissetulevate pakettide kuulamise mÀÀratud IP-aadressil
  // ja pordil. Port 0 tĂ€histab dĂŒnaamiliselt
  // jaotatud pordi kasutamist
  public ReliableUdp(IPAddress localAddress, int port = 0) 
  // registreerimine sissetulevate sÔnumite saamiseks
  public ReliableUdpSubscribeObject SubscribeOnMessages(ReliableUdpMessageCallback callback, ReliableUdpMessageTypes messageType = ReliableUdpMessageTypes.Any, IPEndPoint ipEndPoint = null)    
  // registreerimise tĂŒhistamine
  public void Unsubscribe(ReliableUdpSubscribeObject subscribeObject)
  // asĂŒnkroonne sĂ”numi saatmine 
  // TĂ€helepanu: xp ja Server 2003 ĂŒhilduvus sĂ€ilib, kuna kasutatakse .NET Framework 4.0
  public Task SendMessageAsync(ReliableUdpMessage reliableUdpMessage, IPEndPoint remoteEndPoint, CancellationToken cToken)
  // alustada asĂŒnkroonset sĂ”numi saatmist
  public IAsyncResult BeginSendMessage(ReliableUdpMessage reliableUdpMessage, IPEndPoint remoteEndPoint, AsyncCallback asyncCallback, Object state)
  // saada asĂŒnkroonse saatmise tulemus
  public bool EndSendMessage(IAsyncResult asyncResult)  
  // vabastada ressursid
  public void Dispose()    
}

SÔnumi saamine toimub registreerimise kaudu. Tagasi kutsumise meetodi delegeeritud allkiri:

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

SÔnum:

public class ReliableUdpMessage
{
  // sĂ”numi tĂŒĂŒp, lihtne loetelu
  public ReliableUdpMessageTypes Type { get; private set; }
  // sÔnumi andmed
  public byte[] Body { get; private set; }
  // kui seatud tÔeks - siis kÀtkeb kÀttesaamise kinnitamise mehhanism
  // konkreetse sÔnumi edastamiseks
  public bool NoAsk { get; private set; }
}

Konkreetse sĂ”numitĂŒĂŒbi ja/vĂ”i konkreetse saatja jaoks registreerimiseks kasutatakse kahte valikulist parameetrit: ReliableUdpMessageTypes messageType ja IPEndPoint ipEndPoint.

SĂ”numitĂŒĂŒbid:

public enum ReliableUdpMessageTypes : short
{ 
  // Iga
  Any = 0,
  // PĂ€ring STUN serverile 
  StunRequest = 1,
  // Vastus STUN serverilt
  StunResponse = 2,
  // FailiĂŒlekanne
  FileTransfer =3,
  // ...
}

SĂ”numi saatmine toimub asĂŒnkroonselt, selleks on protokollis rakendatud asĂŒnkroonne programmeerimismudel:

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

SĂ”numi saatmise tulemus on true – kui sĂ”num on edukalt jĂ”udnud saajani ja false – kui ĂŒhendus on aegunud:

public bool EndSendMessage(IAsyncResult asyncResult)

KokkuvÔte

Palju ei ole selle artikli raamides kirjeldatud. Voogude kokkuleppe mehhanismid, erandite ja vigade töötlemine, asĂŒnkroonsete sĂ”numi saatmise meetodite rakendamine. Kuid protokolli tuum, pakettide töötlemise loogika, ĂŒhenduse loomine ja ajaĂŒletuste kĂ€sitlemine peavad Teile selgeks saama.

KĂ€ideldud protokolli usaldusvÀÀrse edastamise versioon on piisavalt stabiilne ja paindlik ning vastab eelnevalt kindlaksmÀÀratud nĂ”uetele. Kuid soovin lisada, et kirjeldatud rakendust saab tĂ€iustada. NĂ€iteks edastamise mahutavuse suurendamiseks ja protokolli aja perioodide dĂŒnaamiliseks muutmiseks saab lisada selliseid mehhanisme nagu uksed ja RTT, samuti oleks kasulik rakendada MTU mÀÀramise mehhanismi ĂŒhenduspunktide vahel (kuid ainult suurte sĂ”numite saatmise korral).

AitÀh tÀhelepanu eest, ootan Teie kommentaare ja mÀrkusi.

P.S. Neile, kes huvituvad ĂŒksikasjadest vĂ”i soovivad lihtsalt protokolli testida, siin on link projektile GitHubis:
UsaldusvÀÀrne UDP projekt

Kasulikud lingid ja artiklid

  1. TCP protokolli spetsifikatsioon: inglise keeles ja eesti keeles
  2. UDP protokolli spetsifikatsioon: inglise keeles ja eesti keeles
  3. RUDP protokolli arutelu: draft-ietf-sigtran-reliable-udp-00
  4. UsaldusvÀÀrne andmeprotokoll: rfc 908 ja rfc 1151
  5. Lihtne UDP kohaletoimetamise kinnituse rakendamine: VÔtke oma vÔrgu tÀielik kontroll .NET ja UDP abil
  6. Artikkel, mis kirjeldab NAT-ide ĂŒletamise mehhanisme: Peer-to-Peer suhtlemine vĂ”rgu aadressi tĂ”lkijate kaudu
  7. AsĂŒnkroonse programmeerimismudeli rakendamine: CLR asĂŒnkroonse programmeerimismudeli rakendamine ja Kuidas rakendada IAsyncResult disainimustrit
  8. AsĂŒnkroonse programmeerimismudeli ĂŒleviimine ĂŒlesannete pĂ”hisele mallile (APM-st TAP-ks):
    TPL ja traditsiooniline .NET asĂŒnkroonne programmeerimine
    Üksikasjad teiste asĂŒnkroonsete mustrite ja tĂŒĂŒpide vahel

Uuendus: AitĂ€h mayorovp ja sidristij idee lisada ĂŒlesanne liidesele. Raamatukogu ĂŒhilduvus vanade opsĂŒsteemidega ei kannata, kuna 4. raamistik toetab nii XP-d kui ka 2003 serverit.

Allikas: habr.com

Osta usaldusvÀÀrne hostimine veebilehtede jaoks DDoS-i kaitsega, VPS VDS serverid đŸ”„ Osta usaldusvÀÀrne hostimine veebilehtede jaoks DDoS-i kaitsega, VPS VDS serverid | ProHoster