Utekelezaji wa itifaki ya Kuaminika ya Udp ya .Net

Mtandao umebadilika muda mrefu uliopita. Moja ya itifaki kuu za mtandao - UDP hutumiwa na maombi sio tu kutoa datagrams na matangazo, lakini pia kutoa uhusiano wa "peer-to-peer" kati ya nodes za mtandao. Kwa sababu ya muundo wake rahisi, itifaki hii ina matumizi mengi ambayo hayajapangwa hapo awali, hata hivyo, mapungufu ya itifaki, kama vile ukosefu wa utoaji wa uhakika, haujatoweka popote. Nakala hii inaelezea utekelezaji wa itifaki ya uwasilishaji iliyohakikishwa juu ya UDP.
Yaliyomo:Entry
Mahitaji ya Itifaki
Kichwa cha kuaminika cha UDP
Kanuni za jumla za itifaki
Muda umekwisha na vipima muda vya itifaki
Mchoro wa hali ya maambukizi ya UDP ya kuaminika
Kwa undani zaidi katika kanuni. kitengo cha kudhibiti maambukizi
Kwa undani zaidi katika kanuni. majimbo

Kwa undani zaidi katika kanuni. Kuunda na Kuanzisha Viunganisho
Kwa undani zaidi katika kanuni. Kufunga muunganisho kwa muda ulioisha
Kwa undani zaidi katika kanuni. Inarejesha uhamisho wa data
API ya UDP inayoaminika
Hitimisho
Viungo muhimu na makala

Entry

Usanifu asilia wa Mtandao ulichukua nafasi ya anwani ya homogeneous ambapo kila nodi ilikuwa na anwani ya IP ya kimataifa na ya kipekee na inaweza kuwasiliana moja kwa moja na nodi nyingine. Sasa Mtandao, kwa kweli, una usanifu tofauti - eneo moja la anwani za IP za kimataifa na maeneo mengi yenye anwani za kibinafsi zilizofichwa nyuma ya vifaa vya NAT.Katika usanifu huu, ni vifaa vilivyo katika nafasi ya kimataifa ya anwani pekee vinavyoweza kuwasiliana kwa urahisi na mtu yeyote kwenye mtandao kwa sababu vina anwani ya kipekee ya IP inayoweza kubadilishwa kimataifa. Node kwenye mtandao wa kibinafsi inaweza kuunganisha kwenye vifungo vingine kwenye mtandao huo huo, na pia inaweza kuunganisha kwenye vifungo vingine vinavyojulikana katika nafasi ya kimataifa ya anwani. Mwingiliano huu unapatikana kwa kiasi kikubwa kutokana na utaratibu wa kutafsiri anwani ya mtandao. Vifaa vya NAT, kama vile vipanga njia vya Wi-Fi, huunda maingizo maalum ya jedwali la tafsiri kwa miunganisho inayotoka na kurekebisha anwani za IP na nambari za mlango kwenye pakiti. Hii inaruhusu miunganisho inayotoka kutoka kwa mtandao wa kibinafsi hadi kwa wapangishi katika nafasi ya anwani ya kimataifa. Lakini wakati huo huo, vifaa vya NAT kawaida huzuia trafiki yote inayoingia isipokuwa sheria tofauti za miunganisho inayoingia zimewekwa.

Usanifu huu wa Mtandao ni sahihi vya kutosha kwa mawasiliano ya mteja-seva, ambapo wateja wanaweza kuwa katika mitandao ya kibinafsi, na seva zina anwani ya kimataifa. Lakini inaleta shida kwa unganisho la moja kwa moja la nodi mbili kati anuwai mitandao ya kibinafsi. Muunganisho wa moja kwa moja kati ya nodi mbili ni muhimu kwa programu-tumizi-rika-kwa-rika kama vile uwasilishaji wa sauti (Skype), kupata ufikiaji wa mbali kwa kompyuta (TeamViewer), au michezo ya mtandaoni.

Mojawapo ya njia bora zaidi za kuanzisha uhusiano kati ya wenzao kati ya vifaa kwenye mitandao tofauti ya kibinafsi inaitwa kuchomwa kwa shimo. Mbinu hii hutumiwa sana na programu kulingana na itifaki ya UDP.

Lakini ikiwa programu yako inahitaji utoaji wa uhakika wa data, kwa mfano, unahamisha faili kati ya kompyuta, basi kutumia UDP itakuwa na matatizo mengi kutokana na ukweli kwamba UDP sio itifaki ya utoaji wa uhakika na haitoi utoaji wa pakiti kwa utaratibu, tofauti na TCP. itifaki.

Katika kesi hii, ili kuhakikisha utoaji wa pakiti uliohakikishiwa, inahitajika kutekeleza itifaki ya safu ya maombi ambayo hutoa utendaji muhimu na inafanya kazi juu ya UDP.

Ninataka kutambua mara moja kuwa kuna mbinu ya kuchomwa kwa shimo la TCP ya kuanzisha miunganisho ya TCP kati ya nodi kwenye mitandao tofauti ya kibinafsi, lakini kwa sababu ya kukosekana kwa msaada kwake na vifaa vingi vya NAT, kawaida haizingatiwi kama njia kuu ya kuunganishwa. nodi kama hizo.

Kwa salio la makala hii, nitazingatia tu utekelezaji wa itifaki ya utoaji wa uhakika. Utekelezaji wa mbinu ya kuchomwa kwa shimo la UDP itaelezewa katika makala zifuatazo.

Mahitaji ya Itifaki

  1. Uwasilishaji wa pakiti unaotegemewa unatekelezwa kupitia utaratibu chanya wa maoni (kinachojulikana kama kukiri chanya)
  2. Uhitaji wa uhamisho wa ufanisi wa data kubwa, i.e. itifaki lazima kuepuka unnecessary pakiti relaying
  3. Itawezekana kughairi utaratibu wa uthibitishaji wa uwasilishaji (uwezo wa kufanya kazi kama itifaki "safi" ya UDP)
  4. Uwezo wa kutekeleza hali ya amri, na uthibitisho wa kila ujumbe
  5. Kitengo cha msingi cha uhamisho wa data juu ya itifaki lazima iwe ujumbe

Mahitaji haya kwa kiasi kikubwa yanaambatana na mahitaji ya Itifaki ya Data ya Kutegemewa yaliyofafanuliwa katika 908. Mchezaji hajali и 1151. Mchezaji hajali, na nilitegemea viwango hivyo wakati wa kuunda itifaki hii.

Ili kuelewa mahitaji haya, hebu tuangalie muda wa uhamisho wa data kati ya nodi mbili za mtandao kwa kutumia itifaki za TCP na UDP. Wacha katika visa vyote viwili tutakuwa na pakiti moja iliyopotea.
Uhamisho wa data isiyoingiliana kupitia TCP:Utekelezaji wa itifaki ya Kuaminika ya Udp ya .Net

Kama unavyoona kwenye mchoro, ikiwa pakiti itapotea, TCP itagundua pakiti iliyopotea na kuripoti kwa mtumaji kwa kuuliza nambari ya sehemu iliyopotea.
Uhamisho wa data kupitia itifaki ya UDP:Utekelezaji wa itifaki ya Kuaminika ya Udp ya .Net

UDP haichukui hatua zozote za kugundua upotevu. Udhibiti wa hitilafu za utumaji katika itifaki ya UDP ni jukumu la programu tumizi.

Ugunduzi wa hitilafu katika itifaki ya TCP unapatikana kwa kuanzisha muunganisho na nodi ya mwisho, kuhifadhi hali ya muunganisho huo, kuonyesha idadi ya baiti zilizotumwa katika kila kichwa cha pakiti, na kuarifu risiti kwa kutumia nambari ya kukiri.

Zaidi ya hayo, ili kuboresha utendakazi (yaani kutuma zaidi ya sehemu moja bila kupokea uthibitisho), itifaki ya TCP hutumia kinachojulikana kama dirisha la upitishaji - idadi ya baiti za data ambazo mtumaji wa sehemu anatarajia kupokea.

Kwa habari zaidi kuhusu itifaki ya TCP, ona 793. Mchezaji hajali, kutoka UDP hadi 768. Mchezaji hajaliambapo, kwa kweli, zinafafanuliwa.

Kutoka hapo juu, ni wazi kwamba ili kuunda itifaki ya kuaminika ya uwasilishaji wa ujumbe juu ya UDP (hapa inajulikana kama UDP ya kuaminika), inahitajika kutekeleza taratibu za uhamisho wa data sawa na TCP. Yaani:

  • kuokoa hali ya uunganisho
  • tumia nambari za sehemu
  • tumia vifurushi maalum vya uthibitisho
  • tumia njia iliyorahisishwa ya kuweka madirisha ili kuongeza upitishaji wa itifaki

Kwa kuongeza, unahitaji:

  • ishara ya kuanza kwa ujumbe, kutenga rasilimali kwa ajili ya uhusiano
  • ishara mwisho wa ujumbe, kupitisha ujumbe uliopokelewa kwa programu ya juu ya mkondo na kutoa rasilimali za itifaki
  • ruhusu itifaki ya muunganisho mahususi kuzima utaratibu wa uthibitishaji wa uwasilishaji kufanya kazi kama UDP "safi".

Kichwa cha kuaminika cha UDP

Kumbuka kwamba datagram ya UDP imeingizwa kwenye datagram ya IP. Pakiti ya UDP inayotegemewa "imefungwa" kwa datagramu ya UDP.
Ufungaji wa kichwa wa UDP unaotegemewa:Utekelezaji wa itifaki ya Kuaminika ya Udp ya .Net

Muundo wa kichwa cha kuaminika cha UDP ni rahisi sana:

Utekelezaji wa itifaki ya Kuaminika ya Udp ya .Net

  • Bendera - bendera za udhibiti wa kifurushi
  • MessageType - aina ya ujumbe unaotumiwa na programu za juu ili kujiandikisha kupokea ujumbe maalum
  • TransmissionId - nambari ya upitishaji, pamoja na anwani na bandari ya mpokeaji, hutambulisha kiunganisho cha kipekee.
  • PacketNumber - nambari ya pakiti
  • Chaguzi - chaguzi za ziada za itifaki. Katika kesi ya pakiti ya kwanza, hutumiwa kuonyesha ukubwa wa ujumbe

Bendera ni kama ifuatavyo:

  • FirstPacket - pakiti ya kwanza ya ujumbe
  • NoAsk - ujumbe hauhitaji utaratibu wa kukiri kuwezeshwa
  • LastPacket - pakiti ya mwisho ya ujumbe
  • OmbiKwaPakiti - pakiti ya uthibitisho au ombi la pakiti iliyopotea

Kanuni za jumla za itifaki

Kwa kuwa UDP ya Kutegemewa inazingatia upitishaji wa ujumbe uliohakikishwa kati ya nodi mbili, lazima iweze kuanzisha muunganisho na upande mwingine. Ili kuanzisha uunganisho, mtumaji hutuma pakiti na bendera ya FirstPacket, jibu ambalo litamaanisha uunganisho umeanzishwa. Pakiti zote za majibu, au, kwa maneno mengine, pakiti za kukiri, daima huweka thamani ya sehemu ya PacketNumber kwa moja zaidi ya thamani kubwa zaidi ya PacketNumber ya pakiti zilizopokewa kwa ufanisi. Sehemu ya Chaguzi kwa pakiti ya kwanza iliyotumwa ni saizi ya ujumbe.

Utaratibu sawa hutumiwa kuzima muunganisho. Bendera ya LastPacket imewekwa kwenye pakiti ya mwisho ya ujumbe. Kifurushi cha majibu kina nambari ya pakiti ya mwisho + 1, ambayo kwa upande wa kupokea inamaanisha uwasilishaji mzuri wa ujumbe.
Mchoro wa kuanzisha na kukomesha uhusiano:Utekelezaji wa itifaki ya Kuaminika ya Udp ya .Net

Wakati uunganisho umeanzishwa, uhamisho wa data huanza. Data hupitishwa katika vifurushi vya pakiti. Kila kizuizi, isipokuwa cha mwisho, kina idadi maalum ya pakiti. Ni sawa na ukubwa wa dirisha la kupokea/kusambaza. Sehemu ya mwisho ya data inaweza kuwa na pakiti chache. Baada ya kutuma kila kizuizi, upande wa kutuma husubiri uthibitisho wa uwasilishaji au ombi la kuwasilisha tena pakiti zilizopotea, na kuacha dirisha la kupokea/kusambaza wazi ili kupokea majibu. Baada ya kupokea uthibitisho wa utoaji wa kuzuia, mabadiliko ya dirisha la kupokea / kusambaza na kizuizi kinachofuata cha data kinatumwa.

Upande wa kupokea hupokea pakiti. Kila pakiti inaangaliwa ili kuona ikiwa itaanguka ndani ya dirisha la upitishaji. Pakiti na nakala ambazo hazianguka kwenye dirisha zinachujwa. Kwa sababu Ikiwa ukubwa wa dirisha umewekwa na sawa kwa mpokeaji na mtumaji, basi katika kesi ya kizuizi cha pakiti hutolewa bila kupoteza, dirisha hubadilishwa ili kupokea pakiti za block inayofuata ya data na uthibitisho wa utoaji ni. imetumwa. Ikiwa dirisha halijaza ndani ya muda uliowekwa na kipima saa cha kazi, basi hundi itaanza ambayo pakiti hazijatolewa na maombi ya kurejesha yatatumwa.
Mchoro wa uhamishaji upya:Utekelezaji wa itifaki ya Kuaminika ya Udp ya .Net

Muda umekwisha na vipima muda vya itifaki

Kuna sababu kadhaa kwa nini muunganisho hauwezi kuanzishwa. Kwa mfano, ikiwa mtu anayepokea yuko nje ya mtandao. Katika kesi hii, wakati wa kujaribu kuanzisha uunganisho, uunganisho utafungwa kwa muda. Utekelezaji wa UDP unaotegemewa hutumia vipima muda viwili kuweka muda wa muda. Ya kwanza, kipima muda cha kufanya kazi, kinatumika kusubiri jibu kutoka kwa mwenyeji wa mbali. Ikiwa inawaka upande wa mtumaji, basi pakiti ya mwisho iliyotumwa imetumwa. Ikiwa kipima muda kinaisha kwa mpokeaji, basi hundi ya pakiti zilizopotea hufanywa na maombi ya kurejesha tena yanatumwa.

Timer ya pili inahitajika ili kufunga uunganisho katika kesi ya ukosefu wa mawasiliano kati ya nodes. Kwa upande wa mtumaji, huanza mara moja baada ya kipima muda cha kufanya kazi kuisha, na kusubiri jibu kutoka kwa nodi ya mbali. Ikiwa hakuna jibu ndani ya muda uliowekwa, uunganisho umesitishwa na rasilimali hutolewa. Kwa upande wa kupokea, kipima saa cha uunganisho kinaanzishwa baada ya kipima muda cha kazi kuisha mara mbili. Hii ni muhimu ili kuhakikisha dhidi ya upotezaji wa pakiti ya uthibitisho. Wakati kipima muda kinaisha, muunganisho pia umekatishwa na rasilimali hutolewa.

Mchoro wa hali ya maambukizi ya UDP ya kuaminika

Kanuni za itifaki zinatekelezwa katika mashine ya hali ya mwisho, kila hali ambayo inawajibika kwa mantiki fulani ya usindikaji wa pakiti.
Mchoro wa Kuaminika wa Jimbo la UDP:

Utekelezaji wa itifaki ya Kuaminika ya Udp ya .Net

Ilifungwa - sio hali, ni mahali pa kuanzia na mwisho kwa automaton. Kwa jimbo Ilifungwa kizuizi cha udhibiti wa maambukizi kinapokelewa, ambacho, kutekeleza seva ya UDP isiyo ya kawaida, hupeleka pakiti kwa viunganisho vinavyofaa na kuanza usindikaji wa hali.

FirstPacketSending - hali ya awali ambayo muunganisho unaotoka ni wakati ujumbe unatumwa.

Katika hali hii, pakiti ya kwanza ya ujumbe wa kawaida hutumwa. Kwa ujumbe bila uthibitisho wa kutuma, hii ndiyo hali pekee ambapo ujumbe wote unatumwa.

Mzunguko wa Kutuma - hali ya chini ya upitishaji wa pakiti za ujumbe.

Mpito kwake kutoka kwa jimbo FirstPacketSending inafanywa baada ya pakiti ya kwanza ya ujumbe kutumwa. Ni katika hali hii kwamba shukrani zote na maombi ya uhamisho huja. Toka kutoka kwake inawezekana katika kesi mbili - katika kesi ya uwasilishaji mafanikio wa ujumbe au kwa kuisha.

FirstPacket Imepokelewa - hali ya awali kwa mpokeaji wa ujumbe.

Inachunguza usahihi wa mwanzo wa maambukizi, huunda miundo muhimu, na kutuma kukiri kwa kupokea pakiti ya kwanza.

Kwa ujumbe unaojumuisha pakiti moja na ulitumwa bila kutumia uthibitisho wa uwasilishaji, hii ndiyo hali pekee. Baada ya kusindika ujumbe kama huo, unganisho umefungwa.

Kukusanyika - hali ya msingi ya kupokea pakiti za ujumbe.

Inaandika pakiti kwa hifadhi ya muda, hundi ya kupoteza pakiti, hutuma shukrani kwa utoaji wa block ya pakiti na ujumbe mzima, na kutuma maombi ya kurejesha pakiti zilizopotea. Katika kesi ya kupokea kwa mafanikio ujumbe mzima, unganisho huenda kwenye hali Iliyokamilishwa, vinginevyo, muda wa kuisha huisha.

Iliyokamilishwa - kufunga muunganisho ikiwa utapokea ujumbe wote kwa mafanikio.

Hali hii ni muhimu kwa mkusanyiko wa ujumbe na kwa kesi wakati uthibitisho wa utoaji wa ujumbe ulipotea kwenye njia ya mtumaji. Hali hii inatoka kwa kuisha kwa muda, lakini muunganisho unachukuliwa kuwa umefungwa kwa ufanisi.

Kwa undani zaidi katika kanuni. kitengo cha kudhibiti maambukizi

Moja ya vipengele muhimu vya UDP ya Kuaminika ni kizuizi cha udhibiti wa maambukizi. Kazi ya kizuizi hiki ni kuhifadhi viunganisho vya sasa na vipengele vya msaidizi, kusambaza pakiti zinazoingia kwenye viunganisho vinavyofanana, kutoa interface ya kutuma pakiti kwenye uhusiano, na kutekeleza API ya itifaki. Kizuizi cha kudhibiti upitishaji hupokea pakiti kutoka kwa safu ya UDP na kuzipeleka kwa mashine ya serikali kwa usindikaji. Ili kupokea pakiti, hutumia seva ya UDP isiyolingana.
Baadhi ya washiriki wa darasa la ReliableUdpConnectionControlBlock:

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

Utekelezaji wa seva ya Asynchronous UDP:

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

Kwa kila uhamishaji wa ujumbe, muundo unaundwa ambao una habari kuhusu muunganisho. Muundo kama huo unaitwa rekodi ya uunganisho.
Baadhi ya washiriki wa darasa la ReliableUdpConnectionRecord:

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

Kwa undani zaidi katika kanuni. majimbo

Mataifa hutekeleza mashine ya serikali ya itifaki ya UDP ya Kuaminika, ambapo usindikaji kuu wa pakiti hufanyika. Darasa dhahania ReliableUdpState hutoa kiolesura cha serikali:

Utekelezaji wa itifaki ya Kuaminika ya Udp ya .Net

Mantiki nzima ya itifaki inatekelezwa na madarasa yaliyotolewa hapo juu, pamoja na darasa la msaidizi ambalo hutoa mbinu za tuli, kama vile, kwa mfano, kujenga kichwa cha ReliableUdp kutoka kwa rekodi ya uunganisho.

Ifuatayo, tutazingatia kwa undani utekelezaji wa njia za interface zinazoamua algorithms ya msingi ya itifaki.

Njia ya DisposeByTimeout

Mbinu ya DisposeByTimeout inawajibika kwa kutoa rasilimali za muunganisho baada ya muda kuisha na kuashiria uwasilishaji wa ujumbe uliofaulu/usiofaulu.
ReliableUdpState.DisposeByTimeout:

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

Imebatilishwa tu katika jimbo Iliyokamilishwa.
Imekamilika.DisposeByTimeout:

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

Njia ya MchakatoPackets

Njia ya ProcessPackets inawajibika kwa usindikaji wa ziada wa kifurushi au vifurushi. Inapigiwa simu moja kwa moja au kupitia kipima muda cha pakiti cha kusubiri.

Katika hali Kukusanyika njia hiyo imefungwa na inajibika kwa kuangalia kwa pakiti zilizopotea na mpito kwa serikali Iliyokamilishwa, katika kesi ya kupokea pakiti ya mwisho na kupitisha hundi iliyofanikiwa
Kukusanya.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);
  }
}

Katika hali Mzunguko wa Kutuma njia hii inaitwa tu kwenye timer, na ni wajibu wa kutuma ujumbe wa mwisho, pamoja na kuwezesha timer ya uunganisho wa karibu.
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);
}

Katika hali Iliyokamilishwa njia hiyo inasimamisha kipima saa na kutuma ujumbe kwa waliojisajili.
Imekamilika.ProcessPackets:

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

Njia ya ReceivePacket

Katika hali FirstPacket Imepokelewa Kazi kuu ya njia ni kuamua ikiwa pakiti ya kwanza ya ujumbe ilifika kwenye kiolesura, na pia kukusanya ujumbe unaojumuisha pakiti moja.
Pakiti ya Kwanza Imepokelewa.PokeaPakiti:

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

Katika hali Mzunguko wa Kutuma njia hii imebatilishwa ili kukubali uthibitisho wa uwasilishaji na maombi ya kutuma tena.
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));
}

Katika hali Kukusanyika katika njia ya ReceivePacket, kazi kuu ya kukusanya ujumbe kutoka kwa pakiti zinazoingia hufanyika.
Kukusanya.PokeaPakiti:

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

Katika hali Iliyokamilishwa kazi pekee ya njia ni kutuma kukiri tena kwa uwasilishaji mzuri wa ujumbe.
Imekamilika.PokeaPakiti:

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

Tuma Njia ya Pakiti

Katika hali FirstPacketSending njia hii hutuma pakiti ya kwanza ya data, au, ikiwa ujumbe hauhitaji uthibitisho wa utoaji, ujumbe mzima.
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);
}

Katika hali Mzunguko wa Kutuma kwa njia hii, block ya pakiti hutumwa.
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 );
  }
}

Kwa undani zaidi katika kanuni. Kuunda na Kuanzisha Viunganisho

Sasa kwa kuwa tumeona majimbo ya msingi na mbinu zinazotumiwa kushughulikia majimbo, hebu tuchambue mifano michache ya jinsi itifaki inavyofanya kazi kwa undani zaidi.
Mchoro wa maambukizi ya data chini ya hali ya kawaida:Utekelezaji wa itifaki ya Kuaminika ya Udp ya .Net

Fikiria kwa undani uumbaji rekodi ya uunganisho kuunganisha na kutuma pakiti ya kwanza. Uhamisho kila mara huanzishwa na programu inayoita API ya ujumbe. Ifuatayo, njia ya StartTransmission ya kizuizi cha udhibiti wa maambukizi inatumiwa, ambayo huanza uhamisho wa data kwa ujumbe mpya.
Kuunda muunganisho unaotoka:

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

Inatuma pakiti ya kwanza (FirstPacketSending state):

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

Baada ya kutuma pakiti ya kwanza, mtumaji huingia katika hali Mzunguko wa Kutuma - subiri uthibitisho wa utoaji wa kifurushi.
Upande wa kupokea, kwa kutumia njia ya EndReceive, hupokea pakiti iliyotumwa, huunda mpya rekodi ya uunganisho na kupitisha pakiti hii, iliyo na kichwa kilichochanganuliwa awali, kwa njia ya ReceivePacket ya serikali kwa usindikaji. FirstPacket Imepokelewa
Kuunda muunganisho kwenye upande wa kupokea:

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

Kupokea kifurushi cha kwanza na kutuma kibali (Hali ya KwanzaPakitiImepokelewa):

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

Kwa undani zaidi katika kanuni. Kufunga muunganisho kwa muda ulioisha

Ushughulikiaji wa muda ulioisha ni sehemu muhimu ya UDP ya Kutegemewa. Fikiria mfano ambao nodi ya kati ilishindwa na uwasilishaji wa data katika pande zote mbili hauwezekani.
Mchoro wa kufunga muunganisho kwa kuisha:Utekelezaji wa itifaki ya Kuaminika ya Udp ya .Net

Kama inavyoonekana kwenye mchoro, kipima saa cha kazi cha mtumaji huanza mara baada ya kutuma kizuizi cha pakiti. Hii hutokea katika njia ya SendPacket ya serikali Mzunguko wa Kutuma.
Kuwasha kipima saa cha kazi (SendingCycle state):

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

Vipindi vya kipima muda huwekwa wakati muunganisho umeundwa. ShortTimerPeriod chaguo-msingi ni sekunde 5. Katika mfano, imewekwa kwa sekunde 1,5.

Kwa muunganisho unaoingia, kipima saa huanza baada ya kupokea pakiti ya mwisho ya data inayoingia, hii hutokea katika njia ya ReceivePacket ya serikali. Kukusanyika
Kuwezesha kipima saa cha kazi (Hali ya Kukusanya):

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

Hakuna pakiti zaidi zilizofika kwenye muunganisho unaoingia zikisubiri kipima muda cha kufanya kazi. Kipima saa kilizima na kuita njia ya ProcessPackets, ambapo pakiti zilizopotea zilipatikana na maombi ya uwasilishaji tena yalitumwa kwa mara ya kwanza.
Inatuma maombi ya uwasilishaji upya (Hali ya kukusanyika):

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

Tofauti ya TimerSecondTry imewekwa kuwa kweli. Tofauti hii inawajibika kwa kuanzisha upya kipima muda cha kufanya kazi.

Kwa upande wa mtumaji, kipima muda cha kufanya kazi pia kimeanzishwa na pakiti ya mwisho iliyotumwa imetumwa tena.
Inawezesha kipima saa cha muunganisho (hali ya SendingCycle):

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

Baada ya hapo, kipima saa cha uunganisho huanza kwenye muunganisho unaotoka.
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);
}

Muda wa kuisha kwa kipima muda cha muunganisho ni sekunde 30 kwa chaguo-msingi.

Baada ya muda mfupi, timer ya kufanya kazi kwenye upande wa mpokeaji huwaka tena, maombi yanatumwa tena, baada ya hapo kipima saa cha uunganisho huanza kwa unganisho linaloingia.

Vipima muda vya kufunga vinapowaka, rasilimali zote za rekodi zote za uunganisho hutolewa. Mtumaji anaripoti kutofaulu kwa uwasilishaji kwa programu ya juu ya mkondo (tazama API ya UDP inayoaminika).
Inatoa rasilimali za rekodi za muunganisho:

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

Kwa undani zaidi katika kanuni. Inarejesha uhamisho wa data

Mchoro wa kurejesha uhamishaji wa data katika kesi ya upotezaji wa pakiti:Utekelezaji wa itifaki ya Kuaminika ya Udp ya .Net

Kama ilivyojadiliwa tayari katika kufunga muunganisho kwa muda ulioisha, kipima saa kinapoisha, mpokeaji ataangalia pakiti zilizopotea. Katika kesi ya upotezaji wa pakiti, orodha ya idadi ya pakiti ambazo hazikufika kwa mpokeaji itaundwa. Nambari hizi huingizwa kwenye safu ya LostPackets ya muunganisho maalum na maombi ya uwasilishaji upya hutumwa.
Kutuma maombi ya kuwasilisha tena vifurushi (Hali ya Kukusanyika):

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

Mtumaji atakubali ombi la kuwasilisha tena na kutuma pakiti ambazo hazipo. Inafaa kumbuka kuwa kwa wakati huu mtumaji tayari ameanza kipima saa cha uunganisho na, wakati ombi limepokelewa, linawekwa upya.
Inatuma tena pakiti zilizopotea (hali ya SendingCycle):

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

Pakiti iliyotumwa (pakiti # 3 kwenye mchoro) inapokelewa na uunganisho unaoingia. Cheki inafanywa ili kuona ikiwa kidirisha cha kupokea kimejaa na utumaji data wa kawaida umerejeshwa.
Inatafuta vibao kwenye kidirisha cha kupokea (Hali ya Kukusanyika):

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

API ya UDP inayoaminika

Ili kuingiliana na itifaki ya uhamishaji data, kuna darasa la Udp la Kuaminika lililo wazi, ambalo ni kanga juu ya kizuizi cha udhibiti wa uhamishaji. Hapa kuna washiriki muhimu zaidi wa darasa:

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

Ujumbe hupokelewa kwa usajili. Kaumu saini ya mbinu ya kurudi nyuma:

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

Ujumbe:

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

Ili kujiandikisha kwa aina mahususi ya ujumbe na/au mtumaji mahususi, vigezo viwili vya hiari vinatumika: ReliableUdpMessageTypes messageType na IPEndPoint ipEndPoint.

Aina za ujumbe:

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

Ujumbe hutumwa kwa usawa; kwa hili, itifaki hutumia modeli ya programu ya asynchronous:

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

Matokeo ya kutuma ujumbe yatakuwa ya kweli - ikiwa ujumbe ulimfikia mpokeaji kwa ufanisi na sivyo - ikiwa muunganisho ulifungwa kwa muda ulioisha:

public bool EndSendMessage(IAsyncResult asyncResult)

Hitimisho

Mengi hayajaelezewa katika makala hii. Taratibu za kulinganisha nyuzi, ubaguzi na ushughulikiaji wa makosa, utekelezaji wa njia za kutuma ujumbe zisizolingana. Lakini msingi wa itifaki, maelezo ya mantiki ya pakiti za usindikaji, kuanzisha uunganisho, na kushughulikia muda, inapaswa kuwa wazi kwako.

Toleo lililoonyeshwa la itifaki ya uwasilishaji inayotegemewa ni thabiti na linaweza kunyumbulika vya kutosha kukidhi mahitaji yaliyobainishwa hapo awali. Lakini nataka kuongeza kwamba utekelezaji ulioelezwa unaweza kuboreshwa. Kwa mfano, ili kuongeza upitishaji na kubadilisha muda wa kipima saa kwa nguvu, mifumo kama vile dirisha la kuteleza na RTT inaweza kuongezwa kwenye itifaki, itakuwa muhimu pia kutekeleza utaratibu wa kuamua MTU kati ya nodi za unganisho (lakini tu ikiwa ujumbe mkubwa utatumwa) .

Asante kwa umakini wako, ninatarajia maoni na maoni yako.

PS Kwa wale ambao wanavutiwa na maelezo au wanataka tu kujaribu itifaki, kiunga cha mradi kwenye GitHube:
Mradi wa UDP wa kuaminika

Viungo muhimu na makala

  1. Vipimo vya itifaki ya TCP: kwa kiingereza и kwa Kirusi
  2. Vipimo vya itifaki ya UDP: kwa kiingereza и kwa Kirusi
  3. Majadiliano ya itifaki ya RUDP: draft-ietf-sigtran-reliable-udp-00
  4. Itifaki ya Data Inayoaminika: 908. Mchezaji hajali и 1151. Mchezaji hajali
  5. Utekelezaji rahisi wa uthibitishaji wa uwasilishaji juu ya UDP: Chukua Udhibiti wa Jumla wa Mtandao Wako Ukiwa na NET na UDP
  6. Kifungu kinachoelezea njia za kupitisha za NAT: Mawasiliano kutoka kwa Rika kwa Watafsiri wa Anwani za Mtandao
  7. Utekelezaji wa muundo wa programu usio na usawa: Utekelezaji wa CLR Asynchronous Programming Model и Jinsi ya kutekeleza muundo wa muundo wa IAsyncResult
  8. Kuhawilisha modeli ya programu isiyolingana kwa muundo wa asynchronous kulingana na kazi (APM katika TAP):
    TPL na Traditional .NET Asynchronous Programming
    Interop na Miundo na Aina Zingine za Asynchronous

Sasisha: Asante mayorovp и sidristij kwa wazo la kuongeza kazi kwenye kiolesura. Utangamano wa maktaba na mifumo ya uendeshaji ya zamani hauvunjwa, kwa sababu Mfumo wa 4 unaauni seva ya XP na 2003.

Chanzo: mapenzi.com

Kuongeza maoni