Implementasi protokol Udp Reliable kanggo .Net

Internet wis suwe saya ganti. Salah sawijining protokol Internet utama, UDP, digunakake dening aplikasi ora mung kanggo ngirim datagram lan siaran, nanging uga kanggo nyedhiyakake sambungan peer-to-peer ing antarane simpul jaringan. Amarga struktur sing prasaja, protokol iki nemokake akeh panggunaan sing ora direncanakake sadurunge, sanajan kekurangane, kayata kekurangan pangiriman sing dijamin, durung ilang. Artikel iki njlèntrèhaké implementasine protokol pangiriman dijamin liwat UDP.
Isi:entri
Syarat protokol
Header UDP sing bisa dipercaya
Prinsip umum protokol
Wektu entek lan wektu protokol
Diagram Negara Transmisi UDP sing dipercaya
Luwih jero menyang kode. Unit kontrol transmisi
Luwih jero menyang kode. negara

Luwih jero kode: Nggawe lan nggawe sambungan
Luwih jero menyang kode. Nutup sambungan ing wektu entek
Luwih jero menyang kode. Mbalekake transfer data
API Reliable UDP
kesimpulan
Pranala lan artikel sing migunani

entri

Arsitèktur asli Internet nyakup ruang alamat sing seragam, sing saben simpul duwé alamat IP global lan unik, lan bisa komunikasi langsung karo simpul liya. Saiki, Internet, nyatane, duwe arsitektur sing beda - siji area alamat IP global lan akeh wilayah kanthi alamat pribadi, sing didhelikake ing mburi piranti NAT.Ing arsitektur iki, mung piranti ing ruang alamat global sing bisa gampang komunikasi karo wong liya ing jaringan amarga duwe alamat IP sing unik lan bisa ditularake sacara global. A simpul ing jaringan pribadi bisa komunikasi karo kelenjar liyane ing jaringan sing padha, lan uga bisa komunikasi karo liyane, simpul kondhang ing papan alamat global. Komunikasi iki digayuh umume liwat mekanisme sing disebut terjemahan alamat jaringan. Piranti NAT, kayata router Wi-Fi, nggawe entri khusus ing tabel terjemahan kanggo sambungan metu lan ngowahi alamat IP lan nomer port ing paket. Iki ngidini sambungan metu saka jaringan pribadi kanggo simpul ing papan alamat global. Nanging, piranti NAT biasane mblokir kabeh lalu lintas mlebu kajaba aturan khusus disetel kanggo sambungan mlebu.

Arsitektur Internet iki cukup bener kanggo interaksi klien-server, nalika klien bisa ing jaringan pribadi lan server duwe alamat global. Nanging nggawe kangelan kanggo sambungan langsung saka rong kelenjar antarane macem-macem jaringan pribadi. Sambungan langsung antarane rong simpul penting kanggo aplikasi peer-to-peer kayata transmisi swara (Skype), akses remot menyang komputer (TeamViewer), utawa game online.

Salah sawijining cara sing paling efektif kanggo nggawe sambungan peer-to-peer ing antarane piranti sing ana ing jaringan pribadi sing beda diarani "bolongan". Teknik iki paling kerep digunakake karo aplikasi basis UDP.

Nanging yen aplikasi sampeyan mbutuhake pangiriman data sing dijamin, umpamane, sampeyan nransfer file ing antarane komputer, banjur nggunakake UDP bakal nyebabake akeh kesulitan amarga kasunyatane UDP dudu protokol pangiriman sing dijamin lan ora njamin pangiriman paket kanthi urutan, ora kaya protokol TCP.

Ing kasus iki, kanggo mesthekake pangiriman dijamin paket, iku perlu kanggo ngleksanakake protokol lapisan aplikasi sing nyedhiyani fungsi perlu lan dianggo ing ndhuwur UDP.

Aku arep langsung nuduhake yen ana TCP hole punching technique kanggo netepake sambungan TCP antarane kelenjar ing jaringan pribadi beda, nanging amarga lack of support kanggo akeh piranti NAT, iku biasane ora dianggep minangka cara utama kanggo nyambungake kelenjar kuwi.

Luwih ing artikel iki aku bakal nimbang mung implementasine saka protokol pangiriman dijamin. Implementasine teknik punching bolongan UDP bakal diterangake ing artikel ing ngisor iki.

Syarat protokol

  1. Pangiriman paket sing bisa dipercaya ditindakake liwat mekanisme umpan balik positif (sing diarani pengakuan positif)
  2. Kebutuhan kanggo transmisi data gedhe sing efisien, yaiku protokol kudu ngindhari transmisi ulang paket sing ora perlu
  3. Sampeyan kudu bisa mateni mekanisme konfirmasi pangiriman (kemampuan kanggo fungsi minangka protokol UDP "murni")
  4. Kamungkinan ngleksanakake mode printah, kanthi konfirmasi saben pesen
  5. Unit dhasar transfer data ing protokol kudu pesen.

Keperluan kasebut umume cocog karo syarat kanggo Protokol Data Reliable sing diterangake ing rfc 908 и rfc 1151, lan aku adhedhasar protokol iki ing standar iki.

Kanggo mangerteni syarat kasebut, ayo dipikirake diagram wektu transmisi data antarane rong node jaringan nggunakake protokol TCP lan UDP. Ayo nganggep yen ing kasus loro, kita bakal kelangan siji paket.
Transfer data non-interaktif liwat TCP:Implementasi protokol Udp Reliable kanggo .Net

Kaya sing bisa dideleng saka diagram, yen paket ilang, TCP bakal ndeteksi paket sing ilang lan menehi kabar marang pangirim kanthi njaluk nomer segmen sing ilang.
Transmisi data liwat protokol UDP:Implementasi protokol Udp Reliable kanggo .Net

UDP ora njupuk langkah kanggo ndeteksi kerugian. Kontrol kesalahan transmisi ing protokol UDP tanggung jawab kanggo aplikasi.

Deteksi kesalahan ing protokol TCP digayuh kanthi nggawe sambungan menyang simpul pungkasan, njaga kahanan sambungan kasebut, nuduhake jumlah bita sing dikirim ing saben header paket, lan menehi kabar babagan kuitansi nggunakake nomer pangenalan.

Kajaba iku, kanggo nambah kinerja (yaiku ngirim luwih saka siji segmen tanpa nampa pangakuan), protokol TCP nggunakake jendhela transmisi sing disebut - jumlah bita data sing dikarepake pangirim segmen kasebut.

Rincian liyane babagan protokol TCP bisa ditemokake ing rfc 793, karo UDP ing rfc 768, ngendi padha, nyatane, ditetepake.

Saka ndhuwur, jelas yen kanggo nggawe protokol pangiriman pesen sing dipercaya liwat UDP (sabanjure diarani UDP sing dipercaya), perlu kanggo ngetrapake mekanisme transfer data sing padha karo TCP. yaiku:

  • nyimpen negara sambungan
  • nggunakake penomoran segmen
  • nggunakake paket konfirmasi khusus
  • nggunakake mekanisme jendhela simplified kanggo nambah throughput protokol

Kajaba iku, ing ngisor iki dibutuhake:

  • sinyal wiwitan pesen kanggo nyedhiakke sumber daya kanggo sambungan
  • menehi tandha pungkasan pesen, kanggo ngirim pesen sing ditampa menyang aplikasi hulu lan ngeculake sumber protokol
  • ngidini protokol kanggo mateni mekanisme konfirmasi pangiriman ing basis saben sambungan supaya bisa dienggo minangka "murni" UDP

Header UDP sing bisa dipercaya

Elinga yen datagram UDP dienkapsulasi ing datagram IP. Paket UDP sing bisa dipercaya "dibungkus" ing datagram UDP.
Enkapsulasi Header UDP sing dipercaya:Implementasi protokol Udp Reliable kanggo .Net

Struktur header UDP Reliable cukup prasaja:

Implementasi protokol Udp Reliable kanggo .Net

  • Flags - kontrol flag paket
  • MessageType - jinis pesen, digunakake dening aplikasi hulu kanggo langganan pesen tartamtu
  • TransmissionId — nomer transmisi, bebarengan karo alamat panampa lan port, unik ngenali sambungan
  • PacketNumber - nomer paket
  • Pilihan - opsi protokol tambahan. Ing kasus paket pisanan, digunakake kanggo nemtokake ukuran pesen

Gendéra-fèrsi kaya ing ngisor iki:

  • FirstPacket - paket pisanan pesen
  • NoAsk - pesen ora mbutuhake mekanisme konfirmasi kanggo diaktifake
  • LastPacket - paket pungkasan pesen
  • RequestForPacket - Paket ngakoni utawa njaluk paket sing ilang

Prinsip umum protokol

Wiwit UDP Reliable oriented menyang transmisi pesen dijamin antarane rong simpul, iku kudu bisa kanggo nggawe sambungan karo sisih liyane. Kanggo nggawe sambungan, sisih ngirim ngirim paket karo flag FirstPacket, respon sing tegese sambungan wis ditetepake. Kabeh paket respon, utawa, ing tembung liyane, paket konfirmasi, tansah nyetel Nilai lapangan PacketNumber kanggo siji luwih gedhe tinimbang Nilai PacketNumber paling gedhe saka paket kasil ditampa. Kolom Pilihan kanggo paket sing dikirim pisanan nyathet ukuran pesen.

Mekanisme sing padha digunakake kanggo mungkasi sambungan. Gendéra LastPacket disetel ing paket pungkasan pesen. Paket respon nemtokake nomer paket pungkasan + 1, sing kanggo sisih panampa tegese pangiriman pesen sing sukses.
Panyiapan sambungan lan diagram terminasi:Implementasi protokol Udp Reliable kanggo .Net

Nalika sambungan digawe, transmisi data diwiwiti. Data dikirim ing pamblokiran paket. Saben blok, kajaba sing pungkasan, ngemot jumlah paket sing tetep. Iku padha karo ukuran jendhela nampa / ngirim. Blok data pungkasan bisa uga duwe paket sing luwih cilik. Sawise ngirim saben blok, sisih ngirim ngenteni konfirmasi pangiriman, utawa panjalukan kanggo ngirim maneh paket ilang, ninggalake jendhela nampa / ngirim mbukak kanggo nampa respon. Sawise nampa konfirmasi pangiriman blok, jendhela nampa / ngirim dipindhah lan blok data sabanjure dikirim.

Pihak sing nampa nampa paket. Saben paket dicenthang kanggo ndeleng manawa cocog karo jendela transmisi. Paket sing ora cocog karo jendhela lan duplikat disaring. Wiwit ukuran jendhela strictly tetep lan padha kanggo panampa lan pangirim, yen pamblokiran paket dikirim tanpa losses, jendhela dipindhah kanggo nampa paket saka pamblokiran data sabanjuré lan konfirmasi pangiriman dikirim. Yen jendhela ora diisi sajrone wektu sing disetel dening timer kerja, priksa bakal diluncurake kanggo ndeleng paket sing ora dikirim lan panjaluk kiriman bola-bali bakal dikirim.
Diagram transmisi maneh:Implementasi protokol Udp Reliable kanggo .Net

Wektu entek lan wektu protokol

Ana sawetara alasan kenapa sambungan ora bisa ditetepake. Contone, yen pihak sing nampa offline. Ing kasus iki, nalika nyoba nggawe sambungan, sambungan bakal ditutup amarga wektu entek. Implementasi UDP Reliable nggunakake rong timer kanggo nyetel wektu entek. Pisanan, wektu kerja, digunakake kanggo ngenteni respon saka host remot. Yen dipicu ing sisih ngirim, banjur pungkasan dikirim paket resent. Yen timer micu ing sisih panrima, banjur mriksa digawe kanggo paket ilang lan panjalukan kanggo re-delivery dikirim.

Timer kapindho dibutuhake kanggo nutup sambungan yen ora ana sambungan antarane simpul. Kanggo sisih ngirim, iku wiwit sanalika sawise wektu kerja micu, lan ngenteni respon saka simpul remot. Yen ora ana respon ing wektu sing disetel, sambungan bakal mandheg lan sumber daya bakal dirilis. Kanggo sisih panrima, wektu nutup sambungan diwiwiti sawise wektu kerja dipicu kaping pindho. Iki perlu kanggo njamin marang mundhut saka paket konfirmasi. Nalika timer micu, sambungan uga mungkasi lan sumber daya dirilis.

Diagram Negara Transmisi UDP sing dipercaya

Prinsip protokol dileksanakake ing mesin negara winates, saben negara tanggung jawab kanggo logika tartamtu saka pangolahan paket.
Diagram status UDP sing bisa dipercaya:

Implementasi protokol Udp Reliable kanggo .Net

Ditutup - dudu negara, iku titik wiwitan lan pungkasan kanggo mesin. Kanggo negara Ditutup Blok kontrol transmisi ditampa, sing, ngleksanakake server UDP sing ora sinkron, ngirim paket menyang sambungan sing cocog lan miwiti proses negara.

FirstPacketSending – negara wiwitan sing sambungan metu nalika ngirim pesen.

Ing negara iki, paket pisanan dikirim kanggo pesen biasa. Kanggo pesen tanpa ngirim konfirmasi, iki mung negara - ing kono, kabeh pesen dikirim.

SendingCycle - negara dhasar kanggo ngirim paket pesen.

Transisi saka negara FirstPacketSending ditindakake sawise ngirim paket pesen pisanan. Ing negara iki kabeh konfirmasi lan panjaluk kanggo transmisi ulang ditampa. Metu saka iku bisa ing rong kasus - ing cilik saka sukses pangiriman pesen utawa dening wektu entek.

FirstPacketReceived – negara wiwitan kanggo panampa pesen.

Iku mriksa bener saka wiwitan transmisi, nggawe struktur perlu, lan ngirim konfirmasi panrimo saka paket pisanan.

Kanggo pesen sing dumadi saka paket siji lan dikirim tanpa nggunakake konfirmasi pangiriman, iki mung negara. Sawise pesen kasebut diproses, sambungan ditutup.

Assembling – negara dhasar kanggo nampa paket pesen.

Iku ngrekam paket ing panyimpenan sak wentoro, mriksa kanggo paket mundhut, ngirim konfirmasi pangiriman pamblokiran paket lan kabeh pesen, lan ngirim panjalukan kanggo re-delivery saka paket ilang. Yen kabeh pesen kasil ditampa, sambungan dadi menyang negara rampung, yen ora, wektu entek ditindakake.

rampung – nutup sambungan yen kabeh pesen kasil ditampa.

Negara iki perlu kanggo perakitan pesen lan kanggo kasus nalika konfirmasi pangiriman pesen ilang ing dalan menyang pangirim. Metu saka negara iki ditindakake kanthi wektu entek, nanging sambungan kasebut dianggep kasil ditutup.

Luwih jero menyang kode. Unit kontrol transmisi

Salah sawijining unsur kunci UDP Reliable yaiku blok kontrol transmisi. Tugas blok iki yaiku kanggo nyimpen sambungan saiki lan unsur tambahan, nyebarake paket sing mlebu menyang sambungan sing cocog, nyedhiyakake antarmuka kanggo ngirim paket menyang sambungan lan ngleksanakake API protokol. Blok kontrol transmisi nampa paket saka lapisan UDP lan diterusake kanggo diproses menyang mesin negara sing winates. Server UDP sing ora sinkron diimplementasikake kanggo nampa paket.
Sawetara anggota kelas 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;    	
  //...
}

Implementasi server UDP asinkron:

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

Kanggo saben transfer pesen, struktur digawe sing ngemot informasi babagan sambungan kasebut. Struktur iki diarani rekaman sambungan.
Sawetara anggota kelas 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;
  //...
}

Luwih jero menyang kode. negara

Negara ngleksanakake mesin negara saka protokol UDP Reliable, ngendi paling Processing paket dumadi. Kelas abstrak ReliableUdpState nyedhiyakake antarmuka menyang negara:

Implementasi protokol Udp Reliable kanggo .Net

Logika kabeh protokol dileksanakake dening kelas sing dituduhake ing ndhuwur, bebarengan karo kelas tambahan sing nyedhiyakake cara statis, kayata, contone, mbangun header ReliableUdp saka rekaman sambungan.

Sabanjure, kita bakal nimbang kanthi rinci implementasi metode antarmuka sing nemtokake algoritma dhasar kanggo protokol kasebut.

Metode DisposeByTimeout

Cara DisposeByTimeout tanggung jawab kanggo ngeculake sumber sambungan sawise wektu entek lan menehi tandha sukses / gagal pangiriman pesen.
ReliableUdpState.DisposeByTimeout:

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

Iku mung overridden ing negara rampung.
Rampung.DisposeByTimeout:

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

Metode ProcessPackets

Cara ProcessPackets tanggung jawab kanggo pangolahan tambahan saka paket utawa paket. Ditelpon langsung utawa liwat packet wait timer.

saged Assembling cara overridden lan tanggung jawab kanggo mriksa paket ilang lan transisi menyang negara rampung, ing cilik saka nampa paket pungkasan lan maringaken mriksa kasil
Assembling.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);
  }
}

saged SendingCycle Cara iki mung diarani timer, lan tanggung jawab kanggo ngirim maneh pesen pungkasan, uga kanggo ngaktifake wektu nutup sambungan.
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);
}

saged rampung Cara kasebut mandhegake timer sing mlaku lan ngirim pesen menyang pelanggan.
Rampung.ProsesPaket:

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

Metode ReceivePacket

saged FirstPacketReceived Tugas utama metode kasebut yaiku kanggo nemtokake manawa paket pisanan pesen kasebut bener-bener teka ing antarmuka, lan uga kanggo ngumpulake pesen sing dumadi saka siji paket.
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);
  }
}

saged SendingCycle Cara iki diganti kanggo nampa konfirmasi pangiriman lan panjaluk transmisi maneh.
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));
}

saged Assembling Cara ReceivePacket yaiku ing ngendi karya utama ngumpulake pesen saka paket sing mlebu.
Assembling.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);
  }
}

saged rampung Siji-sijine tugas cara kasebut yaiku ngirim konfirmasi maneh babagan pangiriman pesen sing sukses.
Rampung.ReceivePacket:

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

Metode SendPacket

saged FirstPacketSending Cara iki ngirim paket data pisanan, utawa, yen pesen ora mbutuhake konfirmasi pangiriman, kabeh pesen.
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);
}

saged SendingCycle Ing cara iki, pamblokiran paket dikirim.
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 );
  }
}

Luwih jero kode: Nggawe lan nggawe sambungan

Saiki kita wis kenal karo negara dhasar lan cara sing digunakake kanggo nangani negara, kita bisa ndeleng sawetara conto babagan cara protokol kasebut kanthi luwih rinci.
Diagram transfer data ing kahanan normal:Implementasi protokol Udp Reliable kanggo .Net

Ayo dideleng kanthi luwih rinci babagan kreasi kasebut rekaman sambungan kanggo nyambung lan ngirim paket pisanan. Inisiator transfer tansah aplikasi nelpon cara API kanggo ngirim pesen. Banjur metode StartTransmission saka blok kontrol transmisi digunakake, miwiti transfer data kanggo pesen anyar.
Nggawe sambungan metu:

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

Ngirim paket pisanan (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);
}

Sawise ngirim paket pisanan, pangirim mlebu negara SendingCycle – ngenteni konfirmasi pengiriman paket.
Pihak sing nampa, nggunakake metode EndReceive, nampa paket sing dikirim, nggawe anyar rekaman sambungan lan ngliwati paket iki, kanthi header sing wis diurai, menyang cara negara ReceivePacket kanggo diproses FirstPacketReceived
Nggawe sambungan ing sisih panrima:

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

Nampa paket pisanan lan ngirim pangakuan (negara FirstPacketReceived):

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

Luwih jero menyang kode. Nutup sambungan ing wektu entek

Nangani wektu entek minangka bagéyan penting saka UDP Reliable. Coba conto ing ngendi simpul penengah gagal lan pangiriman data ing loro arah dadi ora mungkin.
Diagram penutupan wektu entek sambungan:Implementasi protokol Udp Reliable kanggo .Net

Kaya sing bisa dideleng saka diagram, wektu kerja pangirim langsung diaktifake sawise ngirim blok paket. Iki kedadeyan ing metode SendPacket negara SendingCycle.
Ngaktifake wektu kerja (Status SendingCycle):

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

Wektu wektu disetel nalika sambungan digawe. Kanthi gawan, ShortTimerPeriod yaiku 5 detik. Ing conto, disetel kanggo 1,5 detik.

Kanggo sambungan mlebu, wektu diwiwiti sawise nampa paket data mlebu pungkasan, iki kedadeyan ing metode ReceivePacket negara. Assembling
Ngaktifake timer kerja (Status assembling):

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

Nalika sambungan mlebu, ora ana paket maneh sing teka nalika wektu kerja ngenteni. Timer kadaluwarsa lan diarani metode ProcessPackets, sing ndeteksi paket sing ilang lan ngirim panjaluk kiriman maneh kanggo pisanan.
Ngirim panjalukan kiriman maneh (Status assembling):

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

Variabel TimerSecondTry disetel kanggo bener. Variabel iki tanggung jawab kanggo miwiti maneh wektu kerja.

Ing sisih pangirim, wektu kerja uga dipicu lan paket pungkasan dikirim maneh.
Ngaktifake timer nutup sambungan (Status SendingCycle):

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

Sawise wektu nutup sambungan diwiwiti ing sambungan metu.
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);
}

Periode wektu entek wektu cedhak sambungan yaiku 30 detik minangka standar.

Sawise wektu sing cendhak, wektu kerja ing sisih panampa diuripake maneh, panjaluk dikirim maneh, sawise wektu nutup sambungan diwiwiti kanggo sambungan sing mlebu.

Nalika timer cedhak murub, kabeh sumber daya saka loro cathetan sambungan dirilis. Pangirim nglaporake kegagalan pangiriman menyang aplikasi hulu (ndeleng Reliable UDP API).
Mbebasake sumber rekaman sambungan:

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

Luwih jero menyang kode. Mbalekake transfer data

Diagram pemulihan transmisi data kanggo mundhut paket:Implementasi protokol Udp Reliable kanggo .Net

Kaya sing dibahas babagan penutupan sambungan kanthi wektu entek, nalika wektu kerja wis kadaluwarsa, panrima bakal mriksa paket sing ilang. Yen ana mundhut paket, dhaptar nomer paket sing ora tekan panrima bakal disusun. Nomer kasebut dilebokake ing array LostPackets saka sambungan tartamtu lan panjalukan kanggo dikirim maneh dikirim.
Ngirim panjalukan kanggo ngirim maneh paket (Status assembling):

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

Pangirim bakal nampa panjalukan pangiriman maneh lan ngirim paket sing ilang. Wigati dicathet yen ing wektu iki pangirim wis miwiti wektu nutup sambungan lan, sawise nampa panjaluk kasebut, direset.
Ngirim ulang paket sing ilang (Status 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));
}

Paket resent (paket # 3 ing diagram) ditampa dening sambungan mlebu. A mriksa digawe kanggo ndeleng yen jendhela nampa kebak, lan transmisi data normal diterusake.
Priksa manawa mlebu ing jendela panampa (Status assembling):

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 Reliable UDP

Kanggo sesambungan karo protokol transfer data, ana kelas mbukak Reliable Udp, sing pambungkus liwat blok kontrol transfer. Ing ngisor iki minangka anggota kelas sing paling penting:

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

Nampa pesen ditindakake kanthi langganan. Tandha tangan utusan kanggo metode callback yaiku:

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

Pesen:

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

Kanggo langganan jinis pesen tartamtu lan / utawa pangirim tartamtu, loro parameter opsional digunakake: ReliableUdpMessageTypes messageType lan IPEndPoint ipEndPoint.

Jinis pesen:

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

Ngirim pesen ditindakake kanthi ora sinkron, kanggo tujuan iki protokol ngetrapake model pemrograman asinkron:

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

Asil ngirim pesen bakal bener yen pesen kasil tekan panampa lan palsu yen sambungan ditutup amarga wektu entek:

public bool EndSendMessage(IAsyncResult asyncResult)

kesimpulan

Akeh sing durung diterangake ing kerangka artikel iki. Mekanisme koordinasi stream, pangecualian lan penanganan kesalahan, implementasine cara ngirim pesen bedo. Nanging inti protokol, katrangan babagan logika pangolahan paket, panyiapan sambungan lan penanganan wektu entek kudu jelas kanggo sampeyan.

Versi protokol pangiriman sing bisa dipercaya cukup kuat lan fleksibel, lan nyukupi syarat sing wis ditemtokake sadurunge. Nanging aku pengin nambah manawa implementasine sing diterangake bisa ditingkatake. Contone, kanggo nambah throughput lan mbosenke ngganti wektu wektu, mekanisme kayata jendhela ngusapake lan RTT bisa ditambahake menyang protokol, lan uga bakal migunani kanggo ngleksanakake mekanisme kanggo nemtokake MTU antarane kelenjar sambungan (nanging mung ing kasus ngirim pesen gedhe).

Matur nuwun kawigatosanipun, kula ngajeng-ajeng komentar saha sambutan.

PS Kanggo sing kasengsem ing rincian utawa mung pengin nyoba protokol, kene link menyang proyek ing GitHub:
Proyek UDP sing dipercaya

Pranala lan artikel sing migunani

  1. Spesifikasi TCP Protocol: ing basa inggris и ing basa Rusia
  2. Spesifikasi protokol UDP: ing basa inggris и ing basa Rusia
  3. Diskusi protokol RUDP: draft-ietf-sigtran-reliable-udp-00
  4. Protokol Data sing bisa dipercaya: rfc 908 и rfc 1151
  5. Implementasi prasaja konfirmasi pangiriman UDP: Ngontrol Total Jaringan Kanthi .NET Lan UDP
  6. Artikel sing njlèntrèhaké mekanisme kanggo ngatasi NAT: Komunikasi Peer-to-Peer Lintas Penerjemah Alamat Jaringan
  7. Implementasi model pemrograman asinkron: Implementasi Model Pemrograman Asynchronous CLR и Cara ngleksanakake pola desain IAsyncResult
  8. Mindhah model pemrograman asinkron menyang pola asinkron adhedhasar tugas (APM menyang TAP):
    TPL lan Pemrograman Asynchronous .NET Tradisional
    Interop karo Pola lan Jinis Asynchronous Liyane

Update: Matur nuwun mayorovp и sidristij kanggo idea nambah tugas kanggo antarmuka. Kompatibilitas perpustakaan karo OS lawas ora dilanggar, wiwit framework 4 ndhukung loro XP lan 2003 server.

Source: www.habr.com

Tuku hosting sing dipercaya kanggo situs kanthi proteksi DDoS, server VPS VDS 🔥 Tuku hosting situs web sing bisa dipercaya nganggo proteksi DDoS, server VPS VDS | ProHoster