Usaldusväärse Udp-protokolli rakendamine .Neti jaoks

Internet on muutunud juba ammu. Üks Interneti peamisi protokolle - UDP-d ei kasuta rakendused mitte ainult andmegrammide ja leviedastuste edastamiseks, vaid ka võrgusõlmede vahelise "peer-to-peer" ühenduste pakkumiseks. Tänu lihtsale disainile on sellel protokollil palju varem planeerimata kasutusvõimalusi, samas pole protokolli puudused, näiteks garanteeritud kohaletoimetamise puudumine, kuhugi kadunud. Selles artiklis kirjeldatakse garanteeritud tarneprotokolli rakendamist UDP kaudu.
Sisukord:Kanne
Protokolli nõuded
Usaldusväärne UDP päis
Protokolli üldpõhimõtted
Aegumised ja protokollitaimerid
Usaldusväärne UDP edastuse oleku diagramm
Sügavamale koodile. ülekande juhtseade
Sügavamale koodile. osariigid

Sügavamale koodile. Ühenduste loomine ja loomine
Sügavamale koodile. Ühenduse sulgemine ajalõpu ajal
Sügavamale koodile. Andmeedastuse taastamine
Usaldusväärne UDP API
Järeldus
Kasulikud lingid ja artiklid

Kanne

Interneti algne arhitektuur eeldas homogeenset aadressiruumi, milles igal sõlmel oli globaalne ja kordumatu IP-aadress ning see võis suhelda otse teiste sõlmedega. Nüüd on Internetil tegelikult erinev arhitektuur – üks globaalsete IP-aadresside ala ja palju alasid, kus privaatsed aadressid on peidetud NAT-seadmete taha.Selles arhitektuuris saavad ainult globaalses aadressiruumis olevad seadmed hõlpsasti suhelda kõigi võrgus olevate inimestega, kuna neil on ainulaadne globaalselt marsruutitav IP-aadress. Privaatvõrgu sõlm saab ühenduda teiste sama võrgu sõlmedega ja saab luua ühenduse ka teiste globaalses aadressiruumis tuntud sõlmedega. See interaktsioon saavutatakse suuresti tänu võrguaadressi tõlkimise mehhanismile. NAT-seadmed, nagu Wi-Fi-ruuterid, loovad väljuvate ühenduste jaoks spetsiaalseid tõlketabeli kirjeid ning muudavad pakettide IP-aadresse ja pordinumbreid. See võimaldab luua väljuvaid ühendusi privaatvõrgust globaalses aadressiruumis asuvatesse hostidesse. Kuid samal ajal blokeerivad NAT-seadmed tavaliselt kogu sissetuleva liikluse, kui sissetulevate ühenduste jaoks pole seatud eraldi reegleid.

Selline Interneti arhitektuur on piisavalt korrektne kliendi-serveri suhtluseks, kus kliendid võivad olla privaatvõrkudes ja serveritel on globaalne aadress. Kuid see tekitab raskusi kahe sõlme otseseks ühendamiseks erinevad privaatvõrgud. Otsene ühendus kahe sõlme vahel on oluline peer-to-peer rakenduste jaoks, nagu kõneedastus (Skype), arvutile kaugjuurdepääsu saamine (TeamViewer) või võrgumäng.

Üks tõhusamaid meetodeid peer-to-peer-ühenduse loomiseks erinevates privaatvõrkudes olevate seadmete vahel on augustamine. Seda tehnikat kasutatakse kõige sagedamini UDP-protokollil põhinevate rakenduste puhul.

Aga kui teie rakendus vajab garanteeritud andmete edastamist, näiteks edastate faile arvutite vahel, siis on UDP kasutamisel palju raskusi, kuna UDP ei ole garanteeritud edastamisprotokoll ega paku pakettide edastamist erinevalt TCP-st. protokolli.

Sel juhul on garanteeritud paketiedastuse tagamiseks vajalik rakendada rakenduskihi protokoll, mis tagab vajaliku funktsionaalsuse ja töötab üle UDP.

Tahan kohe märkida, et erinevate privaatvõrkude sõlmede vahel TCP-ühenduste loomiseks on olemas TCP-augustamise tehnika, kuid kuna paljud NAT-seadmed seda ei toeta, ei peeta seda tavaliselt ühenduse loomise peamiseks viisiks. sellised sõlmed.

Selle artikli ülejäänud osas keskendun ainult garanteeritud tarneprotokolli rakendamisele. UDP augustamise tehnika rakendamist kirjeldatakse järgmistes artiklites.

Protokolli nõuded

  1. Usaldusväärne pakettide edastamine, mis on rakendatud positiivse tagasiside mehhanismi kaudu (nn positiivne kinnitus)
  2. Vajadus suurandmete tõhusaks edastamiseks, s.o. protokoll peab vältima tarbetut pakettide edastamist
  3. Tarnekinnitusmehhanismi (võimalus toimida "puhta" UDP-protokollina) peaks olema võimalik tühistada
  4. Võimalus rakendada käsurežiimi koos iga sõnumi kinnitusega
  5. Protokolli kaudu andmete edastamise põhiühikuks peab olema teade

Need nõuded langevad suures osas kokku punktis kirjeldatud usaldusväärse andmeprotokolli nõuetega RFF 908 и RFF 1151, ja tuginesin selle protokolli väljatöötamisel nendele standarditele.

Nende nõuete mõistmiseks vaatame kahe võrgusõlme vahelise andmeedastuse ajastust, kasutades TCP- ja UDP-protokolle. Olgu mõlemal juhul üks pakett kadunud.
Mitteinteraktiivsete andmete edastamine TCP kaudu:Usaldusväärse Udp-protokolli rakendamine .Neti jaoks

Nagu diagrammil näha, tuvastab TCP paketi kadumise korral kadunud paketi ja teatab sellest saatjale, küsides kadunud segmendi numbrit.
Andmeedastus UDP-protokolli kaudu:Usaldusväärse Udp-protokolli rakendamine .Neti jaoks

UDP ei võta kaotuse tuvastamiseks ette mingeid samme. UDP-protokolli edastusvigade kontrollimine on täielikult rakenduse vastutusel.

TCP-protokolli vigade tuvastamine saavutatakse ühenduse loomisega lõppsõlmega, selle ühenduse oleku salvestamise, igas paketi päises saadetud baitide arvu näitamise ja kviitungite teatamise kaudu kinnitusnumbri abil.

Lisaks kasutab TCP-protokoll jõudluse parandamiseks (st rohkem kui ühe segmendi saatmine ilma kinnitust saamata) nn edastusakent – ​​andmebaitide arvu, mida segmendi saatja ootab.

Lisateavet TCP-protokolli kohta vt RFF 793, UDP-st kuni RFF 768kus need tegelikult on määratletud.

Eeltoodust nähtub selgelt, et usaldusväärse sõnumiedastusprotokolli loomiseks üle UDP (edaspidi kui Usaldusväärne UDP), on vaja rakendada TCP-ga sarnaseid andmeedastusmehhanisme. Nimelt:

  • salvestage ühenduse olek
  • kasutage segmentide nummerdamist
  • kasutage spetsiaalseid kinnituspakette
  • kasutage protokolli läbilaskevõime suurendamiseks lihtsustatud aknamehhanismi

Lisaks vajate:

  • signaali algusest, et eraldada ühenduse jaoks ressursse
  • signaali lõppu, et edastada vastuvõetud sõnum ülesvoolurakendusele ja vabastada protokolli ressursid
  • lubage ühendusepõhisel protokollil keelata kohaletoimetamise kinnitusmehhanismi toimimine "puhta" UDP-na

Usaldusväärne UDP päis

Tuletage meelde, et UDP-andmegramm on kapseldatud IP-andmegrammi. Usaldusväärne UDP-pakett on asjakohaselt "pakitud" UDP-datagrammi.
Usaldusväärne UDP päise kapseldus:Usaldusväärse Udp-protokolli rakendamine .Neti jaoks

Usaldusväärse UDP päise struktuur on üsna lihtne:

Usaldusväärse Udp-protokolli rakendamine .Neti jaoks

  • Lipud – pakendi kontrolllipud
  • MessageType – sõnumitüüp, mida kasutavad ülesvoolu rakendused konkreetsete sõnumite tellimiseks
  • TransmissionId - edastuse number koos saaja aadressi ja pordiga tuvastab ühenduse unikaalselt
  • PacketNumber – paketi number
  • Valikud – protokolli lisavalikud. Esimese paketi puhul kasutatakse seda sõnumi suuruse märkimiseks

Lipud on järgmised:

  • FirstPacket – sõnumi esimene pakett
  • NoAsk – sõnum ei nõua kinnitusmehhanismi lubamist
  • LastPacket – sõnumi viimane pakett
  • RequestForPacket – kinnituspakett või taotlus kadunud paketi kohta

Protokolli üldpõhimõtted

Kuna Usaldusväärne UDP on keskendunud garanteeritud sõnumiedastusele kahe sõlme vahel, peab see suutma luua ühenduse teise poolega. Ühenduse loomiseks saadab saatja FirstPacket lipuga paketi, millele vastamine tähendab ühenduse loomist. Kõik vastusepaketid ehk teisisõnu kinnituspaketid seavad välja PacketNumber väärtuseks alati ühe võrra suurema kui edukalt vastu võetud pakettide suurim PacketNumber väärtus. Esimese saadetud paketi väljal Valikud on kirja suurus.

Sarnast mehhanismi kasutatakse ühenduse katkestamiseks. LastPacketi lipp on seatud sõnumi viimasele paketile. Vastuspaketis on märgitud viimase paketi number + 1, mis vastuvõtva poole jaoks tähendab sõnumi edukat kohaletoimetamist.
Ühenduse loomise ja lõpetamise skeem:Usaldusväärse Udp-protokolli rakendamine .Neti jaoks

Kui ühendus on loodud, algab andmeedastus. Andmed edastatakse pakettide plokkidena. Iga plokk, välja arvatud viimane, sisaldab fikseeritud arvu pakette. See on võrdne vastuvõtu/edastusakna suurusega. Viimases andmeplokis võib olla vähem pakette. Pärast iga ploki saatmist ootab saatja pool kättetoimetamise kinnitust või taotlust kadunud paketid uuesti kohale toimetada, jättes vastuvõtmise/edastusakna vastuste saamiseks avatuks. Pärast ploki kohaletoimetamise kinnituse saamist nihkub vastuvõtu/edastuse aken ja saadetakse järgmine andmeplokk.

Vastuvõttev pool võtab paketid vastu. Iga paketti kontrollitakse, et näha, kas see jääb edastusaknasse. Paketid ja duplikaadid, mis aknasse ei kuku, filtreeritakse välja. Sest Kui akna suurus on fikseeritud ja sama saajale ja saatjale, siis pakettide ploki kadudeta kohaletoimetamise korral nihutatakse aken järgmise andmeploki pakettide vastuvõtmiseks ja edastamise kinnitus. saadetud. Kui aken ei täitu töötaimeri määratud aja jooksul, siis alustatakse kontrolliga, milliseid pakette pole kohale toimetatud ja saadetakse uuesti kohaletoimetamise taotlused.
Taasedastusskeem:Usaldusväärse Udp-protokolli rakendamine .Neti jaoks

Aegumised ja protokollitaimerid

Põhjuseid, miks ühendust ei saa luua, on mitu. Näiteks kui vastuvõttev osapool on võrguühenduseta. Sel juhul suletakse ühendus ühenduse loomisel ajalõpu tõttu. Usaldusväärne UDP-rakendus kasutab ajalõpu määramiseks kahte taimerit. Esimest, töötaimerit, kasutatakse kaughosti vastuse ootamiseks. Kui see käivitub saatja poolel, saadetakse viimati saadetud pakett uuesti. Kui taimer aegub adressaadi juures, siis kontrollitakse kadunud pakette ja saadetakse uuesti kättetoimetamise taotlused.

Teist taimerit on vaja ühenduse sulgemiseks juhul, kui sõlmedevaheline side puudub. Saatja poolel käivitub see kohe pärast töötaimeri aegumist ja ootab kaugsõlme vastust. Kui määratud aja jooksul vastust ei saada, siis ühendus katkestatakse ja ressursid vabastatakse. Vastuvõtva poole jaoks käivitatakse ühenduse sulgemise taimer pärast seda, kui töötaimer aegub kaks korda. See on vajalik kinnituspaketi kaotsimineku vastu kindlustamiseks. Kui taimer aegub, katkeb ka ühendus ja vabastatakse ressursid.

Usaldusväärne UDP edastuse oleku diagramm

Protokolli põhimõtted realiseeritakse lõpliku olekuga masinas, mille iga olek vastutab teatud pakettide töötlemise loogika eest.
Usaldusväärne UDP olekudiagramm:

Usaldusväärse Udp-protokolli rakendamine .Neti jaoks

suletud - ei ole tegelikult olek, see on automaadi algus- ja lõpp-punkt. Riigi jaoks suletud võetakse vastu edastuse juhtplokk, mis asünkroonset UDP-serverit realiseerides saadab paketid edasi vastavatele ühendustele ja alustab olekutöötlust.

FirstPacket Sending – algolek, milles väljaminev ühendus on sõnumi saatmisel.

Selles olekus saadetakse esimene tavaliste sõnumite pakett. Ilma saatmiskinnituseta sõnumite puhul on see ainus olek, kus kogu sõnum saadetakse.

Saatmistsükkel – põhiolek sõnumipakettide edastamiseks.

Üleminek sellele osariigist FirstPacket Sending teostatakse pärast sõnumi esimese paketi saatmist. Selles olekus tulevad kõik kinnitused ja taasedastustaotlused. Sellest väljumine on võimalik kahel juhul – sõnumi eduka kohaletoimetamise korral või ajalõpu korral.

FirstPacketReceived – sõnumi adressaadi algseisund.

See kontrollib edastuse alguse õigsust, loob vajalikud struktuurid ja saadab esimese paketi kättesaamise kinnituse.

Sõnumi puhul, mis koosneb ühest paketist ja saadeti kättetoimetamistõendit kasutamata, on see ainus olek. Pärast sellise sõnumi töötlemist ühendus suletakse.

Kokkupanek – põhiolek sõnumipakettide vastuvõtmiseks.

See kirjutab paketid ajutisele salvestusruumile, kontrollib pakettide kadumist, saadab kinnitused pakettide ploki ja kogu sõnumi kohaletoimetamise kohta ning saadab taotlused kadunud pakettide uuesti kohaletoimetamiseks. Kogu sõnumi eduka vastuvõtmise korral läheb ühendus olekusse Lõpetatud, vastasel juhul aegub aeg.

Lõpetatud – ühenduse sulgemine kogu sõnumi eduka vastuvõtmise korral.

See olek on vajalik sõnumi kokkupanekuks ja juhuks, kui sõnumi kättetoimetamise kinnitus läks teel saatjani kaotsi. Sellest olekust väljub ajalõpp, kuid ühendus loetakse edukalt suletuks.

Sügavamale koodile. ülekande juhtseade

Usaldusväärse UDP üks põhielemente on edastuse juhtplokk. Selle ploki ülesanne on salvestada jooksvaid ühendusi ja abielemente, jaotada sissetulevad paketid vastavatele ühendustele, pakkuda liidest pakettide ühendusele saatmiseks ja rakendada protokolli API. Edastamise juhtplokk võtab UDP-kihilt vastu pakette ja edastab need töötlemiseks olekumasinale. Pakettide vastuvõtmiseks rakendab see asünkroonset UDP-serverit.
Mõned klassi ReliableUdpConnectionControlBlock liikmed:

internal class ReliableUdpConnectionControlBlock : IDisposable
{
  // массив байт для указанного ключа. Используется для сборки входящих сообщений    
  public ConcurrentDictionary<Tuple<EndPoint, Int32>, byte[]> IncomingStreams { get; private set;}
  // массив байт для указанного ключа. Используется для отправки исходящих сообщений.
  public ConcurrentDictionary<Tuple<EndPoint, Int32>, byte[]> OutcomingStreams { get; private set; }
  // connection record для указанного ключа.
  private readonly ConcurrentDictionary<Tuple<EndPoint, Int32>, ReliableUdpConnectionRecord> m_listOfHandlers;
  // список подписчиков на сообщения.
  private readonly List<ReliableUdpSubscribeObject> m_subscribers;    
  // локальный сокет    
  private Socket m_socketIn;
  // порт для входящих сообщений
  private int m_port;
  // локальный IP адрес
  private IPAddress m_ipAddress;    
  // локальная конечная точка    
  public IPEndPoint LocalEndpoint { get; private set; }    
  // коллекция предварительно инициализированных
  // состояний конечного автомата
  public StatesCollection States { get; private set; }
  // генератор случайных чисел. Используется для создания TransmissionId
  private readonly RNGCryptoServiceProvider m_randomCrypto;    	
  //...
}

Asünkroonse UDP-serveri juurutamine:

private void Receive()
{
  EndPoint connectedClient = new IPEndPoint(IPAddress.Any, 0);
  // создаем новый буфер, для каждого socket.BeginReceiveFrom 
  byte[] buffer = new byte[DefaultMaxPacketSize + ReliableUdpHeader.Length];
  // передаем буфер в качестве параметра для асинхронного метода
  this.m_socketIn.BeginReceiveFrom(buffer, 0, buffer.Length, SocketFlags.None, ref connectedClient, EndReceive, buffer);
}   

private void EndReceive(IAsyncResult ar)
{
  EndPoint connectedClient = new IPEndPoint(IPAddress.Any, 0);
  int bytesRead = this.m_socketIn.EndReceiveFrom(ar, ref connectedClient);
  //пакет получен, готовы принимать следующий        
  Receive();
  // т.к. простейший способ решить вопрос с буфером - получить ссылку на него 
  // из IAsyncResult.AsyncState        
  byte[] bytes = ((byte[]) ar.AsyncState).Slice(0, bytesRead);
  // получаем заголовок пакета        
  ReliableUdpHeader header;
  if (!ReliableUdpStateTools.ReadReliableUdpHeader(bytes, out header))
  {          
    // пришел некорректный пакет - отбрасываем его
    return;
  }
  // конструируем ключ для определения connection record’а для пакета
  Tuple<EndPoint, Int32> key = new Tuple<EndPoint, Int32>(connectedClient, header.TransmissionId);
  // получаем существующую connection record или создаем новую
  ReliableUdpConnectionRecord record = m_listOfHandlers.GetOrAdd(key, new ReliableUdpConnectionRecord(key, this, header.ReliableUdpMessageType));
  // запускаем пакет в обработку в конечный автомат
  record.State.ReceivePacket(record, header, bytes);
}

Iga sõnumiedastuse jaoks luuakse struktuur, mis sisaldab teavet ühenduse kohta. Sellist struktuuri nimetatakse ühenduse rekord.
Mõned klassi ReliableUdpConnectionRecord liikmed:

internal class ReliableUdpConnectionRecord : IDisposable
{    
  // массив байт с сообщением    
  public byte[] IncomingStream { get; set; }
  // ссылка на состояние конечного автомата    
  public ReliableUdpState State { get; set; }    
  // пара, однозначно определяющая connection record
  // в блоке управления передачей     
  public Tuple<EndPoint, Int32> Key { get; private set;}
  // нижняя граница приемного окна    
  public int WindowLowerBound;
  // размер окна передачи
  public readonly int WindowSize;     
  // номер пакета для отправки
  public int SndNext;
  // количество пакетов для отправки
  public int NumberOfPackets;
  // номер передачи (именно он и есть вторая часть Tuple)
  // для каждого сообщения свой	
  public readonly Int32 TransmissionId;
  // удаленный IP endpoint – собственно получатель сообщения
  public readonly IPEndPoint RemoteClient;
  // размер пакета, во избежание фрагментации на IP уровне
  // не должен превышать MTU – (IP.Header + UDP.Header + RelaibleUDP.Header)
  public readonly int BufferSize;
  // блок управления передачей
  public readonly ReliableUdpConnectionControlBlock Tcb;
  // инкапсулирует результаты асинхронной операции для BeginSendMessage/EndSendMessage
  public readonly AsyncResultSendMessage AsyncResult;
  // не отправлять пакеты подтверждения
  public bool IsNoAnswerNeeded;
  // последний корректно полученный пакет (всегда устанавливается в наибольший номер)
  public int RcvCurrent;
  // массив с номерами потерянных пакетов
  public int[] LostPackets { get; private set; }
  // пришел ли последний пакет. Используется как bool.
  public int IsLastPacketReceived = 0;
  //...
}

Sügavamale koodile. osariigid

Osariigid rakendavad Reliable UDP protokolli olekumasinat, kus toimub pakettide põhitöötlus. Abstraktne klass ReliableUdpState pakub oleku jaoks liidest:

Usaldusväärse Udp-protokolli rakendamine .Neti jaoks

Kogu protokolli loogikat rakendavad ülaltoodud klassid koos abiklassiga, mis pakub staatilisi meetodeid, nagu näiteks ReliableUdp päise konstrueerimine ühenduse kirjest.

Järgmisena käsitleme üksikasjalikult protokolli põhialgoritme määravate liidesemeetodite rakendamist.

DisposeByTimeout meetod

DisposeByTimeout meetod vastutab ühenduse ressursside vabastamise eest pärast ajalõpu ja signaali edastamise edukast/ebaõnnestumisest.
ReliableUdpState.DisposeByTimeout:

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

See on tühistatud ainult osariigis Lõpetatud.
Lõpetatud. DisposeByTimeout:

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

ProcessPacketsi meetod

Meetod ProcessPackets vastutab paketi või pakettide täiendava töötlemise eest. Helistatakse otse või paketiootetaimeri kaudu.

Seisundis Kokkupanek meetod on tühistatud ja vastutab kadunud pakettide kontrollimise ja olekusse ülemineku eest Lõpetatud, viimase paketi kättesaamise ja eduka kontrolli läbimise korral
Kokkupanek. Protsessipaketid:

public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
  if (connectionRecord.IsDone != 0)
    return;
  if (!ReliableUdpStateTools.CheckForNoPacketLoss(connectionRecord, connectionRecord.IsLastPacketReceived != 0))
  {
    // есть потерянные пакеты, отсылаем запросы на них
    foreach (int seqNum in connectionRecord.LostPackets)
    {
      if (seqNum != 0)
      {
        ReliableUdpStateTools.SendAskForLostPacket(connectionRecord, seqNum);
      }
    }
    // устанавливаем таймер во второй раз, для повторной попытки передачи
    if (!connectionRecord.TimerSecondTry)
    {
      connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
      connectionRecord.TimerSecondTry = true;
      return;
    }
    // если после двух попыток срабатываний WaitForPacketTimer 
    // не удалось получить пакеты - запускаем таймер завершения соединения
    StartCloseWaitTimer(connectionRecord);
  }
  else if (connectionRecord.IsLastPacketReceived != 0)
  // успешная проверка 
  {
    // высылаем подтверждение о получении блока данных
    ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
    connectionRecord.State = connectionRecord.Tcb.States.Completed;
    connectionRecord.State.ProcessPackets(connectionRecord);
    // вместо моментальной реализации ресурсов
    // запускаем таймер, на случай, если
    // если последний ack не дойдет до отправителя и он запросит его снова.
    // по срабатыванию таймера - реализуем ресурсы
    // в состоянии Completed метод таймера переопределен
    StartCloseWaitTimer(connectionRecord);
  }
  // это случай, когда ack на блок пакетов был потерян
  else
  {
    if (!connectionRecord.TimerSecondTry)
    {
      ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
      connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
      connectionRecord.TimerSecondTry = true;
      return;
    }
    // запускаем таймер завершения соединения
    StartCloseWaitTimer(connectionRecord);
  }
}

Seisundis Saatmistsükkel seda meetodit kutsutakse välja ainult taimeril ja see vastutab viimase sõnumi uuesti saatmise ning ühenduse sulgemise taimeri lubamise eest.
SendingCycle.ProcessPackets:

public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
  if (connectionRecord.IsDone != 0)
    return;        
  // отправляем повторно последний пакет 
  // ( в случае восстановления соединения узел-приемник заново отправит запросы, которые до него не дошли)        
  ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.RetransmissionCreateUdpPayload(connectionRecord, connectionRecord.SndNext - 1));
  // включаем таймер CloseWait – для ожидания восстановления соединения или его завершения
  StartCloseWaitTimer(connectionRecord);
}

Seisundis Lõpetatud meetod peatab töötava taimeri ja saadab sõnumi tellijatele.
Completed.ProcessPackets:

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

ReceivePacket meetod

Seisundis FirstPacketReceived meetodi põhiülesanne on kindlaks teha, kas esimene sõnumipakett ka tegelikult liidesesse jõudis, ning ka ühest paketist koosneva sõnumi kogumine.
FirstPacketReceived.ReceivePacket:

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  if (!header.Flags.HasFlag(ReliableUdpHeaderFlags.FirstPacket))
    // отбрасываем пакет
    return;
  // комбинация двух флагов - FirstPacket и LastPacket - говорит что у нас единственное сообщение
  if (header.Flags.HasFlag(ReliableUdpHeaderFlags.FirstPacket) &
      header.Flags.HasFlag(ReliableUdpHeaderFlags.LastPacket))
  {
    ReliableUdpStateTools.CreateMessageFromSinglePacket(connectionRecord, header, payload.Slice(ReliableUdpHeader.Length, payload.Length));
    if (!header.Flags.HasFlag(ReliableUdpHeaderFlags.NoAsk))
    {
      // отправляем пакет подтверждение          
      ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
    }
    SetAsCompleted(connectionRecord);
    return;
  }
  // by design все packet numbers начинаются с 0;
  if (header.PacketNumber != 0)          
    return;
  ReliableUdpStateTools.InitIncomingBytesStorage(connectionRecord, header);
  ReliableUdpStateTools.WritePacketData(connectionRecord, header, payload);
  // считаем кол-во пакетов, которые должны прийти
  connectionRecord.NumberOfPackets = (int)Math.Ceiling((double) ((double) connectionRecord.IncomingStream.Length/(double) connectionRecord.BufferSize));
  // записываем номер последнего полученного пакета (0)
  connectionRecord.RcvCurrent = header.PacketNumber;
  // после сдвинули окно приема на 1
  connectionRecord.WindowLowerBound++;
  // переключаем состояние
  connectionRecord.State = connectionRecord.Tcb.States.Assembling;
  // если не требуется механизм подтверждение
  // запускаем таймер который высвободит все структуры         
  if (header.Flags.HasFlag(ReliableUdpHeaderFlags.NoAsk))
  {
    connectionRecord.CloseWaitTimer = new Timer(DisposeByTimeout, connectionRecord, connectionRecord.ShortTimerPeriod, -1);
  }
  else
  {
    ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
    connectionRecord.WaitForPacketsTimer = new Timer(CheckByTimer, connectionRecord, connectionRecord.ShortTimerPeriod, -1);
  }
}

Seisundis Saatmistsükkel see meetod alistatakse kohaletoimetamise kinnituste ja kordusedastustaotluste 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;
  // расчет конечной границы окна
  // берется граница окна + 1, для получения подтверждений доставки
  int windowHighestBound = Math.Min((connectionRecord.WindowLowerBound + connectionRecord.WindowSize), (connectionRecord.NumberOfPackets));
  // проверка на попадание в окно        
  if (header.PacketNumber < connectionRecord.WindowLowerBound || header.PacketNumber > windowHighestBound)
    return;
  connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
  if (connectionRecord.CloseWaitTimer != null)
    connectionRecord.CloseWaitTimer.Change(-1, -1);
  // проверить на последний пакет:
  if (header.PacketNumber == connectionRecord.NumberOfPackets)
  {
    // передача завершена
    Interlocked.Increment(ref connectionRecord.IsDone);
    SetAsCompleted(connectionRecord);
    return;
  }
  // это ответ на первый пакет c подтверждением         
  if ((header.Flags.HasFlag(ReliableUdpHeaderFlags.FirstPacket) && header.PacketNumber == 1))
  {
    // без сдвига окна
    SendPacket(connectionRecord);
  }
  // пришло подтверждение о получении блока данных
  else if (header.PacketNumber == windowHighestBound)
  {
    // сдвигаем окно прием/передачи
    connectionRecord.WindowLowerBound += connectionRecord.WindowSize;
    // обнуляем массив контроля передачи
    connectionRecord.WindowControlArray.Nullify();
    // отправляем блок пакетов
    SendPacket(connectionRecord);
  }
  // это запрос на повторную передачу – отправляем требуемый пакет          
  else
    ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.RetransmissionCreateUdpPayload(connectionRecord, header.PacketNumber));
}

Seisundis Kokkupanek ReceivePacket meetodil toimub sissetulevatest pakettidest sõnumi kokkupanemise põhitöö.
Kokkupanek.Paki vastuvõtmine:

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  if (connectionRecord.IsDone != 0)
    return;
  // обработка пакетов с отключенным механизмом подтверждения доставки
  if (header.Flags.HasFlag(ReliableUdpHeaderFlags.NoAsk))
  {
    // сбрасываем таймер
    connectionRecord.CloseWaitTimer.Change(connectionRecord.LongTimerPeriod, -1);
    // записываем данные
    ReliableUdpStateTools.WritePacketData(connectionRecord, header, payload);
    // если получили пакет с последним флагом - делаем завершаем          
    if (header.Flags.HasFlag(ReliableUdpHeaderFlags.LastPacket))
    {
      connectionRecord.State = connectionRecord.Tcb.States.Completed;
      connectionRecord.State.ProcessPackets(connectionRecord);
    }
    return;
  }        
  // расчет конечной границы окна
  int windowHighestBound = Math.Min((connectionRecord.WindowLowerBound + connectionRecord.WindowSize - 1), (connectionRecord.NumberOfPackets - 1));
  // отбрасываем не попадающие в окно пакеты
  if (header.PacketNumber < connectionRecord.WindowLowerBound || header.PacketNumber > (windowHighestBound))
    return;
  // отбрасываем дубликаты
  if (connectionRecord.WindowControlArray.Contains(header.PacketNumber))
    return;
  // записываем данные 
  ReliableUdpStateTools.WritePacketData(connectionRecord, header, payload);
  // увеличиваем счетчик пакетов        
  connectionRecord.PacketCounter++;
  // записываем в массив управления окном текущий номер пакета        
  connectionRecord.WindowControlArray[header.PacketNumber - connectionRecord.WindowLowerBound] = header.PacketNumber;
  // устанавливаем наибольший пришедший пакет        
  if (header.PacketNumber > connectionRecord.RcvCurrent)
    connectionRecord.RcvCurrent = header.PacketNumber;
  // перезапускам таймеры        
  connectionRecord.TimerSecondTry = false;
  connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
  if (connectionRecord.CloseWaitTimer != null)
    connectionRecord.CloseWaitTimer.Change(-1, -1);
  // если пришел последний пакет
  if (header.Flags.HasFlag(ReliableUdpHeaderFlags.LastPacket))
  {
    Interlocked.Increment(ref connectionRecord.IsLastPacketReceived);
  }
  // если нам пришли все пакеты окна, то сбрасываем счетчик
  // и высылаем пакет подтверждение
  else if (connectionRecord.PacketCounter == connectionRecord.WindowSize)
  {
    // сбрасываем счетчик.      
    connectionRecord.PacketCounter = 0;
    // сдвинули окно передачи
    connectionRecord.WindowLowerBound += connectionRecord.WindowSize;
    // обнуление массива управления передачей
    connectionRecord.WindowControlArray.Nullify();
    ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
  }
  // если последний пакет уже имеется        
  if (Thread.VolatileRead(ref connectionRecord.IsLastPacketReceived) != 0)
  {
    // проверяем пакеты          
    ProcessPackets(connectionRecord);
  }
}

Seisundis Lõpetatud meetodi ainus ülesanne on saata korduskinnitus sõnumi edukast kohaletoimetamisest.
Completed.ReceivePacket:

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

Paketi saatmise meetod

Seisundis FirstPacket Sending 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;       
  // если подтверждения не требуется - отправляем все пакеты
  // и высвобождаем ресурсы
  if (connectionRecord.IsNoAnswerNeeded)
  {
    // Здесь происходит отправка As Is
    do
    {
      ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.CreateUdpPayload(connectionRecord, ReliableUdpStateTools. CreateReliableUdpHeader(connectionRecord)));
      connectionRecord.SndNext++;
    } while (connectionRecord.SndNext < connectionRecord.NumberOfPackets);
    SetAsCompleted(connectionRecord);
    return;
  }
  // создаем заголовок пакета и отправляем его 
  ReliableUdpHeader header = ReliableUdpStateTools.CreateReliableUdpHeader(connectionRecord);
  ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.CreateUdpPayload(connectionRecord, header));
  // увеличиваем счетчик
  connectionRecord.SndNext++;
  // сдвигаем окно
  connectionRecord.WindowLowerBound++;
  connectionRecord.State = connectionRecord.Tcb.States.SendingCycle;
  // Запускаем таймер
  connectionRecord.WaitForPacketsTimer = new Timer(CheckByTimer, connectionRecord, connectionRecord.ShortTimerPeriod, -1);
}

Seisundis Saatmistsükkel selle meetodi puhul saadetakse pakettide plokk.
SendingCycle.SendPacket:

public override void SendPacket(ReliableUdpConnectionRecord connectionRecord)
{      
  // отправляем блок пакетов      
  for (connectionRecord.PacketCounter = 0;
        connectionRecord.PacketCounter < connectionRecord.WindowSize &&
        connectionRecord.SndNext < connectionRecord.NumberOfPackets;
        connectionRecord.PacketCounter++)
  {
    ReliableUdpHeader header = ReliableUdpStateTools.CreateReliableUdpHeader(connectionRecord);
    ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.CreateUdpPayload(connectionRecord, header));
    connectionRecord.SndNext++;
  }
  // на случай большого окна передачи, перезапускаем таймер после отправки
  connectionRecord.WaitForPacketsTimer.Change( connectionRecord.ShortTimerPeriod, -1 );
  if ( connectionRecord.CloseWaitTimer != null )
  {
    connectionRecord.CloseWaitTimer.Change( -1, -1 );
  }
}

Sügavamale koodile. Ühenduste loomine ja loomine

Nüüd, kui oleme näinud põhiolekuid ja olekute käsitlemiseks kasutatavaid meetodeid, toome üksikasjalikumalt mõned näited selle kohta, kuidas protokoll toimib.
Andmeedastusskeem tavatingimustes:Usaldusväärse Udp-protokolli rakendamine .Neti jaoks

Mõelge loomisele üksikasjalikult ühenduse rekord ühenduse loomiseks ja esimese paketi saatmiseks. Edastamise algatab alati rakendus, mis kutsub sõnumi saatmise API-le. Järgmisena käivitatakse edastuse juhtploki meetod StartTransmission, mis alustab andmete edastamist uue sõnumi jaoks.
Väljuva ühenduse loomine:

private void StartTransmission(ReliableUdpMessage reliableUdpMessage, EndPoint endPoint, AsyncResultSendMessage asyncResult)
{
  if (m_isListenerStarted == 0)
  {
    if (this.LocalEndpoint == null)
    {
      throw new ArgumentNullException( "", "You must use constructor with parameters or start listener before sending message" );
    }
    // запускаем обработку входящих пакетов
    StartListener(LocalEndpoint);
  }
  // создаем ключ для словаря, на основе EndPoint и ReliableUdpHeader.TransmissionId        
  byte[] transmissionId = new byte[4];
  // создаем случайный номер transmissionId        
  m_randomCrypto.GetBytes(transmissionId);
  Tuple<EndPoint, Int32> key = new Tuple<EndPoint, Int32>(endPoint, BitConverter.ToInt32(transmissionId, 0));
  // создаем новую запись для соединения и проверяем, 
  // существует ли уже такой номер в наших словарях
  if (!m_listOfHandlers.TryAdd(key, new ReliableUdpConnectionRecord(key, this, reliableUdpMessage, asyncResult)))
  {
    // если существует – то повторно генерируем случайный номер 
    m_randomCrypto.GetBytes(transmissionId);
    key = new Tuple<EndPoint, Int32>(endPoint, BitConverter.ToInt32(transmissionId, 0));
    if (!m_listOfHandlers.TryAdd(key, new ReliableUdpConnectionRecord(key, this, reliableUdpMessage, asyncResult)))
      // если снова не удалось – генерируем исключение
      throw new ArgumentException("Pair TransmissionId & EndPoint is already exists in the dictionary");
  }
  // запустили состояние в обработку         
  m_listOfHandlers[key].State.SendPacket(m_listOfHandlers[key]);
}

Esimese paketi saatmine (FirstPacketSending olek):

public override void SendPacket(ReliableUdpConnectionRecord connectionRecord)
{
  connectionRecord.PacketCounter = 0;
  connectionRecord.SndNext = 0;
  connectionRecord.WindowLowerBound = 0;       
  // ... 
  // создаем заголовок пакета и отправляем его 
  ReliableUdpHeader header = ReliableUdpStateTools.CreateReliableUdpHeader(connectionRecord);
  ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.CreateUdpPayload(connectionRecord, header));
  // увеличиваем счетчик
  connectionRecord.SndNext++;
  // сдвигаем окно
  connectionRecord.WindowLowerBound++;
  // переходим в состояние SendingCycle
  connectionRecord.State = connectionRecord.Tcb.States.SendingCycle;
  // Запускаем таймер
  connectionRecord.WaitForPacketsTimer = new Timer(CheckByTimer, connectionRecord, connectionRecord.ShortTimerPeriod, -1);
}

Pärast esimese paketi saatmist siseneb saatja olekusse Saatmistsükkel – oodake kinnitust paki kohaletoimetamise kohta.
Vastuvõttev pool, kasutades EndReceive meetodit, võtab saadetud paketi vastu, loob uue ühenduse rekord ja edastab selle paketi koos eelnevalt parsitud päisega töötlemiseks oleku ReceivePacket meetodile FirstPacketReceived
Ühenduse loomine vastuvõtval poolel:

private void EndReceive(IAsyncResult ar)
{
  // ...
  // пакет получен
  // парсим заголовок пакета        
  ReliableUdpHeader header;
  if (!ReliableUdpStateTools.ReadReliableUdpHeader(bytes, out header))
  {          
    // пришел некорректный пакет - отбрасываем его
    return;
  }
  // конструируем ключ для определения connection record’а для пакета
  Tuple<EndPoint, Int32> key = new Tuple<EndPoint, Int32>(connectedClient, header.TransmissionId);
  // получаем существующую connection record или создаем новую
  ReliableUdpConnectionRecord record = m_listOfHandlers.GetOrAdd(key, new ReliableUdpConnectionRecord(key, this, header. ReliableUdpMessageType));
  // запускаем пакет в обработку в конечный автомат
  record.State.ReceivePacket(record, header, bytes);
}

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

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  if (!header.Flags.HasFlag(ReliableUdpHeaderFlags.FirstPacket))
    // отбрасываем пакет
    return;
  // ...
  // by design все packet numbers начинаются с 0;
  if (header.PacketNumber != 0)          
    return;
  // инициализируем массив для хранения частей сообщения
  ReliableUdpStateTools.InitIncomingBytesStorage(connectionRecord, header);
  // записываем данные пакет в массив
  ReliableUdpStateTools.WritePacketData(connectionRecord, header, payload);
  // считаем кол-во пакетов, которые должны прийти
  connectionRecord.NumberOfPackets = (int)Math.Ceiling((double) ((double) connectionRecord.IncomingStream.Length/(double) connectionRecord.BufferSize));
  // записываем номер последнего полученного пакета (0)
  connectionRecord.RcvCurrent = header.PacketNumber;
  // после сдвинули окно приема на 1
  connectionRecord.WindowLowerBound++;
  // переключаем состояние
  connectionRecord.State = connectionRecord.Tcb.States.Assembling;  
  if (/*если не требуется механизм подтверждение*/)
  // ...
  else
  {
    // отправляем подтверждение
    ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
    connectionRecord.WaitForPacketsTimer = new Timer(CheckByTimer, connectionRecord, connectionRecord.ShortTimerPeriod, -1);
  }
}

Sügavamale koodile. Ühenduse sulgemine ajalõpu ajal

Ajalõpu käsitlemine on usaldusväärse UDP oluline osa. Vaatleme näidet, kus vahepealne sõlm ebaõnnestus ja andmete edastamine mõlemas suunas muutus võimatuks.
Skeem ühenduse sulgemiseks ajalõpu järgi:Usaldusväärse Udp-protokolli rakendamine .Neti jaoks

Nagu diagrammil näha, hakkab saatja töötaimer kohe peale pakettide ploki saatmist. See juhtub oleku SendPacket meetodil Saatmistsükkel.
Töötaimeri lubamine (SendingCycle'i olek):

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

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

Sissetuleva ühenduse korral käivitub taimer pärast viimase sissetuleva andmepaketi vastuvõtmist, see juhtub oleku meetodis ReceivePacket Kokkupanek
Töötaimeri lubamine (kokkupaneku olek):

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  // ... 
  // перезапускаем таймеры        
  connectionRecord.TimerSecondTry = false;
  connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
  if (connectionRecord.CloseWaitTimer != null)
    connectionRecord.CloseWaitTimer.Change(-1, -1);
  // ...
}

Töötaimerit oodates ei saabunud sissetulevale ühendusele enam pakette. Taimer läks tööle ja kutsus välja ProcessPackets meetodi, kus kaotsiläinud paketid leiti ja esimest korda saadeti uuesti kohaletoimetamise taotlused.
Uuesti kohaletoimetamise taotluste saatmine (kokkupaneku olek):

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

Muutuja TimerSecondTry on seatud väärtusele tõsi. See muutuja vastutab töötaimeri taaskäivitamise eest.

Saatja poolel käivitub ka töötaimer ja saadetakse uuesti viimati saadetud pakett.
Ühenduse sulgemise taimeri lubamine (SendingCycle'i olek):

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

Pärast seda käivitub ühenduse sulgemise taimer väljuvas ühenduses.
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 ajalõpu periood on vaikimisi 30 sekundit.

Mõne aja pärast lülitub adressaadi poolne töötaimer uuesti sisse, päringud saadetakse uuesti, misjärel käivitub sissetuleva ühenduse jaoks ühenduse sulgemise taimer

Kui sulgemistaimerid käivituvad, vabastatakse mõlema ühenduse kirje kõik ressursid. Saatja teatab kohaletoimetamise ebaõnnestumisest ülesvoolu rakendusele (vaadake Usaldusväärne UDP API).
Ühenduse kirje 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ügavamale koodile. Andmeedastuse taastamine

Andmeedastuse taastamise diagramm pakettide kadumise korral:Usaldusväärse Udp-protokolli rakendamine .Neti jaoks

Nagu juba ühenduse sulgemisel ajalõpu puhul arutatud, kontrollib vastuvõtja töötaimeri aegumisel kadunud pakette. Paketi kaotsimineku korral koostatakse nimekiri pakettide arvust, mis adressaadini ei jõudnud. Need numbrid sisestatakse konkreetse ühenduse massiivi LostPackets ja saadetakse uuesti kohaletoimetamise taotlused.
Pakkide uuesti kättetoimetamise taotluste saatmine (kokkupaneku olek):

public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
  //...
  if (!ReliableUdpStateTools.CheckForNoPacketLoss(connectionRecord, connectionRecord.IsLastPacketReceived != 0))
  {
    // есть потерянные пакеты, отсылаем запросы на них
    foreach (int seqNum in connectionRecord.LostPackets)
    {
      if (seqNum != 0)
      {
        ReliableUdpStateTools.SendAskForLostPacket(connectionRecord, seqNum);
      }
    }
    // ...
  }
}

Saatja võtab uuesti kohaletoimetamise taotluse vastu ja saadab puuduvad paketid. Väärib märkimist, et sel hetkel on saatja ühenduse sulgemise taimerit juba käivitanud ja kui päring saabub, see lähtestatakse.
Kadunud pakettide uuesti saatmine (SendingCycle'i olek):

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  // ...
  connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
  // сброс таймера закрытия соединения 
  if (connectionRecord.CloseWaitTimer != null)
    connectionRecord.CloseWaitTimer.Change(-1, -1);
  // ...
  // это запрос на повторную передачу – отправляем требуемый пакет          
  else
    ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.RetransmissionCreateUdpPayload(connectionRecord, header.PacketNumber));
}

Sissetulev ühendus võtab vastu uuesti saadetud paketi (skeemil pakett nr 3). Kontrollitakse, kas vastuvõtuaken on täis ja normaalne andmeedastus taastub.
Vastuvõtuaknas tabamuste kontrollimine (kokkupaneku olek):

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  // ...
  // увеличиваем счетчик пакетов        
  connectionRecord.PacketCounter++;
  // записываем в массив управления окном текущий номер пакета        
  connectionRecord.WindowControlArray[header.PacketNumber - connectionRecord.WindowLowerBound] = header.PacketNumber;
  // устанавливаем наибольший пришедший пакет        
  if (header.PacketNumber > connectionRecord.RcvCurrent)
    connectionRecord.RcvCurrent = header.PacketNumber;
  // перезапускам таймеры        
  connectionRecord.TimerSecondTry = false;
  connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
  if (connectionRecord.CloseWaitTimer != null)
    connectionRecord.CloseWaitTimer.Change(-1, -1);
  // ...
  // если нам пришли все пакеты окна, то сбрасываем счетчик
  // и высылаем пакет подтверждение
  else if (connectionRecord.PacketCounter == connectionRecord.WindowSize)
  {
    // сбрасываем счетчик.      
    connectionRecord.PacketCounter = 0;
    // сдвинули окно передачи
    connectionRecord.WindowLowerBound += connectionRecord.WindowSize;
    // обнуление массива управления передачей
    connectionRecord.WindowControlArray.Nullify();
    ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
  }
  // ...
}

Usaldusväärne UDP API

Andmeedastusprotokolliga suhtlemiseks on avatud Reliable Udp klass, mis on edastuse juhtploki ümbris. Siin on klassi kõige olulisemad liikmed:

public sealed class ReliableUdp : IDisposable
{
  // получает локальную конечную точку
  public IPEndPoint LocalEndpoint    
  // создает экземпляр ReliableUdp и запускает
  // прослушивание входящих пакетов на указанном IP адресе
  // и порту. Значение 0 для порта означает использование
  // динамически выделенного порта
  public ReliableUdp(IPAddress localAddress, int port = 0) 
  // подписка на получение входящих сообщений
  public ReliableUdpSubscribeObject SubscribeOnMessages(ReliableUdpMessageCallback callback, ReliableUdpMessageTypes messageType = ReliableUdpMessageTypes.Any, IPEndPoint ipEndPoint = null)    
  // отписка от получения сообщений
  public void Unsubscribe(ReliableUdpSubscribeObject subscribeObject)
  // асинхронно отправить сообщение 
  // Примечание: совместимость с XP и Server 2003 не теряется, т.к. используется .NET Framework 4.0
  public Task<bool> SendMessageAsync(ReliableUdpMessage reliableUdpMessage, IPEndPoint remoteEndPoint, CancellationToken cToken)
  // начать асинхронную отправку сообщения
  public IAsyncResult BeginSendMessage(ReliableUdpMessage reliableUdpMessage, IPEndPoint remoteEndPoint, AsyncCallback asyncCallback, Object state)
  // получить результат асинхронной отправки
  public bool EndSendMessage(IAsyncResult asyncResult)  
  // очистить ресурсы
  public void Dispose()    
}

Sõnumid võetakse vastu tellimuse alusel. Tagasihelistamise meetodi delegeeritud allkiri:

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

Sõnum:

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

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

Sõnumi tüübid:

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

Sõnum saadetakse asünkroonselt, selleks rakendab protokoll asünkroonset programmeerimismudelit:

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

Sõnumi saatmise tulemus on tõene - kui sõnum jõudis adressaadini ja väär - kui ühendus katkes ajalõpu tõttu:

public bool EndSendMessage(IAsyncResult asyncResult)

Järeldus

Selles artiklis pole palju kirjeldatud. Lõime sobitamise mehhanismid, erandite ja vigade käsitlemine, asünkroonsete sõnumite saatmise meetodite rakendamine. Kuid protokolli tuum, pakettide töötlemise, ühenduse loomise ja ajalõppude käsitlemise loogika kirjeldus peaks olema teile selge.

Usaldusväärse tarneprotokolli demonstreeritud versioon on piisavalt tugev ja paindlik, et vastata eelnevalt määratletud nõuetele. Kuid tahan lisada, et kirjeldatud teostust saab parandada. Näiteks läbilaskevõime suurendamiseks ja taimeri perioodide dünaamiliseks muutmiseks saab protokolli lisada mehhanisme, nagu liugaken ja RTT, samuti on kasulik rakendada mehhanismi MTU määramiseks ühenduse sõlmede vahel (kuid ainult siis, kui saadetakse suuri sõnumeid). .

Tänan tähelepanu eest, ootan teie kommentaare ja kommentaare.

PS Neile, kes on huvitatud üksikasjadest või soovivad lihtsalt protokolli testida, link projektile GitHube'is:
Usaldusväärne UDP projekt

Kasulikud lingid ja artiklid

  1. TCP-protokolli spetsifikatsioon: inglise keeles и на русском
  2. UDP-protokolli spetsifikatsioon: inglise keeles и на русском
  3. RUDP-protokolli arutelu: draft-ietf-sigtran-reliable-udp-00
  4. Usaldusväärne andmeprotokoll: RFF 908 и RFF 1151
  5. Tarnekinnituse lihtne rakendamine UDP kaudu: Võtke .NET-i ja UDP-ga oma võrkude üle täielik kontroll
  6. Artikkel, mis kirjeldab NAT-i läbimise mehhanisme: Võrgusuhtlus võrguaadresside tõlkijate vahel
  7. Asünkroonse programmeerimismudeli rakendamine: CLR asünkroonse programmeerimise mudeli rakendamine и Kuidas rakendada IAsyncResulti disainimustrit
  8. Asünkroonse programmeerimismudeli teisaldamine ülesandepõhisele asünkroonsusele (APM TAP-is):
    TPL ja traditsiooniline .NET asünkroonne programmeerimine
    Koostöö teiste asünkroonsete mustrite ja tüüpidega

Värskendus: aitäh linnapea и sidristij liidesele ülesande lisamise idee eest. Teegi ühilduvust vanade operatsioonisüsteemidega ei rikuta, sest Neljas raamistik toetab nii XP kui ka 4 serverit.

Allikas: www.habr.com

Lisa kommentaar