Internét parantos seueur robih. Salah sahiji protokol inti internét, UDP, dianggo ku aplikasi henteu ngan ukur pikeun nganteurkeun datagram sareng siaran tapi ogé pikeun nyayogikeun sambungan peer-to-peer antara node jaringan. Kusabab desainna anu saderhana, protokol ieu parantos ngembangkeun seueur kagunaan anu sateuacanna teu kaduga, sanaos kakuranganna, sapertos kurangna pangiriman anu dijamin, tetep aya. Artikel ieu ngajelaskeun palaksanaan protokol pangiriman anu dijamin ngalangkungan UDP.
eusi:
asup
Arsitektur asli Internét ngabayangkeun rohangan alamat anu seragam, dimana unggal node gaduh alamat IP global sareng unik sareng tiasa komunikasi langsung sareng node anu sanés. Ayeuna, Internét, kanyataanna, gaduh arsitéktur anu béda - hiji domain alamat IP global sareng sababaraha domain kalayan alamat pribadi anu disumputkeun di tukangeun alat NAT.Dina arsitéktur ieu, ngan ukur alat-alat dina rohangan alamat global anu tiasa komunikasi kalayan gampang sareng saha waé dina jaringan, sabab aranjeunna gaduh alamat IP anu unik sareng tiasa dirouting sacara global. Hiji node dina jaringan pribadi tiasa komunikasi sareng node sanés dina jaringan anu sami, ogé sareng node anu terkenal dina rohangan alamat global. Komunikasi ieu kahontal utamina ngalangkungan mékanisme tarjamahan alamat jaringan. Alat NAT, sapertos router Wi-Fi, nyiptakeun entri khusus dina tabel tarjamahanna pikeun sambungan kaluar sareng ngarobih alamat IP sareng nomer port dina pakét. Ieu ngamungkinkeun sambungan kaluar ti jaringan pribadi ka node dina rohangan alamat global. Nanging, alat NAT biasana ngahalangan sadaya lalu lintas anu lebet kecuali aturan khusus pikeun sambungan anu lebet ditetepkeun.
Arsitektur Internét ieu cekap nyukupan pikeun interaksi klien-server, dimana klien tiasa aya dina jaringan pribadi sareng server gaduh alamat global. Nanging, éta nyiptakeun kasusah pikeun sambungan langsung antara dua node. rupa-rupa jaringan pribadi. Sambungan langsung antara dua node penting pikeun aplikasi peer-to-peer sapertos transmisi sora (Skype), aksés komputer jarak jauh (TeamViewer), atanapi kaulinan online.
Salah sahiji metode anu paling efektif pikeun ngadegkeun sambungan peer-to-peer antara alat dina jaringan pribadi anu béda disebut "hole punching." Téhnik ieu paling sering dianggo sareng aplikasi berbasis UDP.
Tapi upami aplikasi anjeun meryogikeun pangiriman data anu dijamin, contona, anjeun mindahkeun file antara komputer, maka nganggo UDP bakal nyababkeun seueur kasusah kusabab UDP sanés protokol pangiriman anu dijamin sareng henteu mastikeun pangiriman pakét dina urutan, teu sapertos protokol TCP.
Dina hal ieu, pikeun mastikeun pangiriman pakét anu dijamin, perlu pikeun nerapkeun protokol tingkat aplikasi anu nyayogikeun fungsi anu diperyogikeun sareng beroperasi di luhur UDP.
Abdi hoyong langsung nunjukkeun yén aya téknik anu disebut TCP hole punching pikeun ngadegkeun sambungan TCP antara node dina jaringan pribadi anu béda, tapi kusabab kurangna dukungan pikeun éta ku seueur alat NAT, éta biasana henteu dianggap salaku metode utama pikeun nyambungkeun node sapertos kitu.
Dina tulisan ieu, kuring ngan ukur bakal ngabahas palaksanaan protokol pangiriman anu dijamin. Implementasi téknik ngocok liang UDP bakal dijelaskeun dina tulisan-tulisan anu bakal datang.
Sarat protokol
- Pangiriman pakét anu tiasa dipercaya dilaksanakeun ngalangkungan mékanisme eupan balik positif (anu disebut pangakuan positif)
- Kabutuhan pikeun transmisi data ageung anu efisien, nyaéta protokol kedah nyingkahan transmisi ulang pakét anu teu perlu.
- Kuduna bisa mareuman mékanisme konfirmasi pangiriman (kamampuan pikeun fungsina salaku protokol UDP "murni")
- Kamungkinan pikeun nerapkeun modeu paréntah, kalayan konfirmasi unggal pesen
- Unit dasar transfer data numutkeun protokol kedah pesen
Sarat-sarat ieu sacara umum saluyu sareng sarat pikeun Protokol Data Anu Reliable anu dijelaskeun dina и , sareng abdi ngadadasarkeun protokol ieu kana standar-standar ieu.
Pikeun ngartos sarat ieu, hayu urang pertimbangkeun diagram timing pikeun transfer data antara dua node jaringan nganggo protokol TCP sareng UDP. Anggap yén dina dua kasus éta, hiji pakét leungit.
Mindahkeun data non-interaktif ngaliwatan TCP:
Sakumaha anu tiasa ditingali tina diagram, upami aya leungitna pakét, TCP bakal ngadeteksi pakét anu leungit sareng ngabéjaan ka pangirim ngeunaan éta ku cara nyuhunkeun nomer segmen anu leungit.
Pangiriman data ngalangkungan protokol UDP:
UDP teu ngalakukeun léngkah-léngkah pikeun ngadeteksi karugian. Pangaturan kasalahan dina transmisi UDP sagemblengna gumantung kana aplikasi.
Deteksi kasalahan dina protokol TCP kahontal ku cara ngadegkeun sambungan ka simpul tungtung, ngajaga kaayaan sambungan éta, nunjukkeun jumlah bait anu dikirim dina unggal header pakét, sareng ngabéjaan panampi nganggo nomer pangakuan.
Salian ti éta, pikeun ningkatkeun kinerja (nyaéta ngirim leuwih ti hiji segmen tanpa nampi pangakuan), protokol TCP nganggo anu disebut jandela transmisi—jumlah bait data anu dipiharep ditampi ku pangirim segmen.
Inpormasi anu langkung lengkep ngeunaan protokol TCP tiasa dipendakan di , kalayan UDP dina , dimana éta, kanyataanna, dihartikeun.
Tina hal di luhur, jelas yén pikeun nyiptakeun protokol pangiriman pesen anu tiasa dipercaya ngalangkungan UDP (salajengna disebut UDP anu tiasa dipercaya), perlu pikeun nerapkeun mékanisme transfer data anu sami sareng TCP. Nyaéta:
- simpen kaayaan sambungan
- nganggo nomer ruas
- nganggo pakét konfirmasi khusus
- nganggo mékanisme jandela anu disederhanakeun pikeun ningkatkeun throughput protokol
Salaku tambahan, ieu di handap diperyogikeun:
- nandakeun awal pesen pikeun ngalokasikeun sumber daya pikeun sambungan
- nandakeun réngséna hiji pesen, pikeun nepikeun pesen anu ditampi ka aplikasi hulu sareng ngaleupaskeun sumber daya protokol
- ngawenangkeun protokol khusus sambungan pikeun mareuman mékanisme konfirmasi pangiriman supados tiasa fungsina salaku UDP "murni"
Lulugu UDP anu tiasa dipercaya
Inget yén datagram UDP dienkapsulasi dina datagram IP. Pakét UDP Reliable ogé "dibungkus" dina datagram UDP.
Enkapsulasi Header UDP anu tiasa dipercaya:
Struktur header Reliable UDP téh basajan pisan:

- Bandéra – bandéra kontrol pakét
- MessageType – jinis pesen anu dianggo ku aplikasi upstream pikeun ngalanggan pesen khusus
- TransmissionId — nomer transmisi, sareng alamat sareng port panampi, sacara unik ngaidentipikasi sambungan
- Nomer Pakét – nomer pakét
- Pilihan – pilihan protokol tambahan. Dina kasus pakét anu munggaran, éta dianggo pikeun nangtukeun ukuran pesen.
Bandéra-bandéra éta nyaéta sapertos kieu:
- FirstPacket — pakét munggaran pesen
- NoAsk — pesenna henteu meryogikeun mékanisme konfirmasi pikeun diaktipkeun
- Paket Terakhir — pakét terakhir pesen
- RequestForPacket - pakét pangakuan atanapi pamundut pikeun pakét anu leungit
Prinsip umum protokol
Kusabab Reliable UDP dirancang pikeun pangiriman pesen anu dijamin antara dua node, éta kedah tiasa ngadegkeun sambungan sareng tungtung anu sanés. Pikeun ngadegkeun sambungan, pangirim ngirim pakét nganggo bandéra FirstPacket, réspon anu nunjukkeun yén sambungan parantos diadegkeun. Sadaya pakét réspon, atanapi pakét pangakuan, salawasna nyetel widang PacketNumber ka hiji anu langkung ageung tibatan PacketNumber pangluhurna tina pakét anu suksés ditampi. Widang Pilihan pikeun pakét anu dikirim munggaran ngandung ukuran pesen.
Mékanisme anu sami dianggo pikeun ngeureunkeun sambungan. Bendera LastPacket disetel dina pakét ahir pesen. Pakét réspon ngandung nomer pakét pamungkas ditambah 1, anu nunjukkeun pangiriman pesen anu suksés ka pihak anu nampi.
Diagram ngadegkeun sareng terminasi sambungan:
Sakali sambungan parantos dijieun, pangiriman data dimimitian. Data dikirimkeun dina blok pakét. Unggal blok, kecuali anu terakhir, ngandung jumlah pakét anu tetep. Jumlah ieu sami sareng ukuran jandela pangiriman/panampi. Blok data terakhir tiasa ngandung pakét anu langkung sakedik. Saatos ngirim unggal blok, pangirim ngantosan konfirmasi pangiriman atanapi pamundut pangiriman deui pakét anu leungit, ngantepkeun jandela pangiriman/panampi kabuka pikeun réspon. Saatos nampi konfirmasi pangiriman blok, jandela pangiriman/panampi digeser sareng blok data salajengna dikirim.
Pihak anu nampi nampi pakét. Unggal pakét dipariksa pikeun mastikeun éta pas dina jandela transmisi. Pakét anu henteu pas dina jandela sareng duplikatna dipiceun. Kusabab ukuran jandela parantos ditangtukeun sacara ketat sareng sami pikeun pangirim sareng panarima, upami blok pakét dikirimkeun tanpa kaleungitan, jandela digeser pikeun nampi blok data salajengna, sareng konfirmasi pangiriman dikirim. Upami jandela henteu ngeusian dina période anu ditetepkeun ku timer anu tiasa dianggo, pamariksaan dimimitian pikeun nangtukeun pakét mana anu henteu dikirimkeun, sareng pamundut pangiriman deui dikirim.
Diagram transmisi ulang:
Waktos béak sareng timer protokol
Aya sababaraha alesan kunaon sambungan teu tiasa dijieun. Contona, upami pihak anu nampi offline. Dina hal ieu, nalika nyobian nyieun sambungan, sambungan bakal béak waktos. Implementasi Reliable UDP nganggo dua timer pikeun nyetel timeout. Anu kahiji, timer anu tiasa dianggo, dianggo pikeun ngantosan réspon ti host jauh. Upami éta dijalankeun dina pihak anu ngirim, pakét terakhir anu dikirim bakal dikirimkeun deui. Upami timer dijalankeun dina pihak anu nampi, pamariksaan dilakukeun pikeun pakét anu leungit sareng pamundut pangiriman deui dikirim.
Timer kadua diperyogikeun pikeun nutup sambungan upami teu aya sambungan antara node. Pikeun pangirim, éta langsung dimimitian saatos timer padamelan béak sareng ngantosan réspon ti node jauh. Upami teu aya réspon dina période anu ditangtukeun, sambungan diputuskeun sareng sumber daya dileupaskeun. Pikeun panampi, timer nutup sambungan dimimitian saatos timer padamelan béak dua kali. Ieu diperyogikeun pikeun ngajaga tina leungitna pakét pangakuan. Nalika timer ieu béak, sambungan ogé diputuskeun sareng sumber daya dileupaskeun.
Diagram Kaayaan Transmisi UDP anu Bisa Dipercaya
Prinsip operasi protokol ieu diimplementasikeun dina mesin kaayaan terbatas, anu masing-masing kaayaan tanggung jawab kana logika pamrosésan pakét anu khusus.
Diagram kaayaan UDP anu tiasa dipercaya:

katutup – sabenerna lain kaayaan, éta titik awal jeung titik ahir pikeun automaton. Pikeun kaayaan katutup Blok kontrol transmisi ditampi, anu, nalika ngalaksanakeun server UDP asinkron, ngirimkeun pakét ka sambungan anu pas sareng ngamimitian pamrosésan kaayaan.
Pangiriman Paket Kahiji – kaayaan awal sambungan kaluar nalika pesen dikirim.
Dina kaayaan kieu, pakét munggaran dikirim pikeun pesen biasa. Pikeun pesen tanpa konfirmasi pangiriman, ieu hiji-hijina kaayaan—éta tempat sakabéh pesen dikirim.
NgirimSiklus – kaayaan dasar pikeun ngirimkeun pakét pesen.
Transisi ka dinya ti nagara bagian Pangiriman Paket Kahiji Kaayaan ieu kahontal saatos pakét pesen anu munggaran dikirim. Sadaya konfirmasi sareng pamundut pangiriman ulang dikirim ka kaayaan ieu. Kaayaan ieu tiasa kaluar dina dua kasus: pangiriman pesen anu suksés atanapi waktosna béak.
Pakét Mimiti Ditampi – kaayaan awal pikeun panampi pesen.
Éta mariksa bener henteuna awal transmisi, nyiptakeun struktur anu diperyogikeun, sareng ngirimkeun konfirmasi panampi pakét anu munggaran.
Pikeun pesen anu diwangun ku hiji pakét sareng dikirim tanpa konfirmasi pangiriman, ieu hiji-hijina kaayaan. Saatos ngolah pesen sapertos kitu, sambungan ditutup.
Assembling – kaayaan dasar pikeun narima pakét pesen.
Éta ngarékam pakét dina panyimpenan samentawis, mariksa leungitna pakét, ngirim konfirmasi pangiriman blok pakét sareng sadaya pesen, sareng ngirim pamundut pikeun pangiriman deui pakét anu leungit. Upami sadaya pesen ditampi kalayan suksés, sambungan lebet kana kaayaan réngsé, upami henteu, timeout bakal dilaksanakeun.
réngsé – nutup sambungan upami sadaya pesen parantos ditampi kalayan suksés.
Kaayaan ieu diperyogikeun pikeun ngumpulkeun deui pesen sareng dina kasus dimana konfirmasi pangiriman leungit dina perjalanan ka pangirim. Kaluar tina kaayaan ieu lumangsung ngalangkungan timeout, tapi sambungan dianggap parantos ditutup kalayan suksés.
Leuwih jero kana kodeu. Unit kontrol transmisi
Salah sahiji unsur konci Reliable UDP nyaéta blok kontrol transmisi. Pancén blok ieu nyaéta pikeun nyimpen sambungan ayeuna sareng unsur bantu, nyebarkeun pakét anu lebet ka sambungan anu pas, nyayogikeun antarmuka pikeun ngirim pakét ka sambungan, sareng ngalaksanakeun API protokol. Blok kontrol transmisi nampi pakét ti lapisan UDP sareng neraskeun ka mesin nagara pikeun diprosés. Pikeun nampi pakét, éta ngalaksanakeun server UDP asinkron.
Sababaraha 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);
}
Pikeun unggal transfer pesen, hiji struktur dijieun anu ngandung informasi sambungan. Struktur ieu disebut catetan sambungan.
Sababaraha 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;
//...
}
Leuwih jero kana kode éta. Nagara
Nagara-nagara ngalaksanakeun mesin nagara protokol Reliable UDP, tempat kalolobaan pamrosésan pakét lumangsung. Kelas abstrak ReliableUdpState nyayogikeun antarmuka pikeun nagara-nagara:

Sakabéh logika protokol diimplementasikeun ku kelas-kelas anu dipidangkeun di luhur, sareng kelas bantu anu nyayogikeun metode statis, sapertos, contona, ngawangun header ReliableUdp tina rékaman sambungan.
Salajengna, urang bakal mertimbangkeun sacara rinci palaksanaan metode antarmuka anu ngahartikeun algoritma dasar pikeun operasi protokol.
Métode DisposeByTimeout
Métode DisposeByTimeout tanggung jawab pikeun ngaleupaskeun sumber daya sambungan saatos timeout sareng pikeun nunjukkeun kasuksésan/kagagalan pangiriman pesen.
ReliableUdpState.DisposeByTimeout: (teu kahartos)
protected virtual void DisposeByTimeout(object record)
{
ReliableUdpConnectionRecord connectionRecord = (ReliableUdpConnectionRecord) record;
if (record.AsyncResult != null)
{
connectionRecord.AsyncResult.SetAsCompleted(false);
}
connectionRecord.Dispose();
}
Éta ngan ukur ditimpa di nagara bagian réngsé.
Réngsé.MiceunKuWaktosna béak:
protected override void DisposeByTimeout(object record)
{
ReliableUdpConnectionRecord connectionRecord = (ReliableUdpConnectionRecord) record;
// сообщаем об успешном получении сообщения
SetAsCompleted(connectionRecord);
}
Métode ProcessPackets
Métode ProcessPackets tanggung jawab pikeun pamrosésan tambahan tina hiji pakét atawa sababaraha pakét. Éta disebut sacara langsung atanapi ngalangkungan timer pakét.
Sanggup Assembling métode ieu ditimpa sareng tanggung jawab pikeun mariksa pakét anu leungit sareng transisi ka kaayaan réngsé, upami nampi pakét terakhir sareng lulus pamariksaan kalayan suksés
Perakitan.Paket Prosés:
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);
}
}
Sanggup NgirimSiklus Métode ieu ngan ukur dipanggil ku timer, sareng tanggung jawab pikeun ngirim deui pesen terakhir, ogé pikeun ngaktipkeun timer nutup sambungan.
Paket Prosés NgirimkeunSiklus:
public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
if (connectionRecord.IsDone != 0)
return;
// отправляем повторно последний пакет
// ( в случае восстановления соединения узел-приемник заново отправит запросы, которые до него не дошли)
ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.RetransmissionCreateUdpPayload(connectionRecord, connectionRecord.SndNext - 1));
// включаем таймер CloseWait – для ожидания восстановления соединения или его завершения
StartCloseWaitTimer(connectionRecord);
}
Sanggup réngsé Métode ieu ngeureunkeun timer anu dijalankeun sareng nyiarkeun pesen ka palanggan.
Réngsé.Paket Prosés:
public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
if (connectionRecord.WaitForPacketsTimer != null)
connectionRecord.WaitForPacketsTimer.Dispose();
// собираем сообщение и передаем его подписчикам
ReliableUdpStateTools.CreateMessageFromMemoryStream(connectionRecord);
}
Métode NampiPacket
Sanggup Pakét Mimiti Ditampi Tugas utama tina metode ieu nyaéta pikeun nangtukeun naha pakét pesen anu munggaran leres-leres sumping ka antarmuka, sareng ogé pikeun ngumpulkeun pesen anu diwangun ku hiji pakét.
PakétMunggaranDitampi.PaketNampi:
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);
}
}
Sanggup NgirimSiklus Métode ieu diganti pikeun nampi konfirmasi pangiriman sareng pamundut pangiriman ulang.
NgirimSiklus.NarimaPaket:
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));
}
Sanggup Assembling Métode ReceivePacket nyaéta tempat padamelan utama pikeun ngumpulkeun pesen tina pakét anu asup lumangsung.
Ngumpulkeun.Paket Narima:
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);
}
}
Sanggup réngsé Hiji-hijina tugas tina metode ieu nyaéta ngirim konfirmasi ulang yén pangiriman pesen parantos suksés.
Réngsé.Paket Narima:
public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
// повторная отправка последнего пакета в связи с тем,
// что последний ack не дошел до отправителя
if (header.Flags.HasFlag(ReliableUdpHeaderFlags.LastPacket))
{
ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
}
}
Métode SendPacket
Sanggup Pangiriman Paket Kahiji Métode ieu ngirim pakét data anu munggaran, atanapi, upami pesenna henteu meryogikeun konfirmasi pangiriman, sadayana pesen.
PacketSend.KirimPacket:
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);
}
Sanggup NgirimSiklus Dina metode ieu, sakumpulan pakét dikirim.
NgirimSiklus.KirimPaket:
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 );
}
}
Ngalenyepan Kodeu Langkung Jero: Nyiptakeun sareng Ngawangun Konéksi
Ayeuna urang parantos ngabahas kaayaan dasar sareng metode anu dianggo pikeun nanganan kaayaan, urang tiasa ningali sababaraha conto kumaha protokol ieu jalanna langkung rinci.
Diagram transfer data dina kaayaan normal:
Hayu urang tingali langkung caket kana ciptaan éta catetan sambungan pikeun ngadegkeun sambungan sareng ngirim pakét anu munggaran. Transfer sok dimimitian ku aplikasi anu nyauran metode Send API. Salajengna, metode StartTransmission tina Transmission Control Block dijalankeun, anu ngamimitian transfer data pikeun pesen anyar.
Nyieun sambungan kaluar:
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 pakét munggaran (kaayaan FirstPacketSending):
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);
}
Saatos ngirim pakét anu munggaran, pangirim ngalebetkeun kaayaan NgirimSiklus - ngantosan konfirmasi pangiriman paket.
Pihak anu nampi, nganggo metode EndReceive, nampi pakét anu dikirim, nyiptakeun pakét énggal catetan sambungan sareng ngirimkeun pakét ieu, kalayan header anu parantos diurai sateuacanna, ka metode kaayaan ReceivePacket pikeun diprosés. Pakét Mimiti Ditampi
Nyieun sambungan di sisi panarima:
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);
}
Narima pakét munggaran sareng ngirim pangakuan (kaayaan 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);
}
}
Ngalenyepan kodeu leuwih jero. Nutup sambungan nalika timeout
Nanganan timeout mangrupikeun bagian penting tina Reliable UDP. Hayu urang pertimbangkeun conto dimana node perantara gagal, nyegah pangiriman data dina dua arah.
Diagram panutupan waktos sambungan béak:
Sakumaha anu dipidangkeun dina diagram, timer padamelan pangirim dimimitian langsung saatos ngirim blok pakét. Ieu lumangsung dina metode kaayaan SendPacket. NgirimSiklus.
Ngaktipkeun timer anu tiasa dianggo (kaayaan SendingCycle):
public override void SendPacket(ReliableUdpConnectionRecord connectionRecord)
{
// отправляем блок пакетов
// ...
// перезапускаем таймер после отправки
connectionRecord.WaitForPacketsTimer.Change( connectionRecord.ShortTimerPeriod, -1 );
if ( connectionRecord.CloseWaitTimer != null )
connectionRecord.CloseWaitTimer.Change( -1, -1 );
}
Periode timer disetel nalika nyieun sambungan. Sacara standar, ShortTimerPeriod nyaéta 5 detik. Dina conto ieu, éta disetel ka 1,5 detik.
Pikeun sambungan anu lebet, timer dimimitian saatos nampi pakét data anu terakhir lebet, ieu kajantenan dina metode ReceivePacket tina kaayaan éta. Assembling
Ngaktipkeun timer anu tiasa dianggo (Kaayaan perakitan):
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);
// ...
}
Teu aya deui pakét anu sumping dina sambungan anu lebet nalika timer anu dianggo ngantosan. Timer éta kadaluwarsa sareng nyauran metode ProcessPackets, anu ngadeteksi pakét anu leungit sareng ngirim pamundut pangiriman deui pikeun anu mimiti.
Ngintunkeun pamundut pangiriman deui (Kaayaan ngarakit):
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 ka benerVariabel ieu tanggung jawab pikeun ngamimitian deui timer anu tiasa dianggo.
Di sisi pangirim, timer anu tiasa dianggo ogé dipicu sareng pakét anu terakhir dikirim dikirim deui.
Ngaktipkeun pangatur waktu nutup sambungan (kaayaan SendingCycle):
public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
// ...
// отправляем повторно последний пакет
// ...
// включаем таймер CloseWait – для ожидания восстановления соединения или его завершения
StartCloseWaitTimer(connectionRecord);
}
Saatos éta, pangatur waktu nutup sambungan dimimitian dina sambungan kaluar.
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 wates waktu panutupan sambungan nyaéta 30 detik sacara standar.
Saatos waktos anu singget, timer anu tiasa dianggo di sisi panampi dipicu deui, pamundut dikirim deui, saatos éta timer panutupan sambungan dimimitian pikeun sambungan anu lebet.
Nalika timer nutup kadaluwarsa, sadaya sumber daya tina dua rékaman sambungan dileupaskeun. Pangirim ngalaporkeun kagagalan pangiriman ka aplikasi hulu (Tingali API UDP anu tiasa dipercaya).
Ngabébaskeun sumber daya rékaman 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);
}
}
Leuwih jero kana kodeu. Mulangkeun transfer data
Diagram pamulihan transmisi data upami aya leungitna pakét:
Sapertos anu parantos dibahas sateuacanna dina timeout sambungan, nalika timer kadaluwarsa, panarima bakal mariksa pakét anu leungit. Upami pakét leungit, daptar nomer pakét anu henteu dugi ka panarima bakal dikompilasi. Nomer-nomer ieu ditambahkeun kana array LostPackets pikeun sambungan khusus, sareng pamundut pangiriman ulang dikirim.
Ngirim pamundut pangiriman ulang pakét (Kaayaan ngarakit):
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 nampi pamundut pangiriman ulang sareng ngirim deui pakét anu leungit. Perlu dicatet yén dina tahap ieu, pangirim parantos gaduh timer nutup sambungan anu jalan, sareng saatos nampi pamundut pangiriman ulang, éta bakal diset ulang.
Ngirim deui pakét anu leungit (kaayaan 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 anu dikirimkeun deui (pakét #3 dina diagram) ditampi ku sambungan anu lebet. Pamariksaan dilaksanakeun pikeun ningali naha jandela panampi pinuh, sareng transmisi data normal diteruskeun deui.
Mariksa naha asup kana jandela panarima (kaayaan ngarakit):
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 UDP anu tiasa dipercaya
Pikeun berinteraksi sareng protokol transfer data, aya kelas kabuka, Reliable Udp, anu ngabungkus blok kontrol transmisi. Ieu anggota kelas anu 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()
}
Narima pesen dilakukeun via langganan. Tanda tangan delegasi pikeun metode callback nyaéta:
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; }
}
Pikeun ngalanggan jinis pesen anu khusus sareng/atanapi pangirim anu khusus, dua parameter opsional dianggo: ReliableUdpMessageTypes messageType sareng IPEndPoint ipEndPoint.
Jenis-jenis pesen:
public enum ReliableUdpMessageTypes : short
{
// Любое
Any = 0,
// Запрос к STUN server
StunRequest = 1,
// Ответ от STUN server
StunResponse = 2,
// Передача файла
FileTransfer =3,
// ...
}
Ngirim pesen dilakukeun sacara asinkron, pikeun tujuan ieu protokol nerapkeun modél pamrograman asinkron:
public IAsyncResult BeginSendMessage(ReliableUdpMessage reliableUdpMessage, IPEndPoint remoteEndPoint, AsyncCallback asyncCallback, Object state)
Hasil tina ngirim pesen bakal leres upami pesen éta hasil dugi ka panampi sareng palsu upami sambungan ditutup kusabab timeout:
public bool EndSendMessage(IAsyncResult asyncResult)
kacindekan
Seueur anu teu acan dibahas dina tulisan ieu, kalebet mékanisme koordinasi thread, penanganan pengecualian sareng kasalahan, sareng implementasi metode pengiriman pesen asinkron. Nanging, inti protokol, pedaran logika pamrosésan pakét, pembentukan sambungan, sareng penanganan timeout kedah langkung jelas pikeun anjeun.
Versi protokol pangiriman anu tiasa dipercaya anu dipidangkeun cekap kuat sareng fleksibel, sareng nyumponan sarat anu ditetepkeun sateuacanna. Nanging, kuring hoyong nambihan yén implementasi anu dijelaskeun tiasa ditingkatkeun. Salaku conto, mékanisme sapertos jandéla geser sareng RTT tiasa ditambihkeun kana protokol pikeun ningkatkeun throughput sareng nyaluyukeun période timer sacara dinamis. Ngalaksanakeun mékanisme pikeun nangtukeun MTU antara simpul dina sambungan ogé bakal mangpaat (tapi ngan ukur nalika ngirim pesen ageung).
Hatur nuhun kana perhatianana, sim kuring ngarepkeun koméntar sareng teguran ti anjeun.
PS Pikeun anu resep kana detil atanapi ngan saukur hoyong nguji protokolna, ieu tautan ka proyékna dina GitHub:
Tautan sareng artikel anu mangpaat
- Spésifikasi Protokol TCP: и
- Spésifikasi protokol UDP: и
- Diskusi ngeunaan protokol RUDP:
- Protokol Data anu Bisa Dipercaya: и
- Implementasi basajan tina konfirmasi pangiriman ngalangkungan UDP:
- Artikel anu ngajelaskeun mékanisme pikeun ngungkulan NAT:
- Implementasi modél pamrograman asinkron: и
- Mindahkeun modél pamrograman asinkron ka pola asinkron berbasis tugas (APM ka TAP):
Apdet: Hatur nuhun и pikeun ideu nambihan tugas kana antarmuka. Kompatibilitas perpustakaan sareng sistem operasi anu langkung lami henteu kapangaruhan, sabab kerangka ka-4 ngadukung server XP sareng 2003.
sumber: www.habr.com
