Ëmsetzung vun der Reliable Udp Protokoll fir .Net

Den Internet huet viru laanger Zäit geännert. Ee vun den Haaptprotokoller vum Internet - UDP gëtt vun Uwendungen benotzt net nëmmen fir Datagrammen a Sendungen ze liwweren, awer och fir "peer-to-peer" Verbindungen tëscht Netzwierkknoten ze bidden. Wéinst sengem einfachen Design huet dëse Protokoll vill virdru ongeplangte Gebrauch, awer d'Defiziter vum Protokoll, wéi de Mangel u garantéierte Liwwerung, sinn néierens verschwonnen. Dësen Artikel beschreift d'Ëmsetzung vum garantéierte Liwwerungsprotokoll iwwer UDP.
Inhalt:Element
Protokoll Ufuerderunge
Zuverlässeg UDP Header
Allgemeng Prinzipien vum Protokoll
Timeouts a Protokoll Timer
Zouverlässeg UDP Transmissioun Staat Diagramm
Méi déif an de Code. Transmissioun Kontroll Eenheet
Méi déif an de Code. Staaten

Méi déif an de Code. Schafen an Opbau vun Verbindungen
Méi déif an de Code. D'Verbindung op Timeout zoumaachen
Méi déif an de Code. Restauratioun vun Daten Transfert
Zouverlässeg UDP API
Konklusioun
Nëtzlech Linken an Artikelen

Element

Déi ursprénglech Architektur vum Internet huet en homogenen Adressraum ugeholl, an deem all Node eng global an eenzegaarteg IP Adress hat an direkt mat anere Wirbelen kommunizéieren konnt. Elo huet den Internet tatsächlech eng aner Architektur - ee Gebitt vu globale IP Adressen a vill Beräicher mat privaten Adressen hannert NAT Apparater verstoppt.An dëser Architektur kënnen nëmmen Apparater am globalen Adressraum einfach mat jidderengem am Netz kommunizéieren, well se eng eenzegaarteg, global routerbar IP Adress hunn. E Node op engem privaten Netzwierk kann mat anere Wirbelen am selwechte Netz verbannen, a kann och mat anere bekannte Wirbelen am globalen Adressraum verbannen. Dës Interaktioun gëtt gréisstendeels erreecht wéinst dem Netzwierk Adress Iwwersetzungsmechanismus. NAT-Geräter, wéi Wi-Fi Router, kreéieren speziell Iwwersetzungstabelle fir erausginn Verbindungen a änneren IP Adressen a Portnummeren a Paketen. Dëst erlaabt erausginn Verbindungen aus dem privaten Netzwierk op Hosten am globalen Adressraum. Awer zur selwechter Zäit blockéieren NAT-Geräter normalerweis all erakommend Traffic, ausser separat Reegele fir erakommende Verbindunge festgeluecht ginn.

Dës Architektur vum Internet ass korrekt genuch fir Client-Server Kommunikatioun, wou Clienten a privaten Netzwierker kënne sinn, a Serveren eng global Adress hunn. Mä et schaaft Schwieregkeeten fir déi direkt Verbindung vun zwee Wirbelen tëscht verschidde privat Netzwierker. Eng direkt Verbindung tëscht zwee Wirbelen ass wichteg fir Peer-to-Peer Uwendungen wéi Stëmm Iwwerdroung (Skype), Erfindungen Zougang zu engem Computer (TeamViewer), oder online Spillerinne.

Ee vun den effektivsten Methoden fir eng Peer-to-Peer Verbindung tëscht Apparater op verschiddene privaten Netzwierker z'etabléieren ass Lachpunching genannt. Dës Technik ass am meeschten benotzt mat Uwendungen baséiert op der UDP Protokoll.

Awer wann Är Uwendung eng garantéiert Liwwerung vun Daten erfuerdert, zum Beispill, Dir transferéiert Dateien tëscht Computeren, da wäert d'Benotzung vun UDP vill Schwieregkeeten hunn wéinst der Tatsaach datt UDP kee garantéierte Liwwerprotokoll ass a keng Packet Liwwerung an Uerdnung ubitt, am Géigesaz zum TCP Protokoll.

An dësem Fall, fir garantéiert Packet Liwwerung ze garantéieren, ass et erfuerderlech en Applikatiounsschichtprotokoll ëmzesetzen deen déi néideg Funktionalitéit ubitt an iwwer UDP funktionnéiert.

Ech wëll direkt bemierken datt et eng TCP Lachpunching Technik gëtt fir TCP Verbindungen tëscht Wirbelen a verschiddene privaten Netzwierker z'etabléieren, awer wéinst dem Mangel un Ënnerstëtzung fir et vu villen NAT-Geräter, gëtt et normalerweis net als den Haapt Wee fir ze verbannen. esou Noden.

Fir de Rescht vun dësem Artikel konzentréieren ech mech nëmmen op d'Ëmsetzung vum garantéierte Liwwerungsprotokoll. D'Ëmsetzung vun der UDP Lachpunching Technik gëtt an de folgenden Artikelen beschriwwen.

Protokoll Ufuerderunge

  1. Zouverlässeg Paket Liwwerung duerch e positive Feedback Mechanismus ëmgesat (déi sougenannte positiv Unerkennung)
  2. De Besoin fir effizienten Transfert vu Big Data, d.h. de Protokoll muss onnéideg Paket-Relais vermeiden
  3. Et sollt méiglech sinn de Liwwerungsconfirmatiounsmechanismus ze annuléieren (d'Fäegkeet als "reng" UDP-Protokoll ze funktionéieren)
  4. D'Kapazitéit fir de Kommandomodus ëmzesetzen, mat Bestätegung vun all Message
  5. D'Basis Eenheet vum Datenübertragung iwwer de Protokoll muss e Message sinn

Dës Ufuerderunge entspriechen gréisstendeels mat de Reliable Data Protocol Ufuerderunge beschriwwen an rfc908 и rfc1151, an ech hunn op dës Norme vertraut wann ech dëse Protokoll entwéckelt hunn.

Fir dës Ufuerderungen ze verstoen, kucke mer den Timing vum Datenübertragung tëscht zwee Netzwierkknoten mat den TCP an UDP Protokoller. Loosst eis a béide Fäll e Paket verluer hunn.
Transfert vun net-interaktiven Donnéeën iwwer TCP:Ëmsetzung vun der Reliable Udp Protokoll fir .Net

Wéi Dir aus dem Diagramm kënnt gesinn, am Fall vu Paketverloscht, wäert TCP de verluerene Paket entdecken an et dem Sender mellen andeems Dir d'Zuel vum verluerene Segment freet.
Datenübertragung iwwer UDP Protokoll:Ëmsetzung vun der Reliable Udp Protokoll fir .Net

UDP hëlt keng Verloschterkennungsschrëtt. Kontroll vun Iwwerdroungsfehler am UDP Protokoll ass ganz d'Verantwortung vun der Applikatioun.

Feelererkennung am TCP-Protokoll gëtt erreecht andeems Dir eng Verbindung mat engem Endknuet opstellt, den Zoustand vun där Verbindung späichert, d'Zuel vun de Bytes ugewisen, déi an all Paket-Header geschéckt ginn, an d'Empfange mat Hëllef vun enger Unerkennungsnummer informéieren.

Zousätzlech, fir d'Performance ze verbesseren (dh méi wéi ee Segment ze schécken ouni eng Unerkennung ze kréien), benotzt den TCP Protokoll déi sougenannt Iwwerdroungsfenster - d'Zuel vun de Bytes vun Daten, déi de Sender vum Segment erwaart ze kréien.

Fir méi Informatiounen iwwer den TCP Protokoll, kuckt rfc793, vun UDP bis rfc768wou, tatsächlech, si definéiert.

Vun uewen ass et kloer datt fir en zouverlässeg Message Liwwerung Protokoll iwwer UDP ze kreéieren (nodréiglech als Zouverlässeg UDP), ass et erfuerderlech fir Datenübertragungsmechanismen ähnlech wéi TCP ëmzesetzen. Nämlech:

  • späicheren Verbindung Staat
  • benotzen Segment nummeréieren
  • benotzen speziell Confirmatioun Pak
  • Benotzt e vereinfachte Fënstermechanismus fir de Protokollduerchgang ze erhéijen

Zousätzlech braucht Dir:

  • Signal de Start vun engem Message, Ressourcen fir d'Verbindung ze verdeelen
  • d'Enn vun engem Message signaliséieren, de kritt Message un d'Upstream Applikatioun ze passéieren an d'Protokollressourcen ze verëffentlechen
  • erlaabt de Verbindungsspezifesche Protokoll fir d'Liwwerbestätegungsmechanismus auszeschalten fir als "reng" UDP ze funktionéieren

Zuverlässeg UDP Header

Erënnert drun datt en UDP-Datagram an engem IP-Datagramm akapselt ass. Den zouverléissege UDP Paket ass passend "gewéckelt" an en UDP Datagram.
Zuverlässeg UDP Header Encapsulation:Ëmsetzung vun der Reliable Udp Protokoll fir .Net

D'Struktur vum Reliable UDP Header ass ganz einfach:

Ëmsetzung vun der Reliable Udp Protokoll fir .Net

  • Fändelen - Package Kontroll Fändelen
  • MessageType - Message Typ benotzt vun Upstream Uwendungen fir spezifesch Messagen ze abonnéieren
  • TransmissionId - d'Zuel vun der Iwwerdroung, zesumme mat der Adress an den Hafen vum Empfänger, identifizéiert d'Verbindung eenzegaarteg
  • PacketNumber - Paketnummer
  • Optiounen - zousätzlech Protokoll Optiounen. Am Fall vum éischte Paket gëtt et benotzt fir d'Gréisst vum Message unzeginn

Fändelen sinn wéi follegt:

  • FirstPacket - den éischte Paket vum Message
  • NoAsk - de Message erfuerdert keen Unerkennungsmechanismus fir ageschalt ze ginn
  • LastPacket - de leschte Paket vum Message
  • RequestForPacket - Bestätegungspaket oder Ufro fir e verluerene Pak

Allgemeng Prinzipien vum Protokoll

Zënter Reliable UDP konzentréiert sech op garantéiert Messageiwwerdroung tëscht zwee Wirbelen, muss et fäeg sinn eng Verbindung mat der anerer Säit opzebauen. Fir eng Verbindung ze grënnen, schéckt de Sender e Paket mam FirstPacket Fändel, d'Äntwert op déi bedeit datt d'Verbindung etabléiert ass. All Äntwert Pakete, oder, an anere Wierder, Unerkennung Pakete, setzen ëmmer de Wäert vum PacketNumber Feld op ee méi wéi de gréisste PacketNumber Wäert vun erfollegräich kritt Pakete. D'Optiounsfeld fir den éischte Paket geschéckt ass d'Gréisst vum Message.

En ähnleche Mechanismus gëtt benotzt fir eng Verbindung ofzeschléissen. De LastPacket Fändel ass op de leschte Paket vun der Noriicht gesat. Am Äntwertpaket gëtt d'Zuel vum leschte Paket + 1 uginn, wat fir d'Empfangssäit eng erfollegräich Liwwerung vun der Noriicht bedeit.
Connection Etablissement an Termindiagramm:Ëmsetzung vun der Reliable Udp Protokoll fir .Net

Wann d'Verbindung etabléiert ass, fänkt d'Donnéeën Transfert. D'Donnéeë ginn a Blocke vu Pakete iwwerdroen. All Block, ausser de leschten, enthält eng fix Zuel vu Päck. Et ass gläich mat der Empfang / Iwwerdroung Fënstergréisst. De leschte Block vun Daten kann manner Pakete hunn. Nodeems Dir all Block geschéckt huet, waart d'Sendsäit op eng Liwwerungsbestätegung oder eng Ufro fir verluere Päck erëm ze liwweren, wat d'Empfang-/Transmissiounsfenster op léisst fir Äntwerten ze kréien. Nodeems Dir d'Bestätegung vun der Blockliwwerung kritt hutt, verännert d'Empfang / Iwwerdroung Fënster an den nächste Block vun Daten gëtt geschéckt.

Déi Empfangssäit kritt d'Päckchen. All Paket gëtt iwwerpréift fir ze kucken ob et an der Iwwerdroungsfenster fällt. Päckchen an Duplikaten, déi net an d'Fënster falen, ginn ausfiltert. Well Wann d'Gréisst vun der Fënster fixéiert ass an d'selwecht fir den Empfänger an de Sender ass, dann am Fall vun engem Block vu Pakete geliwwert ouni Verloscht, gëtt d'Fënster verréckelt fir Pakete vum nächste Block vun Daten ze kréien an eng Liwwerungsbestätegung ass geschéckt. Wann d'Fënster net an der Period vun der Aarbecht Timer fëllt fëllt, da gëtt e Scheck gestart op déi Päckchen net geliwwert goufen an Ufroe fir nei Liwwerung geschéckt ginn.
Retransmission Diagramm:Ëmsetzung vun der Reliable Udp Protokoll fir .Net

Timeouts a Protokoll Timer

Et gi verschidde Grënn firwat eng Verbindung net etabléiert ka ginn. Zum Beispill, wann déi empfaang Partei offline ass. An dësem Fall, wann Dir probéiert eng Verbindung opzebauen, gëtt d'Verbindung duerch Timeout zougemaach. D'Reliable UDP Implementatioun benotzt zwee Timer fir Timeouts ze setzen. Déi éischt, den Aarbechtstimer, gëtt benotzt fir op eng Äntwert vum Fernhost ze waarden. Wann et op der Sender Säit brennt, da gëtt de leschte geschéckte Paket erëm geschéckt. Wann den Timer beim Empfänger ofleeft, da gëtt e Scheck fir verluerene Päckchen ausgeführt an Ufroe fir nei Liwwerung geschéckt.

Den zweeten Timer ass néideg fir d'Verbindung am Fall vun engem Mangel u Kommunikatioun tëscht den Wirbelen ze schloen. Fir d'Sender Säit fänkt et direkt nodeems den Aarbechtstimer ofleeft, a waart op eng Äntwert vum Fernknot. Wann et keng Äntwert bannent der spezifizéierter Period gëtt, gëtt d'Verbindung ofgeschloss a Ressourcen fräigelooss. Fir d'Empfangssäit gëtt d'Verbindungsschloss Timer gestart nodeems den Aarbechtstimer zweemol ofleeft. Dëst ass néideg fir géint de Verloscht vum Bestätegungspaket ze versécheren. Wann den Timer ofleeft, gëtt d'Verbindung och ofgeschloss a Ressourcen fräigelooss.

Zouverlässeg UDP Transmissioun Staat Diagramm

D'Prinzipien vum Protokoll ginn an enger endlecher Staatsmaschinn ëmgesat, déi all Staat verantwortlech ass fir eng gewësse Logik vun der Paketveraarbechtung.
Zouverlässeg UDP Staatsdiagramm:

Ëmsetzung vun der Reliable Udp Protokoll fir .Net

zou - ass net wierklech e Staat, et ass e Start- an Ennpunkt fir den Automat. Fir Staat zou e Transmissiounskontrollblock gëtt kritt, deen, en asynchronen UDP-Server implementéiert, Päckchen op déi entspriechend Verbindungen weiderginn an d'Staatsveraarbechtung ufänkt.

FirstPacketSending - den initialen Zoustand an deem déi erausgaang Verbindung ass wann de Message geschéckt gëtt.

An dësem Zoustand gëtt den éischte Paket fir normal Messagen geschéckt. Fir Messagen ouni Schéckbestätegung ass dëst deen eenzege Staat wou de ganze Message geschéckt gëtt.

SendingCycle - Grondzoustand fir d'Transmissioun vu Message Päckchen.

Iwwergank zu et vum Staat FirstPacketSending duerchgefouert nodeems den éischte Paket vum Message geschéckt gouf. Et ass an dësem Staat datt all Unerkennung an Ufroe fir Iwwerdroe kommen. Ausgang vun et ass méiglech an zwee Fäll - am Fall vun erfollegräich Liwwerung vun der Noriicht oder duerch Timeout.

FirstPacketReceived - den initialen Zoustand fir den Empfänger vum Message.

Et kontrolléiert d'Korrektheet vum Ufank vun der Iwwerdroung, erstellt déi néideg Strukturen a schéckt eng Unerkennung vun der Empfang vum éischte Paket.

Fir e Message deen aus engem eenzege Paket besteet a geschéckt gouf ouni Beweis vun der Liwwerung ze benotzen, ass dëst deen eenzege Staat. No der Veraarbechtung vun esou engem Message gëtt d'Verbindung zougemaach.

Montage - Basiszoustand fir Message Päck ze kréien.

Et schreift Päckchen op temporär Späichere, kontrolléiert fir Paketverloscht, schéckt Unerkennung fir d'Liwwerung vun engem Block vu Päck an de ganze Message, a schéckt Ufroe fir d'Relieferung vu verluerene Päckchen. Am Fall vun erfollegräich Empfang vun der ganzer Noriicht, geet d'Verbindung an de Staat Fäerdeg, soss geet e Timeout eraus.

Fäerdeg - d'Verbindung zoumaachen am Fall vun enger erfollegräicher Empfang vum ganze Message.

Dëse Staat ass néideg fir d'Versammlung vun der Noriicht a fir de Fall wou d'Liwwerbestätegung vum Message op de Wee zum Sender verluer ass. Dëse Staat gëtt mat engem Timeout ofgeschloss, awer d'Verbindung gëtt als erfollegräich zougemaach.

Méi déif an de Code. Transmissioun Kontroll Eenheet

Ee vun de Schlësselelementer vun Reliable UDP ass den Iwwerdroungskontrollblock. D'Aufgab vun dësem Block ass d'aktuell Verbindungen an Hilfselementer ze späicheren, erakommen Pakete op déi entspriechend Verbindungen ze verdeelen, en Interface ze bidden fir Päckchen op eng Verbindung ze schécken an de Protokoll API ëmzesetzen. D'Transmissiounskontrollblock kritt Pakete vun der UDP Schicht a schéckt se an d'Staatsmaschinn fir d'Veraarbechtung weider. Fir Päck ze kréien, implementéiert en asynchronen UDP Server.
E puer Membere vun der Klass 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;    	
  //...
}

Ëmsetzung vun asynchronen UDP Server:

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

Fir all Messagetransfer gëtt eng Struktur erstallt déi Informatioun iwwer d'Verbindung enthält. Sou eng Struktur gëtt genannt Verbindung Rekord.
E puer Membere vun der ReliableUdpConnectionRecord Klass:

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

Méi déif an de Code. Staaten

Staaten implementéieren d'Staatsmaschinn vum Reliable UDP Protokoll, wou d'Haaptveraarbechtung vu Päck stattfënnt. Déi abstrakt Klass ReliableUdpState bitt en Interface fir de Staat:

Ëmsetzung vun der Reliable Udp Protokoll fir .Net

Déi ganz Logik vum Protokoll gëtt vun de Klassen hei uewe presentéiert, zesumme mat enger Hilfsklass ëmgesat, déi statesch Methoden ubitt, wéi zum Beispill den ReliableUdp Header aus dem Verbindungsrekord ze konstruéieren.

Als nächst wäerte mir am Detail d'Ëmsetzung vun den Interfacemethoden berücksichtegen, déi d'Basis Algorithmen vum Protokoll bestëmmen.

DisposeByTimeout Method

D'DiposeByTimeout Method ass verantwortlech fir d'Verëffentlechung vun Verbindungsressourcen no engem Timeout a fir eng erfollegräich / net erfollegräich Message Liwwerung ze signaliséieren.
ReliableUdpState.DisposeByTimeout:

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

Et ass nëmmen am Staat iwwerschratt Fäerdeg.
Ofgeschloss.DisposeByTimeout:

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

ProcessPackets Method

D'ProcessPackets Method ass verantwortlech fir zousätzlech Veraarbechtung vun engem Package oder Packagen. Direkt genannt oder iwwer e Paketwait Timer.

Kënnen Montage d'Method gëtt iwwerschriwwen an ass verantwortlech fir ze kontrolléieren op verluerene Päckchen an den Iwwergank zum Staat Fäerdeg, am Fall wou Dir de leschte Paket kritt an en erfollegräiche Scheck passéiert
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);
  }
}

Kënnen SendingCycle Dës Method gëtt nëmmen op engem Timer genannt, an ass verantwortlech fir de leschte Message nei ze schécken, souwéi d'Verbindungsschloss Timer z'aktivéieren.
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);
}

Kënnen Fäerdeg d'Method stoppt de Lafen Timer a schéckt de Message un d'Abonnenten.
Ofgeschloss.ProcessPackets:

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

ReceivePacket Method

Kënnen FirstPacketReceived d'Haaptaufgab vun der Method ass ze bestëmmen ob den éischte Message Paket tatsächlech um Interface ukomm ass, an och e Message ze sammelen deen aus engem eenzege Paket besteet.
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);
  }
}

Kënnen SendingCycle dës Method gëtt iwwerschratt fir Liwwerungserkennungen a Retransmissionsufroen ze akzeptéieren.
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));
}

Kënnen Montage an der ReceivePacket Method ass d'Haaptaarbecht fir e Message vun erakommende Päck ze sammelen.
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);
  }
}

Kënnen Fäerdeg déi eenzeg Aufgab vun der Method ass eng nei Unerkennung vun der erfollegräicher Liwwerung vun der Noriicht ze schécken.
Completed.ReceivePacket:

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

Schécken Packet Method

Kënnen FirstPacketSending Dës Method schéckt den éischte Pak vun Daten, oder, wann de Message net Liwwerung Bestätegung verlaangen, de ganze Message.
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);
}

Kënnen SendingCycle an dëser Method gëtt e Block vu Päck geschéckt.
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 );
  }
}

Méi déif an de Code. Schafen an Opbau vun Verbindungen

Elo datt mir d'Basiszoustand an d'Methoden gesinn hunn fir Staaten ze handhaben, loosst eis e puer Beispiller opbriechen wéi de Protokoll méi detailléiert funktionnéiert.
Datenübertragungsdiagramm ënner normale Konditiounen:Ëmsetzung vun der Reliable Udp Protokoll fir .Net

Betruecht am Detail d'Schafung Verbindung Rekord fir den éischte Paket ze verbannen an ze schécken. Den Transfer gëtt ëmmer vun der Applikatioun initiéiert déi d'Send Message API nennt. Als nächst gëtt d'StartTransmission Method vum Iwwerdroungskontrollblock opgeruff, wat d'Transmissioun vun Daten fir den neie Message ufänkt.
Erstellt eng erausgaang Verbindung:

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

Den éischte Paket schécken (FirstPacketSending Staat):

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

Nodeems den éischte Paket geschéckt gouf, geet de Sender an de Staat SendingCycle - wait fir Bestätegung vun Pak Liwwerung.
D'Empfangssäit, mat der EndReceive Method, kritt de geschéckte Paket, erstellt en neit Verbindung Rekord a passt dëse Paket, mat engem preparséierten Header, un d'ReceivePacket Method vum Staat fir d'Veraarbechtung FirstPacketReceived
Eng Verbindung op der Empfangssäit erstellen:

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

Den éischte Paket kréien an eng Unerkennung schécken (FirstPacketReceived Staat):

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

Méi déif an de Code. D'Verbindung op Timeout zoumaachen

Timeout Handhabung ass e wichtege Bestanddeel vun Reliable UDP. Betruecht e Beispill an deem en Zwëschennode gescheitert ass an d'Date Liwwerung a béid Richtungen onméiglech gouf.
Diagramm fir d'Verbindung duerch Timeout ofzeschléissen:Ëmsetzung vun der Reliable Udp Protokoll fir .Net

Wéi aus dem Diagramm gesi ka ginn, fänkt den Aarbechtstimer vum Sender direkt un nodeems e Block vu Päck geschéckt gouf. Dëst geschitt an der SendPacket Method vum Staat SendingCycle.
Den Aarbechtstimer aktivéieren (SendingCycle Zoustand):

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

D'Timer Perioden si festgeluecht wann d'Verbindung erstallt gëtt. De Standard ShortTimerPeriod ass 5 Sekonnen. Am Beispill ass et op 1,5 Sekonnen gesat.

Fir eng erakommen Verbindung fänkt den Timer un nodeems de leschten erakommen Datepaket kritt huet, dëst geschitt an der ReceivePacket Method vum Staat Montage
Aktivéiert den Aarbechtstimer (Assembléestatus):

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

Keng Päck méi ukomm op der erakommen Verbindung wärend Dir op den Aarbechtstimer waart. Den Timer ass ausgaang an huet d'ProcessPackets-Methode genannt, wou déi verluerene Päck fonnt goufen an d'Relieferungsfuerderunge fir d'éischte Kéier geschéckt goufen.
Schécken vun Neiliwwerungsufroen (Assemblée Staat):

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

D'Variabel TimerSecondTry ass op richteg. Dës Variabel ass verantwortlech fir den Aarbechtstimer nei ze starten.

Op der Säit vum Sender gëtt den Aarbechtstimer och ausgeléist an de leschte geschéckte Paket gëtt nei geschéckt.
Aktivéiert Verbindung Zoumaache Timer (SendingCycle Zoustand):

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

Duerno fänkt d'Verbindungsschloss Timer an der erausgaang Verbindung un.
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);
}

D'Verbindung zoumaachen Timer Timeout Period ass Standard 30 Sekonnen.

No enger kuerzer Zäit brennt den Aarbechtstimer op der Säit vum Empfänger erëm, Ufroe ginn erëm geschéckt, duerno fänkt de Verbindungsschlusstimer fir déi erakommen Verbindung un.

Wann d'Zoumaache Timer brennen, ginn all Ressourcen vu béide Verbindungsrecords verëffentlecht. De Sender mellt d'Liwwerungsfehler un d'Upstream Applikatioun (gesinn zouverlässeg UDP API).
Verëffentlechung vun Verbindungsrekordressourcen:

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

Méi déif an de Code. Restauratioun vun Daten Transfert

Datetransmission Erhuelung Diagramm am Fall vu Paketverloscht:Ëmsetzung vun der Reliable Udp Protokoll fir .Net

Wéi schonn diskutéiert beim Ofschloss vun der Verbindung op Timeout, wann den Aarbechtstimer ofleeft, wäert den Empfänger op verluerene Päck kucken. Am Fall vu Paketverloscht gëtt eng Lëscht vun der Unzuel vun de Paketen, déi den Empfänger net erreecht hunn, zesummegesat. Dës Zuelen ginn an de LostPackets-Array vun enger spezifescher Verbindung aginn, an Ufroe fir d'Relieferung ginn geschéckt.
Schécken Ufroe fir Pakete nei ze liwweren (Assemblée Staat):

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

De Sender akzeptéiert d'Wiederliwwerungsufro an schéckt déi fehlend Päck. Et ass derwäert ze notéieren datt de Sender an dësem Moment d'Verbindungsschloss Timer schonn ugefaang huet a wann eng Ufro kritt gëtt, gëtt se zréckgesat.
Neiverschéckt verluere Pakete (SendingCycle Staat):

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

De resent Paket (Pak #3 am Diagramm) gëtt vun der erakommen Verbindung kritt. E Scheck gëtt gemaach fir ze kucken ob d'Empfangfenster voll ass an déi normal Dateniwwerdroung restauréiert ass.
Iwwerpréift fir Hits an der Empfangsfenster (Assemblée Staat):

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

Zouverlässeg UDP API

Fir mat dem Datenübertragungsprotokoll ze interagéieren, gëtt et eng oppe Reliable Udp Klass, déi e Wrapper iwwer den Transfer Kontrollblock ass. Hei sinn déi wichtegst Membere vun der Klass:

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

Messagen ginn duerch Abonnement kritt. Delegéiert Ënnerschrëft fir d'Callback Method:

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

Сообщение:

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

Fir e spezifesche Messagetyp an/oder e spezifesche Sender ze abonnéieren, ginn zwee fakultativ Parameter benotzt: ReliableUdpMessageTypes messageType an IPEndPoint ipEndPoint.

Message Zorte:

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

De Message gëtt asynchron geschéckt; dofir implementéiert de Protokoll en asynchrone Programméierungsmodell:

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

D'Resultat vum Schécken vun engem Message wäert richteg sinn - wann de Message erfollegräich den Empfänger erreecht ass a falsch - wann d'Verbindung duerch Timeout zougemaach gouf:

public bool EndSendMessage(IAsyncResult asyncResult)

Konklusioun

Vill ass net an dësem Artikel beschriwwe ginn. Thread passende Mechanismen, Ausnam a Fehlerhandhabung, Ëmsetzung vun asynchrone Message Sendemethoden. Awer de Kär vum Protokoll, d'Beschreiwung vun der Logik fir d'Veraarbechtung vu Paketen, d'Verbindung opzebauen an d'Timeouts ze handhaben, sollt Iech kloer sinn.

Déi demonstréiert Versioun vum zouverléissege Liwwerprotokoll ass robust a flexibel genuch fir déi virdru definéiert Ufuerderungen z'erreechen. Awer ech wëll derbäisetzen datt déi beschriwwe Ëmsetzung ka verbessert ginn. Zum Beispill, fir den Duerchgang ze erhéijen an d'Timerperioden dynamesch z'änneren, kënnen Mechanismen wéi Schieberfenster an RTT zum Protokoll bäigefüügt ginn, et wäert och nëtzlech sinn fir e Mechanismus ze realiséieren fir MTU tëscht Verbindungsknoten ze bestëmmen (awer nëmmen wann grouss Messagen geschéckt ginn) .

Merci fir Är Opmierksamkeet, Ech freeën eis op Är Kommentaren a Kommentaren.

PS Fir déi, déi un d'Detailer interesséiert sinn oder just de Protokoll testen wëllen, de Link zum Projet op GitHube:
Zouverlässeg UDP Projet

Nëtzlech Linken an Artikelen

  1. TCP Protokoll Spezifizéierung: op Englesch и op Russesch
  2. UDP Protokoll Spezifizéierung: op Englesch и op Russesch
  3. Diskussioun vum RUDP Protokoll: draft-ietf-sigtran-reliable-udp-00
  4. Zuverlässeg Dateprotokoll: rfc908 и rfc1151
  5. Eng einfach Ëmsetzung vun der Liwwerung Bestätegung iwwer UDP: Huelt Total Kontroll vun Ärem Vernetzung Mat .NET An UDP
  6. Artikel beschreift NAT Traversal Mechanismen: Peer-to-Peer Kommunikatioun iwwer Netzwierk Adress Iwwersetzer
  7. Ëmsetzung vum asynchrone Programméierungsmodell: Ëmsetzung vum CLR Asynchrone Programméierungsmodell и Wéi implementéiert d'IAsyncResult Designmuster
  8. Portéieren den asynchrone Programméierungsmodell op dat Aufgab-baséiert asynchront Muster (APM an TAP):
    TPL an Traditionell .NET Asynchronous Programméiere
    Interop mat aneren asynchrone Musteren an Typen

Update: Merci Buergermeeschter и sidristij fir d'Iddi eng Aufgab op den Interface ze addéieren. D'Kompatibilitéit vun der Bibliothéik mat alen Betribssystemer gëtt net verletzt, well De 4th Kader ënnerstëtzt béid XP an 2003 Server.

Source: will.com

Setzt e Commentaire