Reliable Udp protokolli rakendamine .Net jaoks

Internet on ammu muutunud. Üks peamisi Interneti protokolle — UDP, mida kasutatakse mitte ainult andmegrammide ja levitamise jaoks, vaid ka «peer-to-peer» ühenduste tagamiseks võrgu sõlmede vahel. Tänu oma lihtsale struktuurile on sellel protokollil ilmnenud palju ootamatuid kasutusviise, kuid protokolli puudused, nagu näiteks tagatud kohaletoimetamise puudumine, ei ole kuhugi kadunud. Selles artiklis käsitletakse tagatud kohaletoimetamise protokolli rakendamist UDP peal.
Sisu:Sissejuhatus
Protokolli nõuded
Reliable UDP päis
Protokolli üldpõhimõtted
Protokolli aegumised ja taimerid
Reliable UDP edastamise olekute diagramm
Sügavam koodi. Edastuse juhtimisplokk
Sügavam koodi. Oleku muutujad

Sügavam koodi. Ühenduste loomine ja seadistamine
Sügavam koodi. Ühenduse sulgemine aegumise põhjal
Sügavam koodi. Andmevoogude taastamine
Reliable UDP API
Kokkuvõte
Kasulikud lingid ja artiklid

Sissejuhatus

Algne Interneti arhitektuur tähendas ühtset aadressiruumi, kus igal sõlmel oli globaalne ja ainulaadne IP-aadress ning see sai otse suhelda teiste sõlmede vahel. Praegu on Internetil aga tegelikult teine arhitektuur – üks globaalsete IP-aadresside piirkond ja palju privaatsete aadressidega piirkondi, mis on NAT-seadmete taha peidetud.Sellises arhitektuuris saavad ainult need seadmed, mis asuvad globaalsetes aadressiruumi, hõlpsasti suhelda teistega võrgus, kuna neil on ainulaadne, globaalne marsruutitav IP-aadress. Privaatvõrgus asuv sõlm võib ühenduda teiste sama võrgu sõlmedega ja samuti ühenduda teiste hästi tuntud sõlmedega globaalsetes aadressiruumi. Selline suhtlemine saavutatakse suuresti võrguaadresside teisendamise mehhanismi kaudu. NAT-seadmed, nagu Wi-Fi marsruuterid, loovad spetsiaalsed kirjed edastamise tabelites väljaminevate ühenduste jaoks ja muudavad pakettides IP-aadresse ja pordinumbreid. See võimaldab privaatvõrgust luua väljamineva ühenduse globaalsetes aadressiruumi asuvate sõlmedega. Kuid samal ajal blokeerivad NAT-seadmed tavaliselt kogu sissetuleva liikluse, kui sissetulevate ühenduste jaoks pole kehtestatud eraldi reegleid.

Selline Interneti arhitektuur on piisavalt õige kliendi-serveri suhtluse jaoks, kus kliendid võivad asuda privaatsetes võrkudes, kuid serveritel on globaalne aadress. Kuid see tekitab raskusi kahe sõlme vaheline otsese ühenduse loomisega erinevate privaatsete võrkude vahel. erinevate privaatsüsteemide vahel. Otsene ühendus kahe sõlme vahel on oluline "peer-to-peer" rakenduste jaoks, nagu näiteks häälteenus (Skype), kaugarvuti juurde pääsemine (TeamViewer) või online-mängud.

Üks kõige tõhusamaid meetodeid peer-to-peer ühenduse loomisel erinevates privaatvõrkudes asuvate seadmete vahel on nii nimetatud "hole punching". Seda tehnikat kasutatakse kõige sagedamini UDP protokolli põhjal töötavates rakendustes.

Kuid kui teie rakendusele on vajalik andmete garanteeritud kohaletoimetamine, näiteks kui edastate faile arvutite vahel, siis tekivad UDP kasutamisel palju raskusi, kuna UDP ei ole garanteeritud kohaletoimetamise protokoll ega tagasta pakette õiges järjekorras, erinevalt TCP protokollist.

Sellisel juhul on garanteeritud pakettide kohaletoimetamise tagamiseks vajalik rakendada rakendusprotokoll, mis tagab vajalikud funktsioonid ja töötab UDP peal.

Tahaksin kohe märkida, et on olemas TCP hole punching tehnika, et luua TCP ühendusi sõlmede vahel erinevates privaatvõrkudes, kuid paljude NAT-seadmete toe puudumise tõttu ei peeta seda tavaliselt peamiseks viisiks selliste sõlmede ühendamiseks.

Edasi käesolevas artiklis käsitlen ainult garanteeritud kohaletoimetamise protokolli rakendust. UDP hole punching tehnika rakendust kirjeldatakse järgmistes artiklites.

Protokolli nõuded

  1. Usaldusväärne pakettide kohaletoimetamine, mille on rakendanud positiivne tagasiside mehhanism (nn positiivne tunnustus)
  2. Suure andmemahu efektiivne edastamine, see tähendab, et protokoll peab vältima tarbetute pakettide edastamisi.
  3. Peab olema võimalus tühistada kohaletoimetamise kinnitamise mehhanismi (võimalus töötada kui "puhas" UDP protokoll)
  4. Võime rakendada käsurežiimi, kinnitades iga sõnumi
  5. Andmete edastamise aluseks protokolli järgi peab olema sõnum

Need nõuded kattuvad suurel määral Reliable Data Protocoli nõuetega, mis on kirjeldatud rfc 908 ja rfc 1151, ja ma lähtusin nende standardite põhjal selle protokolli väljatöötamisel.

Nende nõuete mõistmiseks vaatame ajadiagramme, mis näitavad andmete edastamist kahe võrgu sõlme vahel protokollide TCP ja UDP kaudu. Eeldame, et mõlemas olukorras on kaotatud üks pakett.
Andmete edastamine mitteinteraktiivsete andmete kaudu TCP kaudu:Reliable Udp protokolli rakendamine .Net jaoks

Nagu diagrammil näha, tuvastab TCP pakettide kaotuse ja teavitab sellest saadetist, paludes kadunud segmentide numbreid.
Andmete edastamine UDP protokolli kaudu:Reliable Udp protokolli rakendamine .Net jaoks

UDP ei võta kaotuste tuvastamiseks mingeid meetmeid. Veakontroll UDP protokollis lasub täielikult rakendusel.

Veakontroll TCP protokollis saavutatakse tänu ühenduse loomisele lõpp-punktiga, selle ühenduse staatuse säilitamisele, edastatud baitide arvu märkimisele igas paketi päises ning kinnitusnumbrite edastamisele 'acknowledge number' kaudu.

Lisaks kasutab TCP protokoll, et tõsta jõudlust (s.t. saata rohkem kui üks segment ilma kinnitust saamata), nn edastamise akent — andmete baitide arvu, mida segmentide saatja ootab vastuvõetuna.

TCP protokolliga saab lähemalt tutvuda rfc 793, UDP osas aga rfc 768, kus need on tegelikult määratletud.

Ülest kirjeldatust on selge, et usaldusväärse sõnumitoimetamise protokolli loomine UDP peal (edaspidi nimetame seda Reliable UDP), nõuab andmete edastamiseks sarnaste mehhanismide rakendamist nagu TCP-l. Nimelt:

  • hoida ühenduse olekut
  • kasutada segmentide nummerdamist
  • kasutada eripakette kinnitamiseks
  • kasutada lihtsustatud akmehhanismi, et suurendada protokolli läbilaske võimet

Lisaks on vajalik:

  • indikaatorite saatmine sõnumi algusest, et ressursse ühenduse jaoks eraldada
  • indikaatorite saatmine sõnumi lõpust, et edastada saadud sõnum ülejäänud rakendusele ja vabastada protokolli ressursid
  • lubada protokollil lubada kinnituste mehhanismi väljalülitamist konkreetsete ühenduste jaoks, et toimida nagu "puhas" UDP

Reliable UDP päis

Mäletame, et UDP datagramm on kapseldatud IP datagrammi. Reliable UDP paketid kapseldatakse vastavalt UDP datagrammidesse.
Reliable UDP pealkirja kapseldamine:Reliable Udp protokolli rakendamine .Net jaoks

Reliable UDP pealkirja struktuur on piisavalt lihtne:

Reliable Udp protokolli rakendamine .Net jaoks

  • Flags – paketi juhtflaggid
  • MessageType – sõnumi tüüp, mida kasutatakse kõrgematel rakendustel, et tellida teatud sõnumeid
  • TransmissionId — edastuse number, mis koos adressaadi ja sadama aadressiga unikaalselt määratleb ühenduse
  • PacketNumber – paketi number
  • Options – protokolli täiendavad valikud. Esimese paketi puhul kasutatakse sõnumi suuruse näitamiseks

Flags võivad olla järgmised:

  • FirstPacket — sõnumi esimene pakett
  • NoAsk — sõnum ei nõua kinnituse mehhanismi kasutamist
  • LastPacket — sõnumi viimane pakett
  • RequestForPacket — kinnituse pakett või kadunud paketi taotlus

Protokolli üldpõhimõtted

Kuna Reliable UDP on suunatud sõnumi garanteeritud edastamisele kahe sõlme vahel, peab see suutma luua ühenduse teise poolega. Ühenduse loomiseks saadab saatja esimese paketi, millel on FirstPacket lipp, millele vastus tähendab ühenduse loomist. Kõik vastuspaketid, või teisiti öeldes, kinnituspaketid, seadistavad alati PacketNumber välja väärtuse, mis on üks suurem kui suurim PacketNumber väärtus edukalt saabunud pakettide seas. Esimese saadetud paketi Options väljal kajastatakse sõnumi suurust.

Ühenduse lõpetamiseks kasutatakse sarnast mehhanismi. Viimases sõnumipaketis on seatud LastPacket lipp. Vastuspaketis osutatakse viimase paketi numbrile + 1, mis tähendab vastuvõtja jaoks sõnumi edukat kohaletoimetamist.
Diagramm ühenduse loomise ja lõpetamise kohta:Reliable Udp protokolli rakendamine .Net jaoks

Kui ühendus on loodud, algab andmete edastamine. Andmed edastatakse paketikaubikute kaupa. Iga kaubik, välja arvatud viimane, sisaldab fikseeritud arvu pakette. See arv on vastuvõtu/edastuseaken. Viimasel andmekaubikul võib olla vähem pakette. Iga kaubiku saatmise järel ootab saatja kinnitusest, et see on kätte toimetatud, või allalaaditud kaotatud paketid, avades vastuseid eelneva vastuvõtu/edastuse jaoks. Pärast kaubiku kätte toimetamise kinnitamise saamist nihkub vastuvõtu/edastuseaken ja saadetakse järgmine andmekaubik.

Saaja aktsepteerib pakette. Igal pakett kontrollitakse, kas see sobib edastamisaknaa. Akna ulatusest välja jäävad paketid ja duplikaad filtreeritakse välja. Kuna akna suurus on rangelt fikseeritud ja sama nii saajal kui ka saatjal, siis juhul, kui blokikapid saadetakse ilma kaotuseta, nihkub aken järgmise andmeploki vastuvõtmiseks ja saadetakse kinnitus saadetuse kohta. Kui aken ei täitu seadistatud tööajastumise ajal, käivitatakse kontroll, millised paketid ei ole kätte saadud ja saadetakse taotlused uuesti saadetamiseks.
Edastamise kordusdiagramm:Reliable Udp protokolli rakendamine .Net jaoks

Protokolli aegumised ja taimerid

Ühenduse loomise võimalike takistuste seas on mitu põhjust. Näiteks võib vastuvõtja olla võrgu väljas. Sellisel juhul, kui proovite ühendust luua, katkestatakse ühendus ajaülevaate tõttu. Reliable UDP rakenduses kasutatakse kahe ajastuse määramiseks kahte taimerit. Esimene, töötaimer, ootab vastust kaugserverilt. Kui see aktiveerub saatja poolel, saadetakse viimane saatmise pakett uuesti. Kui aga taimer aktiveerub vastuvõtja poolel, kontrollitakse paketikaotusi ja saadetakse kordusnõudmisi.

Teine taimer on vajalik ühenduse sulgemiseks, kui sõlmede vahel puudub side. Saatja pool käivitab selle kohe pärast töötaimeri aktiveerimist ja ootab vastust kaug-sõlme poolt. Kui vastust ei tule sätestatud aja jooksul, siis ühendus lõpetatakse ja ressursid vabastatakse. Vastuvõtja poolel käivitub ühenduse sulgemise taimer pärast töötaimeri topeltaktiveerimist. See on vajalik kinnituse pakettide kadu vältimiseks. Taimeri aktiveerimisel lõpetatakse samuti ühendus ja ressursid vabastatakse.

Reliable UDP edastamise olekute diagramm

Protokolli tööpõhimõtted on ellu viidud lõppautomaatides, mille iga olek vastutab teatud loogika eest pakettide töötlemisel.
Reliable UDP olekudiagramm:

Reliable Udp protokolli rakendamine .Net jaoks

Closed – ei ole tegelikult olek, see on automaatide algus- ja lõpupunkt. Oleku Closed mõistetakse edastuse juhtimise plokina, mis, rakendades asünkroonset UDP serverit, suunab paketid vastavatesse ühendustesse ja käivitab olekute töötlemise.

FirstPacketSending – algne olek, milles on väljaminev ühendus sõnumi saatmise ajal.

Selle oleku puhul saadetakse esimene pakett tavaliste sõnumite jaoks. Kinnitamise mittesaatmise sõnumite puhul on see ainus olek – kogu sõnum saadetakse just selles seisundis.

SendingCycle – põhiseisund sõnumipakettide edastamiseks.

Üleminek sellesse olekusse FirstPacketSending toimub pärast esimese sõnumipaketi saatmist. Just sellesse olekusse jõuavad kõik kinnitused ja taotlused edastuste kordamiseks. Väljuda sellest on võimalik kahel juhul – sõnumi eduka kohaletoimetamise korral või ajutise katkestamise tõttu.

FirstPacketReceived – algne olek 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 saadetakse ilma kohaletoimetamise kinnituseta – see on ainus olek. Pärast sellise sõnumi töötlemist suletakse ühendus.

Assembling – põhiseisund sõnumipakettide vastuvõtmiseks.

Selles salvestatakse pakette ajutisse hoidlasse, kontrollitakse kadude olemasolu, saadetakse kinnitusi pakettide kohaletoimetamise kohta ja kogu sõnumi kohta ning saadetakse taotlusi kadunud pakettide uuesti kohaletoimetamiseks. Kui kogu sõnum on edukalt vastu võetud, läheb ühendus järgmisse olekusse Täidetud, vastasel juhul toimub välja minek ajaületuse tõttu.

Täidetud – ühenduse sulgemine kogu sõnumi eduka saamise korral.

See olek on vajalik sõnumi koostamiseks ja olukorra jaoks, kus sõnumi kohaletoimetamise kinnitus on teel saatjale kaduma läinud. Sellest olekust väljumine toimub ajaületuse tõttu, kuid ühendus loetakse edukalt suletuks.

Sügavam koodi. Edastuse juhtimisplokk

Üks Reliable UDP peamisi komponente on edastusjuhtimisplokk. Selle ploki ülesanne on hoida käimasolevaid ühendusi ja abielemente, jaotada saabunud pakette vastavatesse ühendustesse, pakkuda liidest pakettide saatmiseks ühendusse ning teostada protokollide API-d. Edastusjuhtimisplokk võtab pakette UDP tasemelt ja suunab need töötlemiseks lõppautomaatidesse. Pakettide vastuvõtmiseks on selles rakendatud asünkroonne UDP server.
Mõned ReliableUdpConnectionControlBlock klassi liikmed:

internal class ReliableUdpConnectionControlBlock : IDisposable
{
  // byte array for the specified key. Used for assembling incoming messages
  public ConcurrentDictionary<Tuple, byte[]> IncomingStreams { get; private set;}
  // byte array for the specified key. Used for sending outgoing messages.
  public ConcurrentDictionary<Tuple, byte[]> OutcomingStreams { get; private set; }
  // connection record for the specified key.
  private readonly ConcurrentDictionary<Tuple, ReliableUdpConnectionRecord> m_listOfHandlers;
  // list of subscribers for messages.
  private readonly List m_subscribers;
  // local socket
  private Socket m_socketIn;
  // port for incoming messages
  private int m_port;
  // local IP address
  private IPAddress m_ipAddress;
  // local endpoint
  public IPEndPoint LocalEndpoint { get; private set; }
  // collection of pre-initialized
  // states of the finite automaton
  public StatesCollection States { get; private set; }
  // random number generator. Used for creating TransmissionId
  private readonly RNGCryptoServiceProvider m_randomCrypto;
  
  //...
}

Asynchronous UDP server implementation:

private void Receive()
{
  EndPoint connectedClient = new IPEndPoint(IPAddress.Any, 0);
  // loome uus puhvr, iga socket.BeginReceiveFrom jaoks
  byte[] buffer = new byte[DefaultMaxPacketSize + ReliableUdpHeader.Length];
  // edastame puhvri parameetrina asünkroonse meetodi jaoks
  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);
  // pakett on saadud, järgmise vastuvõtmiseks valmis        
  Receive();
  // kuna lihtsaim viis lahendada puhvriga seotud küsimus on saada viide sellele
  // IAsyncResult.AsyncState-st        
  byte[] bytes = ((byte[]) ar.AsyncState).Slice(0, bytesRead);
  // saame paketi pealkirja        
  ReliableUdpHeader header;
  if (!ReliableUdpStateTools.ReadReliableUdpHeader(bytes, out header))
  {          
    // ebaõige pakett - lükkame tagasi
    return;
  }
  // loome võtme, et määrata paketi ühenduse kirje
  Tuple key = new Tuple(connectedClient, header.TransmissionId);
  // saame olemasoleva ühenduse kirje või loome uue
  ReliableUdpConnectionRecord record = m_listOfHandlers.GetOrAdd(key, new ReliableUdpConnectionRecord(key, this, header.ReliableUdpMessageType));
  // käivitame paketi töötlemise lõppautomaatides
  record.State.ReceivePacket(record, header, bytes);
}

Iga sõnumi edastamine loob struktuuri, mis sisaldab teavet ühenduse kohta. Sellist struktuuri nimetatakse connection record.
Mõned ReliableUdpConnectionRecord klassi liikmed:

internal class ReliableUdpConnectionRecord : IDisposable
{    
  // sissetulevate sõnumite baitide massiiv    
  public byte[] IncomingStream { get; set; }
  // viide automaatseisundi objekti
  public ReliableUdpState State { get; set; }    
  // paar, mis määratleb selgelt connection record'i
  // edastamise juhtimissektoris     
  public Tuple Key { get; private set;}
  // vastuvõtuvaba alumine piir    
  public int WindowLowerBound;
  // edastamise akna suurus
  public readonly int WindowSize;     
  // edastatav paketi number
  public int SndNext;
  // edastatavate pakettide hulk
  public int NumberOfPackets;
  // edastamise number (see on Tuple'i teine osa)
  // iga sõnumi jaoks eraldi	
  public readonly Int32 TransmissionId;
  // kaugserver – sõnumi tegelik vastuvõtja
  public readonly IPEndPoint RemoteClient;
  // paketi suurus, et vältida IP tasemel fragmenteerimist
  // ei tohi ületada MTU – (IP.Header + UDP.Header + RelaibleUDP.Header)
  public readonly int BufferSize;
  // edastamise juhtimise blok
  public readonly ReliableUdpConnectionControlBlock Tcb;
  // kapseldab asünkroonsete toimingute tulemusi BeginSendMessage/EndSendMessage
  public readonly AsyncResultSendMessage AsyncResult;
  // ei saada kinnitavate pakettide
  public bool IsNoAnswerNeeded;
  // viimane korrektselt vastuvõetud pakett (alati seatud suurimale numbrile)
  public int RcvCurrent;
  // massiiv kadunud pakettide numbritega
  public int[] LostPackets { get; private set; }
  // kas viimane pakett on saadud. Kasutatakse boolina.
  public int IsLastPacketReceived = 0;
  //...
}

Sügavam koodi. Oleku muutujad

Seisundid rakendavad Reliable UDP protokolli lõppautomaat, kus toimub pakettide põhikäsitlus. Abstraktne klass ReliableUdpState pakub oleku liidest:

Reliable Udp protokolli rakendamine .Net jaoks

Kogu protokolli tööloogika on realiseeritud ülaltoodud klasside poolt, koos abiklassiga, mis pakub staatilisi meetodeid, nagu näiteks ReliableUdp päise koostamine connection record'ist.

Edasi käsitletakse detailides liidese meetodite realiseerimist, mis määravad protokolli põhialgoritmid.

Meetod DisposeByTimeout

Meetod DisposeByTimeout vastutab ühenduse ressursside vabastamise eest ajaülevaate möödumisel ja edastamise eduka/ebaõnnestunud signaalimise eest.
ReliableUdpState.DisposeByTimeout:

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

Seda on üle määratud ainult olekus Täidetud.
Completed.DisposeByTimeout:

protected override void DisposeByTimeout(object record)
{
  ReliableUdpConnectionRecord connectionRecord = (ReliableUdpConnectionRecord) record;
  // teavitame eduka sõnumi saamisest
  SetAsCompleted(connectionRecord);
}

Meetod ProcessPackets

ProcessPackets meetod vastutab pakettide või pakettide täiendava töötlemise eest. Seda kutsutakse otse või pakettide ootamisaja taimeri kaudu.

olekus Assembling meetod on üle kirjutatud ja vastutab kadunud pakettide kontrollimise ja olekusse ülemineku eest Täidetud, viimase paketi saamisel ja eduka kontrolli läbimisel
Assembling.ProcessPackets:

public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
  if (connectionRecord.IsDone != 0)
    return;
  if (!ReliableUdpStateTools.CheckForNoPacketLoss(connectionRecord, connectionRecord.IsLastPacketReceived != 0))
  {
    // on kaotatud pakette, saadame nende kohta päringud
    foreach (int seqNum in connectionRecord.LostPackets)
    {
      if (seqNum != 0)
      {
        ReliableUdpStateTools.SendAskForLostPacket(connectionRecord, seqNum);
      }
    }
    // seadistame taimeri teist korda, et proovida edastust uuesti
    if (!connectionRecord.TimerSecondTry)
    {
      connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
      connectionRecord.TimerSecondTry = true;
      return;
    }
    // kui kahe proovimise järel WaitForPacketTimer ei
    // õnnestunud pakette saada - alustame ühenduse sulgemise taimerit
    StartCloseWaitTimer(connectionRecord);
  }
  else if (connectionRecord.IsLastPacketReceived != 0)
  // edukas kontroll
  {
    // saadame kinnituse andmepaki vastuvõtmiseks
    ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
    connectionRecord.State = connectionRecord.Tcb.States.Completed;
    connectionRecord.State.ProcessPackets(connectionRecord);
    // собираем ресурсы для момента слушания
    // käivitame taimeri, juhuks kui
    // kui viimane kinnitamine ei jõua saatjale ja ta küsib selle uuesti.
    // taimeri löömisel - realiseerime ressursid
    // olekus Completed on taimeri meetod ümber määratud
    StartCloseWaitTimer(connectionRecord);
  }
  // see on olukord, kui andmepaki kinnitamine on kaduma läinud
  else
  {
    if (!connectionRecord.TimerSecondTry)
    {
      ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
      connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
      connectionRecord.TimerSecondTry = true;
      return;
    }
    // alustame ühenduse sulgemise taimerit
    StartCloseWaitTimer(connectionRecord);
  }
}

olekus SendingCycle see meetod kutsutakse esmakordselt ainult ajastamise järgi ning see vastutab viimase sõnumi saatmise taastamise ning ühenduse sulgemise taimeri aktiveerimise eest.
SendingCycle.ProcessPackets:

public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
  if (connectionRecord.IsDone != 0)
    return;        
  \/\/ saadame viimase paketi uuesti 
  \/\/ (ühenduse taastamisel saadetakse saajale uuesti päringud, mis temani ei jõudnud)        
  ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.RetransmissionCreateUdpPayload(connectionRecord, connectionRecord.SndNext - 1));
  \/\/ aktiveerime CloseWait taimeri – ootamiseks ühenduse taastamise või lõpetamise oletamiseks
  StartCloseWaitTimer(connectionRecord);
}

olekus Täidetud meetod peatab töötava 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 FirstPacketReceived meetodi peamine ülesanne on määrata, kas esimene sõnumipakett tõepoolest jõudis liidesele ning koguda ühekordne paketist koosnev sõnum.
FirstPacketReceived.ReceivePacket:

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  if (!header.Flags.HasFlag(ReliableUdpHeaderFlags.FirstPacket))
    // pakett kõrvaldame
    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 kinnituse paketi          
      ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
    }
    SetAsCompleted(connectionRecord);
    return;
  }
  // disaini järgi kõik paketi numbrid algavad 0-st;
  if (header.PacketNumber != 0)          
    return;
  ReliableUdpStateTools.InitIncomingBytesStorage(connectionRecord, header);
  ReliableUdpStateTools.WritePacketData(connectionRecord, header, payload);
  // loeme pakettide arvu, mis peaksid saama
  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 nihutame vastuvõtu akna 1 võrra
  connectionRecord.WindowLowerBound++;
  // vahetame olekuid
  connectionRecord.State = connectionRecord.Tcb.States.Assembling;
  // kui kinnituse mehhanism ei ole vajalik
  // 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 SendingCycle see meetodi on ülekirjutatud kohaletoimetamise kinnituste ja edastamise taotluste 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;
  // akna lõpp-punkti arvestamine
  // akna piir + 1, et saada kinnitus edastamise kohta
  int windowHighestBound = Math.Min((connectionRecord.WindowLowerBound + connectionRecord.WindowSize), (connectionRecord.NumberOfPackets));
  // kontroll akna piiridele        
  if (header.PacketNumber  windowHighestBound)
    return;
  connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
  if (connectionRecord.CloseWaitTimer != null)
    connectionRecord.CloseWaitTimer.Change(-1, -1);
  // kontroll viimasel paketil:
  if (header.PacketNumber == connectionRecord.NumberOfPackets)
  {
    // edastus on lõpetatud
    Interlocked.Increment(ref connectionRecord.IsDone);
    SetAsCompleted(connectionRecord);
    return;
  }
  // see on esimese paketi kinnitus          
  if ((header.Flags.HasFlag(ReliableUdpHeaderFlags.FirstPacket) && header.PacketNumber == 1))
  {
    // ilma akna liigutamiseta
    SendPacket(connectionRecord);
  }
  // saadud andmeploki kinnitus
  else if (header.PacketNumber == windowHighestBound)
  {
    // liigutame vastuvõtu/edastamise akna
    connectionRecord.WindowLowerBound += connectionRecord.WindowSize;
    // lähtestame edastuse kontrollimise massiivi
    connectionRecord.WindowControlArray.Nullify();
    // saadame pakettide ploki
    SendPacket(connectionRecord);
  }
  // see on taotlus uuesti edastamiseks – saadame vajaliku paketi          
  else
    ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.RetransmissionCreateUdpPayload(connectionRecord, header.PacketNumber));
}

olekus Assembling ReceivePacket meetodis toimub peamine töö saabuvate pakkide sõnumi kokkupanemiseks.
Kogumine.ReceivePacket:

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  if (connectionRecord.IsDone != 0)
    return;
  // Pakettide töötlemine, kui kinnitusmehhanism on välja lülitatud
  if (header.Flags.HasFlag(ReliableUdpHeaderFlags.NoAsk))
  {
    // Taimeri lähtestamine
    connectionRecord.CloseWaitTimer.Change(connectionRecord.LongTimerPeriod, -1);
    // Andmete salvestamine
    ReliableUdpStateTools.WritePacketData(connectionRecord, header, payload);
    // Kui saime viimase flagiga paketi - lõpetame
    if (header.Flags.HasFlag(ReliableUdpHeaderFlags.LastPacket))
    {
      connectionRecord.State = connectionRecord.Tcb.States.Completed;
      connectionRecord.State.ProcessPackets(connectionRecord);
    }
    return;
  }        
  // Aknapiiri lõpparvutamine
  int windowHighestBound = Math.Min((connectionRecord.WindowLowerBound + connectionRecord.WindowSize - 1), (connectionRecord.NumberOfPackets - 1));
  // Eemaldame aknasse mitte mahuvad paketid
  if (header.PacketNumber  (windowHighestBound))
    return;
  // Eemaldame duplikaadid
  if (connectionRecord.WindowControlArray.Contains(header.PacketNumber))
    return;
  // Andmete salvestamine 
  ReliableUdpStateTools.WritePacketData(connectionRecord, header, payload);
  // Pakettide arvu suurendamine        
  connectionRecord.PacketCounter++;
  // Salvestame juhise alla praeguse paketi numbri        
  connectionRecord.WindowControlArray[header.PacketNumber - connectionRecord.WindowLowerBound] = header.PacketNumber;
  // Määrame suurima saabuva 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 viimane pakett on saabunud
  if (header.Flags.HasFlag(ReliableUdpHeaderFlags.LastPacket))
  {
    Interlocked.Increment(ref connectionRecord.IsLastPacketReceived);
  }
  // Kui kõik akna paketid on saabunud, siis lähtestame arvu
  // ja saadame kinnituspaketi
  else if (connectionRecord.PacketCounter == connectionRecord.WindowSize)
  {
    // Arvu lähtestamine.      
    connectionRecord.PacketCounter = 0;
    // Edastusakna liigutamine
    connectionRecord.WindowLowerBound += connectionRecord.WindowSize;
    // Edastusjuhtimise massiivi nullimine
    connectionRecord.WindowControlArray.Nullify();
    ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
  }
  // Kui viimane pakett on juba olemas        
  if (Thread.VolatileRead(ref connectionRecord.IsLastPacketReceived) != 0)
  {
    // Kontrollime pakette          
    ProcessPackets(connectionRecord);
  }
}

olekus Täidetud meetodi ainus eesmärk on saata korduv kinnitus sõnumi edastamise õnnestumisest.
Completed.ReceivePacket:

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  // viimane pakett saadetakse uuesti,
  // kuna viimane ack ei jõudnud saatjani
  if (header.Flags.HasFlag(ReliableUdpHeaderFlags.LastPacket))
  {
    ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
  }
}

Meetod SendPacket

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

public override void SendPacket(ReliableUdpConnectionRecord connectionRecord)
{
  connectionRecord.PacketCounter = 0;
  connectionRecord.SndNext = 0;
  connectionRecord.WindowLowerBound = 0;       
  // kui kinnitus ei ole vajalik - 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 pea ja saadame selle 
  ReliableUdpHeader header = ReliableUdpStateTools.CreateReliableUdpHeader(connectionRecord);
  ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.CreateUdpPayload(connectionRecord, header));
  // suurendame loendurit
  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 SendingCycle Selles meetodis toimub pakettide bloki saatmine.
SendingCycle.SendPacket:

public override void SendPacket(ReliableUdpConnectionRecord connectionRecord)
{      
  // saadame paketibloki      
  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++;
  }
  // juhul kui edastamise aken on suur, taaskäivitame taimeri peale saatmist
  connectionRecord.WaitForPacketsTimer.Change( connectionRecord.ShortTimerPeriod, -1 );
  if ( connectionRecord.CloseWaitTimer != null )
  {
    connectionRecord.CloseWaitTimer.Change( -1, -1 );
  }
}

Sügavam koodi. Ühenduste loomine ja seadistamine

Nüüd, kui oleme tutvunud põhiseisundite ja meetoditega, mida kasutatakse olekute töötlemiseks, saame kaaluda veidi põhjalikumalt mõningaid protokolli tööd.
Andmete edastamise diagramm normaalsetes tingimustes:Reliable Udp protokolli rakendamine .Net jaoks

Kaalume põhjalikult loomist connection record ühenduse jaoks ja esimese paketi saatmist. Edastamise algatajana toimib alati rakendus, mis kutsub välja sõnumi saatmise API-meetodi. Järgmisena kaasatakse edastamise juhtimise ploki StartTransmission meetod, mis käivitab uue sõnumi andmete edastamise.
Väljamineva ü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 konstruktorit või käivitama kuulaja messagi saatmise eel.");
    }
    // Käivitame sissetulevate pakettide töötlemise
    StartListener(LocalEndpoint);
  }
  // Loome võtme sõnastikule, tuginedes 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 meie sõnastikes eksisteerib
  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 jälle ei õnnestunud – genereerime erandi
      throw new ArgumentException("Paari TransmissionId & EndPoint' olemasolek sõnastikus");
  }
  // Oleme seisundi töötlemise käivitanud
  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 pealkiri ja saatke see
  ReliableUdpHeader header = ReliableUdpStateTools.CreateReliableUdpHeader(connectionRecord);
  ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.CreateUdpPayload(connectionRecord, header));
  // suurendame loendurit
  connectionRecord.SndNext++;
  // nihutame akna
  connectionRecord.WindowLowerBound++;
  // läheme SendingCycle olekusse
  connectionRecord.State = connectionRecord.Tcb.States.SendingCycle;
  // Käivitage ajastaja
  connectionRecord.WaitForPacketsTimer = new Timer(CheckByTimer, connectionRecord, connectionRecord.ShortTimerPeriod, -1);
}

Pärast esimese paketi saatmist liigub saatja olekusse SendingCycle – oodata kinnitust paketi kohaletoimetamisest.
Saaja pool, kasutades meetodit EndReceive, aktsepteerib saadetud paketti, loob uue connection record ja edastab antud paketi, eelnevalt parsitud pealkirjaga, ReceivePacket meetodisse olekust FirstPacketReceived
Ühenduse loomine vastuvõtupoolselt:

private void EndReceive(IAsyncResult ar)
{
  // ...
  // pakett on saadud
  // paketi päise analüüsimine
  ReliableUdpHeader header;
  if (!ReliableUdpStateTools.ReadReliableUdpHeader(bytes, out header))
  {          
    // vale pakett - loobume sellest
    return;
  }
  // konstrueerime võtme, et määrata kindlaks ühenduse rekord paketi jaoks
  Tuple<EndPoint, Int32> key = new Tuple<EndPoint, Int32>(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õppstaadiumis
  record.State.ReceivePacket(record, header, bytes);
}

Esimese paketi vastuvõtmine ja kinnituse saatmine (olekus FirstPacketReceived):

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  if (!header.Flags.HasFlag(ReliableUdpHeaderFlags.FirstPacket))
    // kuvatakse paketti
    return;
  // ...
  // disaini tõttu algavad kõik pakettide numbrid 0-st;
  if (header.PacketNumber != 0)          
    return;
  // initsialiseerime massiivi sõnumi osade hoidmiseks
  ReliableUdpStateTools.InitIncomingBytesStorage(connectionRecord, header);
  // salvestame andmed paketis massiivi
  ReliableUdpStateTools.WritePacketData(connectionRecord, header, payload);
  // arvutame, kui palju pakette peaks saama
  connectionRecord.NumberOfPackets = (int)Math.Ceiling((double) ((double) connectionRecord.IncomingStream.Length/(double) connectionRecord.BufferSize));
  // salvestame viimase saadud paketi numbri (0)
  connectionRecord.RcvCurrent = header.PacketNumber;
  // edastame vastuvõtuvahendi akna ühe võrra
  connectionRecord.WindowLowerBound++;
  // vahetame olekut
  connectionRecord.State = connectionRecord.Tcb.States.Assembling;  
  if (/*kui tõendusse mõeldud mehhanismi ei nõuta*/)
  // ...
  else
  {
    // saadame tõenduse
    ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
    connectionRecord.WaitForPacketsTimer = new Timer(CheckByTimer, connectionRecord, connectionRecord.ShortTimerPeriod, -1);
  }
}

Sügavam koodi. Ühenduse sulgemine aegumise põhjal

Ajutiste katkestuste käsitlemine on oluline osa Reliable UDP-st. Vaatame näidet, kus vahepealses sõlmes toimus rike ja andmete edastamine mõlemal suunal muutus võimatuks.
Ühenduse sulgemise ajagraafik:Reliable Udp protokolli rakendamine .Net jaoks

Nagu diagrammil näha, käivitub saatja tööajanäit kohe pärast paketibloki saatmist. See toimub meetodis SendPacket olekus. SendingCycle.
Tööajanäidu sisselülitamine (olek SendingCycle):

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

Taimeri perioodid määratakse ühenduse loomisel. Vaikimisi on ShortTimerPeriod 5 sekundit. Näites on see seatud 1,5 sekundiks.

Siseneva ühenduse taimer käivitub pärast viimasest saabunud andmepakist, see toimub meetodis ReceivePacket olekus. Assembling
Tööajanäidu sisselülitamine (olek Assembling):

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

Sissetulevas ühenduses ei tulnud tööajal ooteaega jooksul enam pakette. Taimer aktiveerus ja kutsus välja meetodi ProcessPackets, kus tuvastati kaotatud paketid ja esmakordselt saadeti kordusnõuded.
Kordusnõuete saatmine (seisund: Assembling):

public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
  \/\/ ...        
  if (\/*kaotatud paketid kontrollimine *\/)
  {
    \/\/ saadame kordusnõuded
    \/\/ seadistame taimeri teist korda, edastamise proovimiseks
    if (!connectionRecord.TimerSecondTry)
    {
      connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
      connectionRecord.TimerSecondTry = true;
      return;
    }
  \/\/ kui kahel korral WaitForPacketTimer säilitamisel 
  \/\/ ei õnnestunud pakette saada - käivitame ühenduse lõpufaasi taimeri
  StartCloseWaitTimer(connectionRecord);
  }
  else if (\/*viimane pakk oli saadetud ja edukas kontroll *\/)
  {
    \/\/ ...
    StartCloseWaitTimer(connectionRecord);
  }
  \/\/ kui akn pakkide blokk oli kadunud
  else
  { 
    if (!connectionRecord.TimerSecondTry)
    {
      \/\/ saadame akn uuesti
      connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
      connectionRecord.TimerSecondTry = true;
      return;
    }
    \/\/ käivitame ühenduse lõpufaasi taimeri
    StartCloseWaitTimer(connectionRecord);
  }
}

Muutuja TimerSecondTry seadistati true. See muutuja vastutab tööaegade taaskäivitamise eest.

Saatja poolel aktiveeritakse ka tööaeg ja saadetakse uuesti viimane saadetud pakett.
Ühendi sulgemise taimeri aktiveerimine (seisund SendingCycle):

public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
  // ...        
  // saadame uuesti viimase paketi 
  // ...        
  // lülitame sisse CloseWait taimeri – et oodata ühenduse taastumist või lõpetamist
  StartCloseWaitTimer(connectionRecord);
}

Pärast seda käivitatakse väljuvas ühenduses 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);
}

Ühendi sulgemise taimeri ooteperiood on vaikimisi 30 sekundit.

Lühikese aja pärast aktiveeritakse uuesti tööaeg saaja poolel, saadetakse uuesti päringud, seejärel käivitatakse sissetuleva ühenduse sulgemise taimer.

Sulgedes ühenduse eelregistreerimise taimerite tegevuse, vabastatakse kõigi ressursid. Saaja teatab ebaõnnestunud edastamisest ülemuurivale rakendusele (vt API Reliable UDP).
Ühenduse eelregistreerimise 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ügavam koodi. Andmevoogude taastamine

Andmete edastamise taastamise diagramm paketi kaotuse korral:Reliable Udp protokolli rakendamine .Net jaoks

Kuidas juba arutatud ühenduse sulgemise ajal ajaülesande lõppemise korral, toimub vastuvõtja juures kontroll kaotatud pakettide järgi. Kui pakette on kaotatud, koostatakse kaotatud paketide numbrite loetelu, mis kantakse ühenduse konkreetse LostPackets massiivi. Need numbrid saadetakse uuesti edastamise taotlemiseks.
Uuesti edastamisnõuete saatmine (kogumise seisund):

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

Saatja aktsepteerib taotlust uue kohaletoimetamise jaoks ja saadab puuduvad paketid. Tuleb märkida, et saatjal on sel hetkel juba käivitatud ühenduse sulgemise taimer ja taotluse saamisel see nullitakse.
Kadunud pakettide kordussaatmine (olek SendingCycle):

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

Uuesti saadetud pakett (packet#3 diagrammil) võetakse vastu siseneva ühenduse kaudu. Kontrollitakse vastuvõtu akna täitmist ja tavaline andmeedastus taastatakse.
Kontroll vastuvõtu aknasse (seisund Assembling):

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  // ...
  // suurendame pakettide loenduri
  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;
  // taaskäivitame taimerid
  connectionRecord.TimerSecondTry = false;
  connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
  if (connectionRecord.CloseWaitTimer != null)
    connectionRecord.CloseWaitTimer.Change(-1, -1);
  // ...
  // kui kõik akna paketid on saabunud, siis lähtestame loenduri
  // ja saadame kinnituse paketi
  else if (connectionRecord.PacketCounter == connectionRecord.WindowSize)
  {
    // lähtestame loenduri.
    connectionRecord.PacketCounter = 0;
    // oleme liikunud edastamise akent
    connectionRecord.WindowLowerBound += connectionRecord.WindowSize;
    // lähtestame edastamise juhtimise massiivi
    connectionRecord.WindowControlArray.Nullify();
    ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
  }
  // ...
}

Reliable UDP API

Andmete edastamise protokolli suhtlemiseks on olemas avatud klass Reliable Udp, mis on wrapper edastamise juhtimise ploki üle. Siin on klassi kõige olulisemad liikmed:

avalik suletud klass ReliableUdp : IDisposable
{
  // saab kohaliku lõpp-punkti
  public IPEndPoint LocalEndpoint    
  // loob ReliableUdp eksemplari ja käivitab
  // sisenevate pakkide kuulamise antud IP aadressil
  // ja sadamal. Port 0 tähendab dünaamiliselt eraldatud
  // pordi kasutamist
  public ReliableUdp(IPAddress localAddress, int port = 0) 
  // tellimine sisenevate sõnumite saamiseks
  public ReliableUdpSubscribeObject SubscribeOnMessages(ReliableUdpMessageCallback callback, ReliableUdpMessageTypes messageType = ReliableUdpMessageTypes.Any, IPEndPoint ipEndPoint = null)    
  // tellimisest tagasiside saamiseks loobumine
  public void Unsubscribe(ReliableUdpSubscribeObject subscribeObject)
  // asünkroonne sõnumi saatmine 
  // Märkus: ühilduvus XP ja Server 2003-ga ei kaandu, kuna kasutatakse .NET Framework 4.0
  public Task SendMessageAsync(ReliableUdpMessage reliableUdpMessage, IPEndPoint remoteEndPoint, CancellationToken cToken)
  // alusta 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)  
  // vabasta ressursid
  public void Dispose()    
}

Sõnumi vastuvõtt toimub tellimise kaudu. Tagasikutsumise meetodi delegeeri allkiri:

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

Sõnum:

public class ReliableUdpMessage
{
  // sõnumi tüüp, lihtne loend
  public ReliableUdpMessageTypes Type { get; private set; }
  // sõnumi andmed
  public byte[] Body { get; private set; }
  // kui true, siis kinnitusmehhanism tarnimiseks on keelatud
  // konkreetse sõnumi edastamiseks
  public bool NoAsk { get; private set; }
}

Konkreetse sõnumi tüübi ja/või konkreetse saatja tellimiseks kasutatakse kahte valikulist parameetrit: ReliableUdpMessageTypes messageType ja IPEndPoint ipEndPoint.

Sõnumi tüübid:

public enum ReliableUdpMessageTypes : short
{ 
  // Igaüks
  Any = 0,
  // Kirjutamine STUN serverisse
  StunRequest = 1,
  // Vastus STUN serverilt
  StunResponse = 2,
  // Faili edastamine
  FileTransfer = 3,
  // ...
}

Sõnumi edastamine toimub asünkroonselt, selleks on protokollis rakendatud asünkroonne programmeerimis mudel:

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

Sõnumi edastamise tulemus on true – kui sõnum jõudis saajani edu korral ja false – kui ühendus suleti ajaülesande tõttu:

public bool EndSendMessage(IAsyncResult asyncResult)

Kokkuvõte

Käesolevas artiklis ei ole palju kirjeldatud. Voogude sünkroniseerimise mehhanismid, erandite ja vigade töötlemine ning asünkroonsete sõnumite saatmise meetodite rakendamine. Kuid protokolli tuum, pakettide töötlemise loogika, ühenduse loomine ja ajutiste kontaktide töötlemine peaksid Teile selgemaks saama.

Käideldav usaldusliku kohaletoimetamise protokoll on piisavalt stabiilne ja paindlik ning vastab eelnevalt määratletud nõuetele. Soovin aga lisada, et kirjeldatud rakendust on võimalik täiustada. Näiteks võiksite suurendada läbilaskevõimet ja dünaamiliselt muuta ajastuse perioode ning protokolli võiks lisada mehhanisme nagu liugukse mehhanism ja RTT; samuti oleks kasulik rakendada MTU määramise mehhanismi ühenduse sõlmede vahel (kuid ainult suuremate sõnumite saatmise korral).

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

P.S. Neile, kes huvituvad üksikasjadest või lihtsalt tahavad protokolli testida, on siin projekt GitHubis:
Reliable UDP projekt

Kasulikud lingid ja artiklid

  1. TCP protokolli spetsifikatsioon: inglise keeles ja vene keeles
  2. UDP protokolli spetsifikatsioon: inglise keeles ja vene keeles
  3. RUDP protokolli arutelu: draft-ietf-sigtran-reliable-udp-00
  4. Usaldusväärne andmeprotokoll: rfc 908 ja rfc 1151
  5. Lihtne rakendus UDP kohaletoimetamise kinnitamiseks: Saage täielik kontroll oma võrgu üle .NET ja UDP abil
  6. Artikkel, mis käsitleb NAT-de ületamise mehhanisme: Peer-to-Peer kommunikatsioon võrguaadresside tõlkijate kaudu
  7. Asünkroonse programmeerimise mudeli rakendamine: CLR asünkroonse programmeerimise mudeli rakendamine ja Kuidas rakendada IAsyncResult disainimustrit
  8. Asünkroonse programmeerimise mudeli viimine ülesannete põhisesse asünkroonse mustrisse (APM-st TAP-ks):
    TPL ja traditsiooniline .NET asünkroonne programmeerimine
    Koosolek teiste asünkroonsete mustrite ja tüüpidega

Uuendus: Aitäh mayorovp ja sidristij ideede lisamise ülesande rakendamiseks. Raamatukogu ühilduvus vanade opsüsteemidega ei muutu, kuna 4. raamistik toetab nii XP-d kui ka 2003 serverit.

Allikas: habr.com

Osta usaldusväärne veebihosting DDoS kaitsega, VPS VDS serverid 🔥 Osta usaldusväärne veebihosting DDoS kaitsega, VPS VDS serverid | ProHoster