Implementasi protokol Udp Reliable kanggo .Net

Internet wis suwe saya ganti. Salah siji saka protokol utama Internet - UDP digunakake dening aplikasi ora mung kanggo ngirim datagrams lan siaran, nanging uga kanggo nyedhiyani sambungan "peer-to-peer" antarane simpul jaringan. Amarga desain sing prasaja, protokol iki akeh panggunaan sing ora direncanakake sadurunge, nanging kekurangan protokol kasebut, kayata kekurangan pangiriman sing dijamin, ora ilang ing endi wae. Artikel iki nerangake 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 menyang kode. Nggawe lan Nggawe Sambungan
Luwih jero menyang kode. Nutup sambungan ing wektu entek
Luwih jero menyang kode. Mulihake transfer data
UDP API sing dipercaya
kesimpulan
Pranala lan artikel sing migunani

entri

Arsitèktur asli Internet nganggep spasi alamat homogen 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 sapa wae ing jaringan amarga duwe alamat IP sing unik lan bisa diruteake sacara global. A simpul ing jaringan pribadi bisa nyambung menyang simpul liyane ing jaringan sing padha, lan uga bisa nyambung menyang simpul kondhang liyane ing papan alamat global. Interaksi iki bisa ditindakake amarga mekanisme terjemahan alamat jaringan. Piranti NAT, kayata router Wi-Fi, nggawe entri tabel terjemahan khusus kanggo sambungan metu lan ngowahi alamat IP lan nomer port ing paket. Iki ngidini sambungan metu saka jaringan pribadi menyang host ing papan alamat global. Nanging ing wektu sing padha, piranti NAT biasane mblokir kabeh lalu lintas sing mlebu kajaba aturan sing kapisah kanggo sambungan mlebu.

Arsitektur Internet iki cukup bener kanggo komunikasi klien-server, ing ngendi klien bisa ana 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), entuk akses remot menyang komputer (TeamViewer), utawa game online.

Salah sawijining cara sing paling efektif kanggo nggawe sambungan peer-to-peer ing antarane piranti ing jaringan pribadi sing beda diarani hole punching. Teknik iki paling umum digunakake karo aplikasi adhedhasar protokol UDP.

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

Ing kasus iki, kanggo mesthekake pangiriman paket dijamin, iku kudu ngleksanakake protokol lapisan aplikasi sing nyedhiyani fungsi perlu lan bisa liwat UDP.

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

Kanggo sisa artikel iki, aku mung bakal fokus ing implementasine protokol pangiriman sing 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. Kabutuhan kanggo transfer efisien data amba, i.e. protokol kudu ngindhari relaying paket sing ora perlu
  3. Sampeyan kudu bisa mbatalake mekanisme konfirmasi pangiriman (kemampuan kanggo fungsi minangka protokol UDP "murni")
  4. Kemampuan kanggo ngleksanakake mode printah, kanthi konfirmasi saben pesen
  5. Unit dhasar transfer data liwat protokol kudu pesen

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

Kanggo mangerteni syarat kasebut, ayo deleng wektu transfer data antarane rong node jaringan nggunakake protokol TCP lan UDP. Ayo ing loro kasus kita bakal duwe siji paket ilang.
Transfer data non-interaktif liwat TCP:Implementasi protokol Udp Reliable kanggo .Net

Nalika sampeyan bisa ndeleng saka diagram, ing cilik saka paket mundhut, TCP bakal ndeteksi paket ilang lan laporan menyang pangirim kanthi takon nomer bagean ilang.
Transfer data liwat protokol UDP:Implementasi protokol Udp Reliable kanggo .Net

UDP ora njupuk langkah deteksi mundhut. Kontrol kesalahan transmisi ing protokol UDP tanggung jawab saka aplikasi.

Deteksi kesalahan ing protokol TCP digayuh kanthi nggawe sambungan karo simpul pungkasan, nyimpen status sambungan kasebut, nuduhake jumlah bita sing dikirim ing saben header paket, lan menehi notifikasi 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.

Kanggo informasi luwih lengkap babagan protokol TCP, waca rfc 793, saka UDP kanggo rfc 768ngendi, ing kasunyatan, padha ditetepake.

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

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

Kajaba iku, sampeyan kudu:

  • sinyal wiwitan pesen, kanggo nyedhiakke sumber kanggo sambungan
  • menehi tandha pungkasan pesen, kanggo ngirim pesen sing ditampa menyang aplikasi hulu lan ngeculake sumber protokol
  • ngidini protokol sambungan-tartamtu mateni mekanisme konfirmasi pangiriman kanggo fungsi minangka "murni" UDP

Header UDP sing bisa dipercaya

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

Struktur header UDP Reliable cukup prasaja:

Implementasi protokol Udp Reliable kanggo .Net

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

Gendéra kaya ing ngisor iki:

  • FirstPacket - paket pisanan pesen
  • NoAsk - pesen ora mbutuhake mekanisme pangenalan bisa diaktifake
  • LastPacket - paket pungkasan pesen
  • RequestForPacket - paket konfirmasi utawa njaluk paket ilang

Prinsip umum protokol

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

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

Nalika sambungan digawe, transfer 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 sithik. 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 pamblokiran, jendhela nampa / ngirim pindhah lan pamblokiran sabanjuré data dikirim.

Sisih panampa nampa paket. Saben paket dicenthang kanggo ndeleng manawa ana ing jendela transmisi. Paket lan duplikat sing ora tiba ing jendhela disaring. Amarga Yen ukuran jendhela tetep lan padha kanggo panampa lan pangirim, banjur ing kasus pamblokiran paket dikirim tanpa mundhut, jendhela dipindhah kanggo nampa paket saka pamblokiran data sabanjuré lan konfirmasi pangiriman. dikirim. Yen jendhela ora kebak ing wektu sing disetel dening timer kerja, mriksa bakal diwiwiti ing paket sing durung dikirim lan panjaluk kiriman maneh 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 dening wektu entek. Implementasi UDP Reliable nggunakake rong timer kanggo nyetel wektu entek. Pisanan, timer kerja, digunakake kanggo ngenteni respon saka host remot. Yen murub ing sisih pangirim, banjur paket pungkasan dikirim maneh. Yen wektu kadaluwarsa ing panampa, mriksa paket sing ilang ditindakake lan panjaluk kiriman ulang dikirim.

Timer kapindho dibutuhake kanggo nutup sambungan yen ana kekurangan komunikasi antarane simpul. Kanggo sisih pangirim, diwiwiti sanalika sawise wektu kerja kadaluwarsa, lan ngenteni respon saka simpul remot. Yen ora ana respon ing wektu sing ditemtokake, sambungan bakal mandheg lan sumber daya bakal dirilis. Kanggo sisih panampa, wektu cedhak sambungan diwiwiti sawise wektu kerja kadaluwarsa kaping pindho. Iki perlu kanggo njamin marang mundhut saka paket konfirmasi. Nalika wektu kadaluwarsa, sambungan uga mandheg lan sumber daya bakal dirilis.

Diagram negara transmisi UDP sing dipercaya

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

Implementasi protokol Udp Reliable kanggo .Net

Ditutup - dudu negara, iku titik wiwitan lan pungkasan kanggo automaton. Kanggo negara Ditutup pemblokiran kontrol transmisi ditampa, kang, ngleksanakake server UDP bedo, forwards paket menyang sambungan cocok lan miwiti Processing negara.

FirstPacketSending – negara wiwitan ing ngendi sambungan metu nalika pesen dikirim.

Ing negara iki, paket pisanan kanggo pesen normal dikirim. Kanggo pesen tanpa konfirmasi kirim, iki mung negara ngendi kabeh pesen dikirim.

SendingCycle – ground state kanggo transmisi paket pesen.

Transisi saka negara FirstPacketSending digawa metu sawise paket pisanan pesen wis dikirim. Ing negara kasebut kabeh panjaluk lan panjaluk kanggo transmisi ulang teka. Metu saka iku bisa ing rong kasus - ing cilik saka sukses pangiriman pesen utawa dening wektu entek.

FirstPacketReceived – negara wiwitan kanggo panampa pesen.

Iki mriksa akurasi wiwitan transmisi, nggawe struktur sing dibutuhake, lan ngirim pangakuan nampa paket pisanan.

Kanggo pesen sing kasusun saka paket siji lan dikirim tanpa nggunakake bukti pangiriman, iki mung negara. Sawise ngolah pesen kasebut, sambungan ditutup.

Assembling - negara dhasar kanggo nampa paket pesen.

Iku nulis paket kanggo panyimpenan sak wentoro, mriksa kanggo paket mundhut, ngirim pangakuan kanggo layang saka pamblokiran paket lan kabeh pesen, lan ngirim panjalukan kanggo redelivery saka paket ilang. Ing cilik saka sukses panrimo kabeh pesen, sambungan dadi menyang negara rampung, yen ora, wektu entek metu.

rampung – nutup sambungan ing cilik saka sukses nampa kabeh pesen.

Negara iki perlu kanggo ngumpulake pesen lan kanggo kasus nalika konfirmasi pangiriman pesen ilang ing dalan menyang pangirim. Negara iki metu 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 pemblokiran iki kanggo nyimpen sambungan saiki lan unsur tambahan, disebaraké paket mlebu menyang sambungan cocog, nyedhiyani antarmuka kanggo ngirim paket kanggo sambungan, lan ngleksanakake API protokol. Blok kontrol transmisi nampa paket saka lapisan UDP lan diterusake menyang mesin negara kanggo diproses. Kanggo nampa paket, iku ngleksanakake server UDP asynchronous.
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 kasebut 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 Processing utama paket njupuk Panggonan. Kelas abstrak ReliableUdpState nyedhiyakake antarmuka kanggo 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 protokol kasebut.

Metode DisposeByTimeout

Cara DisposeByTimeout tanggung jawab kanggo ngeculake sumber sambungan sawise wektu entek lan menehi tandha pangiriman pesen sing sukses/kasil.
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 sukses
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 diarani mung ing wektu, lan tanggung jawab kanggo ngirim maneh pesen pungkasan, uga mbisakake wektu cedhak 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 mandheg wektu 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 saka cara iki kanggo nemtokake apa paket pesen pisanan bener teka ing antarmuka, lan uga kanggo ngumpulake pesen dumadi saka paket siji.
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 ditindhes kanggo nampa panyuwunan pangiriman lan panjalukan retransmisi.
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 ing metode ReceivePacket, karya utama ngumpulake pesen saka paket sing mlebu ditindakake.
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 mung tugas saka cara kanggo ngirim maneh ngakoni saka pangiriman sukses pesen.
Rampung.ReceivePacket:

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

Metode Kirim Paket

saged FirstPacketSending cara iki ngirim paket pisanan data, 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 menyang kode. Nggawe lan Nggawe Sambungan

Saiki kita wis weruh negara dhasar lan cara sing digunakake kanggo nangani negara, ayo padha break mudhun sawetara conto carane protokol dianggo ing dicokot liyane rinci.
Diagram transmisi data ing kahanan normal:Implementasi protokol Udp Reliable kanggo .Net

Coba rinci babagan nggawe rekaman sambungan kanggo nyambung lan ngirim paket pisanan. Transfer tansah diwiwiti dening aplikasi sing nelpon API pesen ngirim. Sabanjure, metode StartTransmission saka blok kontrol transmisi dijaluk, sing miwiti transmisi 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.
Sisih panampa, nggunakake metode EndReceive, nampa paket sing dikirim, nggawe anyar rekaman sambungan lan ngliwati paket iki, kanthi header sing wis diurai, menyang metode ReceivePacket saka negara 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

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

Kaya sing bisa dideleng saka diagram, wektu kerja pangirim diwiwiti langsung sawise ngirim blok paket. Iki kedadeyan ing metode SendPacket negara SendingCycle.
Ngaktifake timer 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. ShortTimerPeriod standar 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);
  // ...
}

Ora ana paket maneh sing teka ing sambungan sing mlebu nalika ngenteni wektu kerja. Timer mati lan diarani metode ProcessPackets, ing ngendi paket sing ilang ditemokake lan panjaluk kiriman ulang dikirim 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 wektu cedhak sambungan (Status SendingCycle):

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

Sawise iku, wektu cedhak 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 sawetara wektu, timer sing digunakake ing sisih panampa murub maneh, panjaluk dikirim maneh, sawise wektu cedhak sambungan diwiwiti kanggo sambungan mlebu

Nalika timer cedhak murub, kabeh sumber daya saka loro cathetan sambungan dirilis. Pangirim nglaporake kegagalan pangiriman menyang aplikasi hulu (ndeleng Reliable UDP API).
Ngeculake 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. Mulihake transfer data

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

Kaya sing wis dibahas nalika nutup sambungan nalika entek, nalika wektu kerja wis kadaluwarsa, panrima bakal mriksa paket sing ilang. Yen paket ilang, dhaptar jumlah paket sing ora tekan panampa bakal disusun. Nomer kasebut dilebokake ing array LostPackets saka sambungan tartamtu, lan panjaluk kiriman ulang 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 cedhak sambungan lan, nalika panjalukan ditampa, 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 dibalèkaké.
Priksa tekan ing jendela nampa (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);
  }
  // ...
}

UDP API sing dipercaya

Kanggo sesambungan karo protokol transfer data, ana kelas Udp Reliable sing mbukak, yaiku 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()    
}

Pesen ditampa kanthi langganan. Delegasi tandha tangan kanggo metode callback:

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

Pesen dikirim kanthi ora sinkron; kanggo 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 dening wektu entek:

public bool EndSendMessage(IAsyncResult asyncResult)

kesimpulan

Akeh sing durung diterangake ing artikel iki. Mekanisme pencocokan thread, pangecualian lan penanganan kesalahan, implementasine cara ngirim pesen bedo. Nanging inti saka protokol, katrangan babagan logika kanggo ngolah paket, nggawe sambungan, lan nangani wektu entek, kudu jelas kanggo sampeyan.

Versi sing dituduhake protokol pangiriman sing dipercaya cukup kuat lan fleksibel kanggo nyukupi syarat sing wis ditemtokake sadurunge. Nanging aku pengin nambah manawa implementasine sing diterangake bisa ditingkatake. Contone, kanggo nambah throughput lan ngganti wektu wektu kanthi dinamis, mekanisme kayata jendhela geser lan RTT bisa ditambahake ing protokol, uga migunani kanggo ngetrapake mekanisme kanggo nemtokake MTU antarane simpul sambungan (nanging mung yen pesen gedhe dikirim). .

Matur nuwun kanggo kawigatosan sampeyan, aku ngarepake komentar lan komentar sampeyan.

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

Pranala lan artikel sing migunani

  1. Spesifikasi protokol TCP: 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 liwat UDP: Ngontrol Total Jaringan Kanthi .NET Lan UDP
  6. Artikel sing nerangake mekanisme traversal 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. Porting model pemrograman asinkron menyang pola asinkron adhedhasar tugas (APM ing TAP):
    TPL lan Pemrograman Asynchronous .NET Tradisional
    Interop karo Pola lan Jinis Asynchronous Liyane

Update: Matur nuwun mayorovp и sidristij kanggo gagasan nambah tugas menyang antarmuka. Kompatibilitas perpustakaan karo sistem operasi lawas ora nerak, amarga Framework kaping 4 ndhukung server XP lan 2003.

Source: www.habr.com

Add a comment