Efektivigo de la Fidinda Udp-protokolo por .Net

Interreto ŝanĝiĝis antaŭ longe. Unu el la ĉefaj protokoloj de la Interreto - UDP estas uzata de aplikaĵoj ne nur por liveri datagramojn kaj elsendojn, sed ankaŭ por provizi "kunulajn" konektojn inter retaj nodoj. Pro sia simpla dezajno, ĉi tiu protokolo havas multajn antaŭe neplanitajn uzojn, tamen la mankoj de la protokolo, kiel la manko de garantiita livero, ne malaperis ie ajn. Ĉi tiu artikolo priskribas la efektivigon de la garantiita livera protokolo super UDP.
Enhavo:eniro
Protokolaj Postuloj
Fidinda UDP-kapo
Ĝeneralaj principoj de la protokolo
Tempotempoj kaj protokolaj temporiziloj
Fidinda UDP dissenda stato diagramo
Pli profunde en la kodon. transmisio-kontrolunuo
Pli profunde en la kodon. ŝtatoj

Pli profunde en la kodon. Krei kaj Establi Ligojn
Pli profunde en la kodon. Fermante la konekton ĉe tempoforigo
Pli profunde en la kodon. Restarigi datumojn
Fidinda UDP API
konkludo
Utilaj ligiloj kaj artikoloj

eniro

La origina arkitekturo de la Interreto supozis homogenan adresspacon en kiu ĉiu nodo havis tutmondan kaj unikan IP-adreson kaj povis komuniki rekte kun aliaj nodoj. Nun la Interreto, fakte, havas malsaman arkitekturon - unu areon de tutmondaj IP-adresoj kaj multajn areojn kun privataj adresoj kaŝitaj malantaŭ NAT-aparatoj.En ĉi tiu arkitekturo, nur aparatoj en la tutmonda adresspaco povas facile komuniki kun iu ajn en la reto ĉar ili havas unikan, tutmonde enrutigebla IP-adreso. Nodo sur privata reto povas ligi al aliaj nodoj sur la sama reto, kaj ankaŭ povas ligi al aliaj konataj nodoj en la tutmonda adresspaco. Tiu interagado estas atingita plejparte pro la ret-adrestraduka mekanismo. NAT-aparatoj, kiel Wi-Fi-enkursigiloj, kreas specialajn traduktabelajn enskribojn por elirantaj konektoj kaj modifas IP-adresojn kaj havennumerojn en pakaĵoj. Ĉi tio permesas eksiĝintajn ligojn de la privata reto al gastigantoj en la tutmonda adresspaco. Sed samtempe, NAT-aparatoj kutime blokas la tutan envenantan trafikon krom se apartaj reguloj por envenantaj konektoj estas fiksitaj.

Ĉi tiu arkitekturo de la Interreto estas sufiĉe ĝusta por komunikado kliento-servilo, kie klientoj povas esti en privataj retoj, kaj serviloj havas tutmondan adreson. Sed ĝi kreas malfacilaĵojn por la rekta ligo de du nodoj inter diversaj privataj retoj. Rekta konekto inter du nodoj estas grava por kunul-al-kunulaj aplikoj kiel ekzemple voĉtranssendo (Skype), akirante malproksiman aliron al komputilo (TeamViewer), aŭ interreta videoludado.

Unu el la plej efikaj metodoj por establi kunul-al-kunulan ligon inter aparatoj sur malsamaj privataj retoj estas nomita truotruado. Tiu tekniko estas plej ofte uzita kun aplikoj bazitaj sur la UDP-protokolo.

Sed se via aplikaĵo bezonas garantian liveron de datumoj, ekzemple, vi transdonas dosierojn inter komputiloj, tiam uzado de UDP havos multajn malfacilaĵojn pro la fakto, ke UDP ne estas garantiita livera protokolo kaj ne provizas pakaĵeton en ordo, male al la TCP. protokolo.

En ĉi tiu kazo, por certigi garantiitan pakaĵeton, necesas efektivigi aplikan tavolprotokolon kiu disponigas la necesan funkciecon kaj funkcias super UDP.

Mi volas tuj rimarki, ke ekzistas TCP-trua truotekniko por establi TCP-konektojn inter nodoj en malsamaj privataj retoj, sed pro la manko de subteno por ĝi de multaj NAT-aparatoj, ĝi kutime ne estas konsiderata kiel la ĉefa maniero por konekti. tiaj nodoj.

Por la resto de ĉi tiu artikolo, mi koncentriĝos nur pri la efektivigo de la garantiita livera protokolo. La efektivigo de la UDP-trua trua tekniko estos priskribita en la sekvaj artikoloj.

Protokolaj Postuloj

  1. Fidinda pakaĵeto efektivigita per pozitiva retromekanismo (la tielnomita pozitiva agnosko)
  2. La bezono de efika translokigo de grandaj datumoj, t.e. la protokolo devas eviti nenecesan pakaĵeton
  3. Devus esti eble nuligi la liveran konfirmmekanismon (la kapablo funkcii kiel "pura" UDP-protokolo)
  4. Kapablo efektivigi komandan reĝimon, kun konfirmo de ĉiu mesaĝo
  5. La baza unuo de datumtransigo super la protokolo devas esti mesaĝo

Ĉi tiuj postuloj plejparte koincidas kun la postuloj pri Fidindaj Datumoj pri Protokolo priskribitaj en RFC 908 и RFC 1151, kaj mi fidis je tiuj normoj dum disvolvado de ĉi tiu protokolo.

Por kompreni ĉi tiujn postulojn, ni rigardu la tempon de datumtransigo inter du retaj nodoj uzante la TCP kaj UDP-protokolojn. Lasu en ambaŭ kazoj ni havos unu paketon perdita.
Transdono de ne-interagaj datumoj tra TCP:Efektivigo de la Fidinda Udp-protokolo por .Net

Kiel vi povas vidi de la diagramo, en kazo de paka perdo, TCP detektos la perditan pakon kaj raportos ĝin al la sendinto petante la nombron de la perdita segmento.
Transdono de datumoj per UDP-protokolo:Efektivigo de la Fidinda Udp-protokolo por .Net

UDP ne faras iujn ajn perdan detektajn paŝojn. Kontrolo de dissendaj eraroj en la UDP-protokolo estas tute la respondeco de la aplikaĵo.

Erardetekto en la TCP-protokolo estas atingita establante ligon kun finnodo, stokante la staton de tiu ligo, indikante la nombron da bajtoj senditaj en ĉiu pakaĵetokapo, kaj sciigante kvitancojn uzante agnoskan nombron.

Aldone, por plibonigi rendimenton (t.e. sendante pli ol unu segmenton sen ricevado de agnosko), la TCP-protokolo uzas la tielnomitan dissendfenestron - la nombron da bajtoj da datenoj kiujn la sendinto de la segmento atendas ricevi.

Por pliaj informoj pri la TCP-protokolo, vidu RFC 793, de UDP al RFC 768kie, fakte, ili estas difinitaj.

De ĉi-supra, estas klare, ke por krei fidindan mesaĝan protokolon per UDP (ĉi-poste nomata kiel Fidinda UDP), estas postulate efektivigi datumtransigajn mekanismojn similajn al TCP. Nome:

  • konservi konektan staton
  • uzi segmentan numeradon
  • uzu specialajn konfirmpakaĵojn
  • uzu simpligitan fenestromekanismon por pliigi protokoltrafluon

Aldone, vi bezonas:

  • signali la komencon de mesaĝo, por asigni rimedojn por la konekto
  • signali la finon de mesaĝo, por transdoni la ricevitan mesaĝon al la kontraŭflua aplikaĵo kaj liberigi protokolresursojn
  • permesi al la lig-specifa protokolo malfunkciigi la liverkonfirmmekanismon funkcii kiel "pura" UDP

Fidinda UDP-kapo

Memoru, ke UDP-dagramo estas enkapsuligita en IP-dagramo. La Reliable UDP-pakaĵo estas konvene "envolvita" en UDP-dagramon.
Fidinda UDP-kapa enkapsuligo:Efektivigo de la Fidinda Udp-protokolo por .Net

La strukturo de la fidinda UDP-kapo estas sufiĉe simpla:

Efektivigo de la Fidinda Udp-protokolo por .Net

  • Flagoj - pakaĵkontrolflagoj
  • MessageType - mesaĝtipo uzata de kontraŭfluaj aplikaĵoj por aboni specifajn mesaĝojn
  • TransmissionId - la nombro de la dissendo, kune kun la adreso kaj haveno de la ricevanto, unike identigas la konekton
  • PacketNumber - paka nombro
  • Opcioj - pliaj protokolaj opcioj. En la kazo de la unua pako, ĝi estas uzata por indiki la grandecon de la mesaĝo

Flagoj estas kiel sekvas:

  • FirstPacket - la unua pako de la mesaĝo
  • NoAsk - la mesaĝo ne postulas agnoskan mekanismon por esti ebligita
  • LastPacket - la lasta pako de la mesaĝo
  • RequestForPacket - konfirma pako aŭ peto pri perdita pako

Ĝeneralaj principoj de la protokolo

Ĉar Fidinda UDP estas koncentrita sur garantiita mesaĝtransdono inter du nodoj, ĝi devas povi establi ligon kun la alia flanko. Por establi konekton, la sendinto sendas pakaĵeton kun la FirstPacket-flago, la respondo al kiu signifos ke la ligo estas establita. Ĉiuj respondpakaĵoj, aŭ, alivorte, agnoskaj pakoj, ĉiam starigas la valoron de la kampo PacketNumber al unu pli ol la plej granda PacketNumber-valoro de sukcese ricevitaj pakaĵetoj. La kampo Opcioj por la unua pako sendita estas la grandeco de la mesaĝo.

Simila mekanismo estas uzata por ĉesigi konekton. La flago LastPacket estas metita sur la lasta pako de la mesaĝo. En la respondpakaĵo, la nombro de la lasta pako + 1 estas indikita, kio por la ricevanta flanko signifas sukcesan liveron de la mesaĝo.
Konektoestablado kaj findiagramo:Efektivigo de la Fidinda Udp-protokolo por .Net

Kiam la konekto estas establita, datumtransigo komenciĝas. Datenoj estas transdonitaj en blokoj da pakaĵoj. Ĉiu bloko, krom la lasta, enhavas fiksan nombron da pakaĵoj. Ĝi estas egala al la ricevo/elsenda fenestrograndeco. La lasta bloko de datumoj eble havas malpli da pakaĵoj. Post sendado de ĉiu bloko, la senda flanko atendas liverkonfirmon aŭ peton por re-liveri perditajn pakaĵetojn, lasante la ricevi/elsendan fenestron malfermita por ricevi respondojn. Post ricevado de konfirmo pri bloka livero, la ricevo/elsenda fenestro ŝanĝas kaj la sekva bloko de datumoj estas sendita.

La ricevanta flanko ricevas la pakaĵojn. Ĉiu pako estas kontrolita por vidi ĉu ĝi falas ene de la dissenda fenestro. Pakoj kaj duplikatoj kiuj ne falas en la fenestron estas filtritaj. Ĉar Se la grandeco de la fenestro estas fiksita kaj la sama por la ricevanto kaj la sendinto, tiam en la kazo de bloko de pakaĵoj estas liverita sen perdo, la fenestro estas ŝanĝita por ricevi pakaĵetojn de la sekva bloko de datumoj kaj konfirmo de livero estas. sendis. Se la fenestro ne pleniĝas en la periodo fiksita de la labortempigilo, tiam kontrolo estos komencita pri kiuj pakoj ne estis liveritaj kaj petoj por reliverado estos senditaj.
Retransdona Diagramo:Efektivigo de la Fidinda Udp-protokolo por .Net

Tempotempoj kaj protokolaj temporiziloj

Estas pluraj kialoj kial konekto ne povas esti establita. Ekzemple, se la ricevanto estas eksterrete. En ĉi tiu kazo, kiam vi provas establi konekton, la konekto estos fermita per tempodaŭro. La Reliable UDP-efektivigo uzas du tempigilojn por agordi paŭzojn. La unua, la labortempigilo, estas uzata por atendi respondon de la fora gastiganto. Se ĝi pafas ĉe la sendinto, tiam la lasta sendita pako estas resendita. Se la tempigilo eksvalidiĝas ĉe la ricevanto, tiam kontrolo por perditaj pakaĵoj estas farita kaj petoj por relivero estas senditaj.

La dua tempigilo estas necesa por fermi la konekton en kazo de manko de komunikado inter la nodoj. Por la sendinto, ĝi komenciĝas tuj post kiam la labortempigilo eksvalidiĝas, kaj atendas respondon de la fora nodo. Se ne estas respondo ene de la specifita periodo, la konekto estas ĉesigita kaj rimedoj estas liberigitaj. Por la ricevanta flanko, la koneksa fermtempigilo estas komencita post kiam la labortempigilo eksvalidiĝas dufoje. Ĉi tio estas necesa por certigi kontraŭ la perdo de la konfirma pako. Kiam la tempigilo eksvalidiĝas, la konekto ankaŭ estas ĉesigita kaj resursoj estas liberigitaj.

Fidinda UDP dissenda stato diagramo

La principoj de la protokolo estas efektivigitaj en finhava ŝtatmaŝino, ĉiu stato de kiu respondecas pri certa logiko de pakaĵpretigo.
Fidinda UDP Ŝtata Diagramo:

Efektivigo de la Fidinda Udp-protokolo por .Net

Fermita - ne vere estas stato, ĝi estas komenca kaj finpunkto por la aŭtomato. Por ŝtato Fermita estas ricevita dissenda kontrolbloko, kiu, efektivigante nesinkronan UDP-servilon, plusendas pakaĵetojn al la taŭgaj ligoj kaj komencas ŝtatpretigon.

Unua Pako-Sendado – la komenca stato en kiu la elira konekto estas kiam la mesaĝo estas sendita.

En ĉi tiu stato, la unua pako por normalaj mesaĝoj estas sendita. Por mesaĝoj sen senda konfirmo, ĉi tiu estas la sola ŝtato kie la tuta mesaĝo estas sendita.

SendingCycle – baza stato por transdono de mesaĝpakaĵoj.

Transiro al ĝi de la ŝtato Unua Pako-Sendado efektivigita post kiam la unua pako de la mesaĝo estas sendita. Ĝuste en ĉi tiu stato venas ĉiuj agnoskoj kaj petoj por retransmisioj. Eliro el ĝi eblas en du kazoj - en kazo de sukcesa livero de la mesaĝo aŭ per tempodaŭro.

Unua Pako Ricevita – la komenca stato por la ricevanto de la mesaĝo.

Ĝi kontrolas la ĝustecon de la komenco de la dissendo, kreas la necesajn strukturojn, kaj sendas akcepton de ricevo de la unua pako.

Por mesaĝo kiu konsistas el ununura pako kaj estis sendita sen uzi pruvon de livero, ĉi tiu estas la sola ŝtato. Post prilaborado de tia mesaĝo, la konekto estas fermita.

Kunvenanta – baza stato por ricevi mesaĝajn pakojn.

Ĝi skribas pakaĵetojn al provizora stokado, kontrolas pakaĵetperdon, sendas agnoskojn por livero de bloko da pakaĵetoj kaj la tuta mesaĝo, kaj sendas petojn por relivero de perditaj pakaĵetoj. En kazo de sukcesa ricevo de la tuta mesaĝo, la konekto iras en la ŝtaton Kompletigita, alie, forpaso eliras.

Kompletigita – fermi la konekton en kazo de sukcesa ricevo de la tuta mesaĝo.

Ĉi tiu stato estas necesa por la kunigo de la mesaĝo kaj por la kazo kiam la liverokonfirmo de la mesaĝo estis perdita survoje al la sendinto. Ĉi tiu stato estas forigita per tempodaŭro, sed la konekto estas konsiderata sukcese fermita.

Pli profunde en la kodon. transmisio-kontrolunuo

Unu el la ŝlosilaj elementoj de Fidinda UDP estas la transdona kontrolo-bloko. La tasko de ĉi tiu bloko estas stoki aktualajn konektojn kaj helpajn elementojn, distribui envenajn pakaĵojn al la respondaj ligoj, provizi interfacon por sendi pakaĵetojn al konekto kaj efektivigi la protokolon API. La dissenda kontrolbloko ricevas pakaĵetojn de la UDP-tavolo kaj plusendas ilin al la ŝtatmaŝino por pretigo. Por ricevi pakaĵetojn, ĝi efektivigas nesinkronan UDP-servilon.
Kelkaj membroj de la ReliableUdpConnectionControlBlock klaso:

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

Efektivigo de nesinkrona UDP-servilo:

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

Por ĉiu mesaĝo transdono, strukturo estas kreita kiu enhavas informojn pri la konekto. Tia strukturo nomiĝas rekordo de konekto.
Kelkaj membroj de la ReliableUdpConnectionRecord klaso:

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

Pli profunde en la kodon. ŝtatoj

Ŝtatoj efektivigas la ŝtatmaŝinon de la Fidinda UDP-protokolo, kie okazas la ĉefa pretigo de pakoj. La abstrakta klaso ReliableUdpState disponigas interfacon por la ŝtato:

Efektivigo de la Fidinda Udp-protokolo por .Net

La tuta logiko de la protokolo estas efektivigita de la klasoj prezentitaj supre, kune kun helpa klaso, kiu provizas senmovajn metodojn, kiel ekzemple konstrui la kaplinion ReliableUdp el la koneksa registro.

Poste, ni konsideros detale la efektivigon de la interfacaj metodoj, kiuj determinas la bazajn algoritmojn de la protokolo.

DisposeByTimeout metodo

La metodo DisposeByTimeout respondecas pri liberigo de konektaj rimedoj post tempodaŭro kaj por signalado de sukcesa/malsukcesa liverado de mesaĝoj.
ReliableUdpState.DisposeByTimeout:

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

Ĝi estas nur forigita en la ŝtato Kompletigita.
Kompletigita.DisposeByTimeout:

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

ProcessPackets Metodo

La metodo ProcessPackets respondecas pri plia prilaborado de pakaĵo aŭ pakaĵoj. Vokita rekte aŭ per paka atendotempigilo.

Kapabla Kunvenanta la metodo estas anstataŭita kaj respondecas pri kontrolado de perditaj pakaĵetoj kaj transiro al la ŝtato Kompletigita, en kazo de ricevi la lastan paketon kaj trapasi sukcesan kontrolon
Asembling.ProcessPackets:

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

Kapabla SendingCycle ĉi tiu metodo estas vokita nur sur temporizilo, kaj respondecas pri resendi la lastan mesaĝon, same kiel ebligi la konekton proksima temporigilo.
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);
}

Kapabla Kompletigita la metodo haltigas la kurantan tempigilon kaj sendas la mesaĝon al la abonantoj.
Kompletigita.ProcessPackets:

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

ReceivePacket Metodo

Kapabla Unua Pako Ricevita la ĉefa tasko de la metodo estas determini ĉu la unua mesaĝpako efektive alvenis al la interfaco, kaj ankaŭ kolekti mesaĝon konsistantan el ununura pako.
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);
  }
}

Kapabla SendingCycle ĉi tiu metodo estas anstataŭita por akcepti liverajn agnoskojn kaj retranssendajn petojn.
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));
}

Kapabla Kunvenanta en la metodo ReceivePacket okazas la ĉefa laboro kunmeti mesaĝon el alvenantaj pakaĵoj.
Asemble.ReceivePacket:

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

Kapabla Kompletigita la sola tasko de la metodo estas sendi re-konon pri la sukcesa livero de la mesaĝo.
Kompletigita. Ricevu Pakon:

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

Sendi Paka Metodo

Kapabla Unua Pako-Sendado ĉi tiu metodo sendas la unuan paketon da datumoj, aŭ, se la mesaĝo ne postulas liverkonfirmon, la tutan mesaĝon.
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);
}

Kapabla SendingCycle en ĉi tiu metodo, bloko de pakoj estas sendita.
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 );
  }
}

Pli profunde en la kodon. Krei kaj Establi Ligojn

Nun kiam ni vidis la bazajn ŝtatojn kaj la metodojn uzatajn por trakti ŝtatojn, ni iom pli detale malkonstruu kelkajn ekzemplojn pri kiel la protokolo funkcias.
Diagramo de transdono de datumoj en normalaj kondiĉoj:Efektivigo de la Fidinda Udp-protokolo por .Net

Konsideru detale la kreadon rekordo de konekto por konekti kaj sendi la unuan paketon. La translokigo ĉiam estas iniciatita de la aplikaĵo, kiu nomas la sendi mesaĝon API. Poste, la metodo StartTransmission de la transdona kontrolbloko estas alvokita, kiu komencas la transdonon de datumoj por la nova mesaĝo.
Kreante eksiĝintan konekton:

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

Sendante la unuan paketon (FirstPacketSending-stato):

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

Post sendado de la unua pako, la sendinto eniras la ŝtaton SendingCycle – atendu konfirmon pri pak-livero.
La ricevanta flanko, uzante la EndReceive-metodon, ricevas la senditan pakaĵon, kreas novan rekordo de konekto kaj pasas ĉi tiun pakaĵeton, kun antaŭ-analiza kaplinio, al la metodo ReceivePacket de la ŝtato por prilaborado Unua Pako Ricevita
Kreante konekton ĉe la riceva flanko:

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

Ricevante la unuan pakaĵon kaj sendante agnoskon (FirstPacketReceived stato):

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

Pli profunde en la kodon. Fermante la konekton ĉe tempoforigo

Pritraktado de la tempodaŭro estas grava parto de Fidinda UDP. Konsideru ekzemplon, en kiu meza nodo malsukcesis kaj livero de datumoj en ambaŭ direktoj fariĝis neebla.
Diagramo por fermi konekton per tempotempo:Efektivigo de la Fidinda Udp-protokolo por .Net

Kiel videblas el la diagramo, la labortempigilo de la sendinto komenciĝas tuj post sendado de bloko da pakaĵoj. Ĉi tio okazas en la metodo SendPacket de la ŝtato SendingCycle.
Ebligante la labortempigilon (SendingCycle-stato):

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

La temporiziĝperiodoj estas fiksitaj kiam la konekto estas kreita. La defaŭlta ShortTimerPeriod estas 5 sekundoj. En la ekzemplo, ĝi estas agordita al 1,5 sekundoj.

Por envenanta konekto, la tempigilo komenciĝas post ricevo de la lasta envenanta datumpakaĵo, tio okazas en la metodo ReceivePacket de la ŝtato Kunvenanta
Ebligante la labortempigilon (Asemblea stato):

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

Ne pliaj pakoj alvenis al la envenanta konekto dum atendado de la funkcianta tempigilo. La tempigilo eksplodis kaj vokis la metodon ProcessPackets, kie la perditaj pakaĵoj estis trovitaj kaj reliveraj petoj estis senditaj por la unua fojo.
Sendado de reliveraj petoj (kunvena stato):

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

La variablo TimerSecondTry estas agordita al veraj. Ĉi tiu variablo respondecas pri rekomenco de la labortempigilo.

Flanke de la sendinto, la labortempigilo ankaŭ estas ekigita kaj la lasta sendita pako estas resendita.
Ebligante konektan fermtempigilon (SendingCycle-stato):

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

Post tio, la ferma temporigilo de konekto komenciĝas en la elira konekto.
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);
}

La tempoperiodo de fermo de konekto estas 30 sekundoj defaŭlte.

Post mallonga tempo, la labortempigilo ĉe la flanko de la ricevanto denove ekbrulas, petoj estas senditaj denove, post kio komenciĝas la fermo de konekto por la envenanta konekto.

Kiam la proksimaj temporiziloj ekfunkciigas, ĉiuj rimedoj de ambaŭ konektorekordoj estas liberigitaj. La sendinto raportas la malsukceson de livero al la kontraŭflua aplikaĵo (vidu Fidinda UDP-API).
Liberigante konektajn rekordajn rimedojn:

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

Pli profunde en la kodon. Restarigi datumojn

Diagramo de reakiro de transdono de datumoj en kazo de paka perdo:Efektivigo de la Fidinda Udp-protokolo por .Net

Kiel jam diskutite en fermo de la konekto ĉe tempoforigo, kiam la labortempigilo eksvalidiĝas, la ricevilo kontrolos por perditaj pakaĵoj. En kazo de paka perdo, listo de la nombro da pakoj kiuj ne atingis la ricevonton estos kompilita. Ĉi tiuj nombroj estas enigitaj en la LostPackets-aron de specifa konekto, kaj petoj por relivero estas senditaj.
Sendante petojn por reliveri pakaĵojn (Kunmunta stato):

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

La sendinto akceptos la reliveran peton kaj sendos la mankantajn pakaĵojn. Rimarkindas, ke ĉi-momente la sendinto jam ekfunkciigis la konekton fermo-tempigilon kaj, kiam oni ricevas peton, ĝi estas rekomencigita.
Resendante perditajn pakaĵojn (SendingCycle-ŝtato):

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

La resendita pako (pako numero 3 en la diagramo) estas ricevita per la envenanta konekto. Kontrolo estas farita por vidi ĉu la ricevfenestro estas plena kaj normala transdono de datumoj estas restarigita.
Kontrolante trafojn en la ricevfenestro (Kunmunta stato):

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

Fidinda UDP API

Por interagi kun la datumtransiga protokolo, ekzistas malfermita Fidinda Udp-klaso, kiu estas envolvaĵo super la transiga kontrolo-bloko. Jen la plej gravaj membroj de la klaso:

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

Mesaĝoj ricevas per abono. Delegita subskribo por la revokmetodo:

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

Senpaga:

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

Por aboni specifan mesaĝspecon kaj/aŭ specifan sendinto, estas uzataj du laŭvolaj parametroj: ReliableUdpMessageTypes messageType kaj IPEndPoint ipEndPoint.

Tipoj de mesaĝoj:

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

La mesaĝo estas sendita nesinkrone; por tio, la protokolo efektivigas nesinkronan programan modelon:

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

La rezulto de sendado de mesaĝo estos vera - se la mesaĝo sukcese atingis la ricevanton kaj malvera - se la konekto estis fermita per tempodestinto:

public bool EndSendMessage(IAsyncResult asyncResult)

konkludo

Multo ne estis priskribita en ĉi tiu artikolo. Mekanismoj de kongruaj fadenoj, traktado de esceptoj kaj eraroj, efektivigo de nesinkronaj sendaj metodoj. Sed la kerno de la protokolo, la priskribo de la logiko por prilaborado de pakaĵoj, establado de konekto kaj pritraktado de tempodaŭroj, devus esti klara al vi.

La pruvita versio de la fidinda livera protokolo estas sufiĉe fortika kaj fleksebla por plenumi la antaŭe difinitajn postulojn. Sed mi volas aldoni, ke la priskribita efektivigo estas plibonigebla. Ekzemple, por pliigi trairon kaj dinamike ŝanĝi tempigilojn, mekanismojn kiel glitfenestro kaj RTT povas esti aldonitaj al la protokolo, ankaŭ estos utile efektivigi mekanismon por determini MTU inter lignodoj (sed nur se grandaj mesaĝoj estas senditaj) .

Dankon pro via atento, mi atendas viajn komentojn kaj komentojn.

PS Por tiuj, kiuj interesiĝas pri la detaloj aŭ nur volas testi la protokolon, la ligo al la projekto ĉe GitHube:
Fidinda UDP-projekto

Utilaj ligiloj kaj artikoloj

  1. TCP-protokolo-specifo: en la angla и en la rusa
  2. UDP-protokolo-specifo: en la angla и en la rusa
  3. Diskuto de la RUDP-protokolo: skizo-ietf-sigtran-fidinda-udp-00
  4. Fidinda Datuma Protokolo: RFC 908 и RFC 1151
  5. Simpla efektivigo de konfirmo de livero super UDP: Prenu Tutan Kontrolon De Via Reto Kun .NET Kaj UDP
  6. Artikolo priskribanta NAT-traversajn mekanismojn: Samulo-al-kunula Komunikado Tra Retaj Adresaj Tradukistoj
  7. Efektivigo de la nesinkrona programa modelo: Efektivigo de la CLR Nesinkrona Programado-Modelo и Kiel efektivigi la IAsyncResult dezajnan ŝablonon
  8. Portante la nesinkronan programan modelon al la task-bazita nesinkrona ŝablono (APM en TAP):
    TPL kaj Tradicia .NET Nesinkrona Programado
    Interagado kun Aliaj Nesinkronaj Ŝablonoj kaj Tipoj

Ĝisdatigo: Dankon mayorovp и sidristij por la ideo aldoni taskon al la interfaco. La kongruo de la biblioteko kun malnovaj operaciumoj ne estas malobservita, ĉar La 4-a kadro subtenas kaj XP kaj 2003-servilon.

fonto: www.habr.com

Aldoni komenton