.Net üçün Etibarlı Udp protokolunun həyata keçirilməsi

İnternet çoxdan dəyişib. İnternetin əsas protokollarından biri - UDP proqramlar tərəfindən yalnız dataqramların və yayımların çatdırılması üçün deyil, həm də şəbəkə qovşaqları arasında "peer-to-peer" əlaqəni təmin etmək üçün istifadə olunur. Sadə dizaynı sayəsində bu protokolun əvvəllər planlaşdırılmamış bir çox istifadəsi var, lakin protokolun zəmanətli çatdırılma olmaması kimi çatışmazlıqları heç bir yerdə aradan qalxmayıb. Bu məqalə UDP üzərində zəmanətli çatdırılma protokolunun həyata keçirilməsini təsvir edir.
İçindekiler:Giriş
Protokol Tələbləri
Etibarlı UDP başlığı
Protokolun ümumi prinsipləri
Taymoutlar və protokol taymerləri
Etibarlı UDP ötürmə vəziyyəti diaqramı
Kodu daha dərindən. ötürücü idarəetmə bloku
Kodu daha dərindən. dövlətlər

Kodu daha dərindən. Əlaqələrin yaradılması və qurulması
Kodu daha dərindən. Vaxt aşımında əlaqənin bağlanması
Kodu daha dərindən. Məlumat ötürülməsi bərpa olunur
Etibarlı UDP API
Nəticə
Faydalı bağlantılar və məqalələr

Giriş

İnternetin orijinal arxitekturasında hər bir qovşağın qlobal və unikal IP ünvanına malik olduğu və digər qovşaqlarla birbaşa əlaqə saxlaya bildiyi homojen ünvan məkanı nəzərdə tutulurdu. İndi İnternet, əslində, fərqli bir arxitekturaya malikdir - qlobal IP ünvanlarının bir sahəsi və NAT cihazlarının arxasında gizli ünvanları olan bir çox sahə.Bu arxitekturada yalnız qlobal ünvan məkanındakı qurğular şəbəkədəki hər kəslə asanlıqla əlaqə saxlaya bilər, çünki onların unikal, qlobal yönləndirilə bilən IP ünvanları var. Şəxsi şəbəkədəki qovşaq eyni şəbəkədəki digər qovşaqlara qoşula bilər və qlobal ünvan məkanında digər tanınmış qovşaqlara da qoşula bilər. Bu qarşılıqlı əlaqə əsasən şəbəkə ünvanının tərcümə mexanizmi sayəsində əldə edilir. Wi-Fi marşrutlaşdırıcıları kimi NAT cihazları gedən əlaqələr üçün xüsusi tərcümə cədvəli qeydləri yaradır və paketlərdə IP ünvanlarını və port nömrələrini dəyişdirir. Bu, özəl şəbəkədən qlobal ünvan məkanındakı hostlara gedən bağlantılara imkan verir. Ancaq eyni zamanda, daxil olan əlaqələr üçün ayrıca qaydalar müəyyən edilmədikdə, NAT cihazları adətən bütün gələn trafiki bloklayır.

İnternetin bu arxitekturası müştəri-server əlaqəsi üçün kifayət qədər düzgündür, burada müştərilər şəxsi şəbəkələrdə ola bilər və serverlər qlobal ünvana malikdirlər. Amma bu, iki qovşaq arasında birbaşa əlaqə üçün çətinliklər yaradır müxtəlif özəl şəbəkələr. İki qovşaq arasında birbaşa əlaqə səs ötürülməsi (Skype), kompüterə uzaqdan giriş əldə etmək (TeamViewer) və ya onlayn oyun kimi peer-to-peer proqramları üçün vacibdir.

Fərqli özəl şəbəkələrdəki cihazlar arasında peer-to-peer əlaqə yaratmaq üçün ən təsirli üsullardan biri dəliklərin açılması adlanır. Bu texnika ən çox UDP protokoluna əsaslanan proqramlarda istifadə olunur.

Ancaq tətbiqiniz məlumatların zəmanətli çatdırılmasını tələb edirsə, məsələn, kompüterlər arasında faylları köçürürsünüzsə, UDP-dən istifadə etmək TCP-dən fərqli olaraq UDP-nin zəmanətli çatdırılma protokolu olmadığı və paketlərin sifarişlə çatdırılmasını təmin etmədiyi üçün bir çox çətinlik çəkəcək. protokol.

Bu halda, zəmanətli paket çatdırılmasını təmin etmək üçün lazımi funksionallığı təmin edən və UDP üzərində işləyən tətbiq səviyyəsi protokolunun tətbiqi tələb olunur.

Dərhal qeyd etmək istəyirəm ki, müxtəlif özəl şəbəkələrdə qovşaqlar arasında TCP əlaqələrinin qurulması üçün bir TCP deşik vurma texnikası var, lakin bir çox NAT cihazları tərəfindən dəstəklənmədiyinə görə, ümumiyyətlə qoşulmağın əsas yolu hesab edilmir. belə düyünlər.

Bu məqalənin qalan hissəsində mən yalnız zəmanətli çatdırılma protokolunun həyata keçirilməsinə diqqət yetirəcəyəm. UDP deşik açma texnikasının tətbiqi aşağıdakı məqalələrdə təsvir olunacaq.

Protokol Tələbləri

  1. Etibarlı paket çatdırılması müsbət rəy mexanizmi vasitəsilə həyata keçirilir (sözdə müsbət etiraf)
  2. Böyük məlumatların səmərəli ötürülməsinə ehtiyac, yəni. protokol lazımsız paket relelərindən qaçmalıdır
  3. Çatdırılmanın təsdiqi mexanizmini ləğv etmək mümkün olmalıdır ("təmiz" UDP protokolu kimi fəaliyyət göstərmək imkanı)
  4. Hər mesajın təsdiqi ilə əmr rejimini həyata keçirmək imkanı
  5. Protokol üzərindən məlumat ötürülməsinin əsas vahidi mesaj olmalıdır

Bu tələblər əsasən Etibarlı Məlumat Protokolunun tələbləri ilə üst-üstə düşür RF 908 и RF 1151, və mən bu protokolu hazırlayarkən həmin standartlara etibar etdim.

Bu tələbləri başa düşmək üçün TCP və UDP protokollarından istifadə edərək iki şəbəkə qovşağı arasında məlumatların ötürülmə vaxtına baxaq. Qoy hər iki halda bir paket itiriləcək.
İnteraktiv olmayan məlumatların TCP üzərindən ötürülməsi:.Net üçün Etibarlı Udp protokolunun həyata keçirilməsi

Diaqramdan göründüyü kimi, paket itkisi halında, TCP itirilmiş paketi aşkarlayacaq və itirilmiş seqmentin nömrəsini soruşaraq göndərənə məlumat verəcəkdir.
UDP protokolu ilə məlumat ötürülməsi:.Net üçün Etibarlı Udp protokolunun həyata keçirilməsi

UDP heç bir itki aşkarlama addımı atmır. UDP protokolunda ötürmə xətalarına nəzarət tamamilə tətbiqin üzərinə düşür.

TCP protokolunda səhvlərin aşkarlanması son node ilə əlaqə yaratmaq, bu əlaqənin vəziyyətini saxlamaq, hər bir paket başlığında göndərilən baytların sayını göstərmək və təsdiq nömrəsindən istifadə edərək qəbzləri bildirməklə əldə edilir.

Bundan əlavə, performansı yaxşılaşdırmaq üçün (yəni, bir təsdiq almadan birdən çox seqment göndərmək) TCP protokolu sözdə ötürmə pəncərəsindən istifadə edir - seqmentin göndəricisinin almağı gözlədiyi məlumat baytlarının sayı.

TCP protokolu haqqında ətraflı məlumat üçün baxın RF 793, UDP-dən RF 768harada, əslində, onlar müəyyən edilir.

Yuxarıdakılardan aydın olur ki, UDP üzərindən etibarlı mesaj çatdırılması protokolu yaratmaq üçün (bundan sonra Etibarlı UDP), TCP-yə bənzər məlumat ötürmə mexanizmlərinin həyata keçirilməsi tələb olunur. Məhz:

  • əlaqə vəziyyətini yadda saxla
  • seqment nömrələməsindən istifadə edin
  • xüsusi təsdiq paketlərindən istifadə edin
  • protokol ötürmə qabiliyyətini artırmaq üçün sadələşdirilmiş pəncərə mexanizmindən istifadə edin

Bundan əlavə, sizə lazımdır:

  • əlaqə üçün resursları ayırmaq üçün mesajın başlanğıcını bildirin
  • mesajın sonunu bildirmək, qəbul edilmiş mesajı yuxarı proqrama ötürmək və protokol resurslarını buraxmaq
  • əlaqə üçün xüsusi protokolun "təmiz" UDP kimi işləməsi üçün çatdırılma təsdiqləmə mexanizmini söndürməyə icazə verin

Etibarlı UDP başlığı

Xatırladaq ki, UDP dataqramı IP dataqramına daxil edilmişdir. Etibarlı UDP paketi UDP dataqramına uyğun şəkildə "bükülür".
Etibarlı UDP başlıq inkapsulyasiyası:.Net üçün Etibarlı Udp protokolunun həyata keçirilməsi

Etibarlı UDP başlığının quruluşu olduqca sadədir:

.Net üçün Etibarlı Udp protokolunun həyata keçirilməsi

  • Bayraqlar - paket nəzarət bayraqları
  • MessageType - xüsusi mesajlara abunə olmaq üçün yuxarı proqramlar tərəfindən istifadə edilən mesaj növü
  • TransmissionId - ötürülmə nömrəsi, alıcının ünvanı və portu ilə birlikdə əlaqəni unikal şəkildə müəyyənləşdirir.
  • PacketNumber - paket nömrəsi
  • Seçimlər - əlavə protokol seçimləri. Birinci paketə gəldikdə, mesajın ölçüsünü göstərmək üçün istifadə olunur

Bayraqlar aşağıdakılardır:

  • FirstPacket - mesajın ilk paketi
  • NoAsk - mesajın aktiv olması üçün təsdiqləmə mexanizmi tələb olunmur
  • LastPacket - mesajın son paketi
  • RequestForPacket - təsdiq paketi və ya itirilmiş paket üçün sorğu

Protokolun ümumi prinsipləri

Etibarlı UDP iki qovşaq arasında zəmanətli mesaj ötürülməsinə yönəldildiyi üçün qarşı tərəflə əlaqə qura bilməlidir. Əlaqə yaratmaq üçün göndərici FirstPacket bayrağı ilə bir paket göndərir, ona cavab əlaqənin qurulduğunu bildirir. Bütün cavab paketləri və ya başqa sözlə, təsdiq paketləri həmişə PacketNumber sahəsinin dəyərini uğurla qəbul edilmiş paketlərin ən böyük PacketNumber dəyərindən bir daha çox təyin edir. Göndərilən ilk paket üçün Seçimlər sahəsi mesajın ölçüsüdür.

Bənzər bir mexanizm əlaqəni dayandırmaq üçün istifadə olunur. LastPacket bayrağı mesajın sonuncu paketində qoyulur. Cavab paketində sonuncu paketin nömrəsi + 1 göstərilir ki, bu da qəbul edən tərəf üçün mesajın uğurlu çatdırılması deməkdir.
Bağlantının qurulması və dayandırılması diaqramı:.Net üçün Etibarlı Udp protokolunun həyata keçirilməsi

Bağlantı qurulduqda məlumat ötürülməsi başlayır. Məlumat paket bloklarında ötürülür. Hər bir blok, sonuncu istisna olmaqla, müəyyən sayda paketdən ibarətdir. Qəbul/ötürmə pəncərəsinin ölçüsünə bərabərdir. Sonuncu məlumat blokunda daha az paket ola bilər. Hər blok göndərildikdən sonra göndərən tərəf cavabların qəbulu üçün qəbul/ötürmə pəncərəsini açıq qoyaraq çatdırılma təsdiqini və ya itirilmiş paketlərin yenidən çatdırılması tələbini gözləyir. Blokun çatdırılması təsdiqini aldıqdan sonra qəbul/ötürmə pəncərəsi dəyişir və növbəti məlumat bloku göndərilir.

Qəbul edən tərəf paketləri qəbul edir. Hər bir paket ötürmə pəncərəsinə daxil olub-olmadığını yoxlamaq üçün yoxlanılır. Pəncərəyə düşməyən paketlər və dublikatlar süzülür. Çünki Pəncərənin ölçüsü sabitdirsə və alıcı və göndərici üçün eynidirsə, paketlər blokunun itkisiz çatdırılması halında, pəncərə növbəti məlumat blokunun paketlərini qəbul etmək üçün dəyişdirilir və çatdırılma təsdiqi verilir. göndərildi. Pəncərə iş taymerinin təyin etdiyi müddətdə doldurulmazsa, o zaman hansı paketlərin çatdırılmadığını yoxlamağa başlanacaq və yenidən çatdırılma sorğuları göndəriləcək.
Yenidən ötürülmə diaqramı:.Net üçün Etibarlı Udp protokolunun həyata keçirilməsi

Taymoutlar və protokol taymerləri

Bağlantının qurulmasının bir neçə səbəbi var. Məsələn, qəbul edən tərəf oflayndırsa. Bu halda, əlaqə qurmağa çalışarkən, əlaqə fasilə ilə bağlanacaq. Etibarlı UDP tətbiqi fasilələri təyin etmək üçün iki taymerdən istifadə edir. Birincisi, işləyən taymer, uzaq hostdan cavab gözləmək üçün istifadə olunur. Göndərən tərəfdə işə salınarsa, sonuncu göndərilən paket yenidən göndərilir. Taymer alıcıda başa çatarsa, itirilmiş paketlərin yoxlanılması aparılır və yenidən çatdırılma sorğuları göndərilir.

İkinci taymer qovşaqlar arasında əlaqə olmaması halında əlaqəni bağlamaq üçün lazımdır. Göndərən tərəf üçün, iş taymerinin müddəti bitdikdən dərhal sonra başlayır və uzaq qovşaqdan cavab gözləyir. Göstərilən müddətdə heç bir cavab alınmasa, əlaqə dayandırılır və resurslar buraxılır. Qəbul edən tərəf üçün əlaqənin bağlanması taymeri iş taymerinin müddəti iki dəfə bitdikdən sonra işə salınır. Bu, təsdiq paketinin itirilməsindən sığortalanmaq üçün lazımdır. Taymeri başa çatdıqda, əlaqə də dayandırılır və resurslar buraxılır.

Etibarlı UDP ötürmə vəziyyəti diaqramı

Protokolun prinsipləri hər bir vəziyyəti paket emalının müəyyən məntiqinə cavabdeh olan sonlu dövlət maşınında həyata keçirilir.
Etibarlı UDP Dövlət Diaqramı:

.Net üçün Etibarlı Udp protokolunun həyata keçirilməsi

Qapalı - əslində vəziyyət deyil, avtomat üçün başlanğıc və son nöqtədir. Dövlət üçün Qapalı asinxron UDP serverini həyata keçirərək paketləri müvafiq əlaqələrə yönləndirən və vəziyyətin işlənməsini başlatan ötürücü idarəetmə bloku alınır.

FirstPacketSending – mesaj göndərilərkən gedən əlaqənin ilkin vəziyyəti.

Bu vəziyyətdə normal mesajlar üçün ilk paket göndərilir. Göndərmə təsdiqi olmayan mesajlar üçün bu, bütün mesajın göndərildiyi yeganə dövlətdir.

SendingCycle – mesaj paketlərinin ötürülməsi üçün əsas vəziyyət.

Dövlətdən ona keçid FirstPacketSending mesajın ilk paketi göndərildikdən sonra həyata keçirilir. Məhz bu vəziyyətdə təkrar ötürmə üçün bütün təsdiqlər və sorğular gəlir. Ondan çıxmaq iki halda mümkündür - mesajın uğurlu çatdırılması və ya fasilə ilə.

İlk Paket Qəbul edildi – mesajın alıcısı üçün ilkin vəziyyət.

O, ötürülmənin başlanmasının düzgünlüyünü yoxlayır, lazımi strukturları yaradır və birinci paketin alınması barədə bildiriş göndərir.

Tək bir paketdən ibarət olan və çatdırılma sübutundan istifadə edilmədən göndərilən mesaj üçün bu, yeganə vəziyyətdir. Belə bir mesajı emal etdikdən sonra əlaqə bağlanır.

Quraşdırma – mesaj paketlərinin qəbulu üçün əsas vəziyyət.

O, paketləri müvəqqəti yaddaşa yazır, paket itkisini yoxlayır, paketlər blokunun və bütün mesajın çatdırılması üçün bildirişlər göndərir və itirilmiş paketlərin yenidən çatdırılması üçün sorğular göndərir. Bütün mesajın müvəffəqiyyətlə alınması halında, əlaqə dövlətə keçir Tamamlanmış, əks halda vaxt aşımı çıxır.

Tamamlanmış – bütün mesajın uğurlu alınması halında əlaqənin bağlanması.

Bu vəziyyət mesajın yığılması və göndəriciyə gedən yolda mesajın çatdırılma təsdiqinin itirildiyi hal üçün lazımdır. Bu vəziyyətdən fasilə ilə çıxılır, lakin əlaqə uğurla bağlanmış hesab olunur.

Kodu daha dərindən. ötürücü idarəetmə bloku

Reliable UDP-nin əsas elementlərindən biri ötürmə idarəetmə blokudur. Bu blokun vəzifəsi cari əlaqələri və köməkçi elementləri saxlamaq, daxil olan paketləri müvafiq bağlantılara paylamaq, paketlərin əlaqəyə göndərilməsi üçün interfeysi təmin etmək və protokol API-ni həyata keçirməkdir. Transmissiya idarəetmə bloku paketləri UDP qatından qəbul edir və emal üçün dövlət maşınına ötürür. Paketləri qəbul etmək üçün o, asinxron UDP serverini tətbiq edir.
ReliableUdpConnectionControlBlock sinifinin bəzi üzvləri:

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

Asinxron UDP serverinin tətbiqi:

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

Hər bir mesaj ötürülməsi üçün əlaqə haqqında məlumatları ehtiva edən bir struktur yaradılır. Belə bir quruluş adlanır əlaqə qeydi.
ReliableUdpConnectionRecord sinifinin bəzi üzvləri:

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

Kodu daha dərindən. dövlətlər

Dövlətlər paketlərin əsas işlənməsinin baş verdiyi Etibarlı UDP protokolunun dövlət maşını həyata keçirir. ReliableUdpState mücərrəd sinfi vəziyyət üçün interfeys təqdim edir:

.Net üçün Etibarlı Udp protokolunun həyata keçirilməsi

Protokolun bütün məntiqi yuxarıda təqdim olunan siniflər tərəfindən, məsələn, əlaqə qeydindən ReliableUdp başlığının qurulması kimi statik metodları təmin edən köməkçi siniflə birlikdə həyata keçirilir.

Sonra, protokolun əsas alqoritmlərini təyin edən interfeys üsullarının həyata keçirilməsini ətraflı nəzərdən keçirəcəyik.

DisposeByTimeout metodu

DisposeByTimeout metodu fasilədən sonra əlaqə resurslarının buraxılması və uğurlu/uğursuz mesajın çatdırılması üçün cavabdehdir.
ReliableUdpState.DisposeByTimeout:

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

Bu, yalnız ştatda ləğv edilir Tamamlanmış.
Tamamlandı. DisposeByTimeout:

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

ProcessPackets metodu

ProcessPackets metodu paket və ya paketlərin əlavə emalı üçün cavabdehdir. Birbaşa və ya paket gözləmə taymeri vasitəsilə zəng edilir.

Bacarır Quraşdırma üsul ləğv edilir və itirilmiş paketlərin yoxlanılması və vəziyyətə keçid üçün cavabdehdir Tamamlanmış, sonuncu paketi aldıqda və uğurlu yoxlamadan keçdikdə
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);
  }
}

Bacarır SendingCycle bu üsul yalnız taymerdə çağırılır və son mesajın yenidən göndərilməsi, həmçinin əlaqənin bağlanması taymerinin aktivləşdirilməsi üçün məsuliyyət daşıyır.
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);
}

Bacarır Tamamlanmış üsul işləyən taymeri dayandırır və mesajı abunəçilərə göndərir.
Tamamlandı.ProsesPaketləri:

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

ReceivePacket metodu

Bacarır İlk Paket Qəbul edildi metodun əsas vəzifəsi ilk mesaj paketinin həqiqətən interfeysə gəlib çatmadığını müəyyən etmək və həmçinin tək paketdən ibarət mesaj toplamaqdır.
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);
  }
}

Bacarır SendingCycle bu üsul çatdırılma təsdiqlərini və təkrar ötürmə sorğularını qəbul etmək üçün ləğv edilir.
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));
}

Bacarır Quraşdırma ReceivePacket metodunda daxil olan paketlərdən mesajın yığılmasının əsas işi baş verir.
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);
  }
}

Bacarır Tamamlanmış metodun yeganə vəzifəsi mesajın müvəffəqiyyətlə çatdırılması barədə yenidən təsdiq göndərməkdir.
Tamamlandı.Paket alın:

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

Paket Göndərmə Metodu

Bacarır FirstPacketSending bu üsul məlumatın ilk paketini və ya mesaj çatdırılma təsdiqini tələb etmirsə, bütün mesajı göndərir.
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);
}

Bacarır SendingCycle bu üsulda paketlər bloku göndərilir.
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 );
  }
}

Kodu daha dərindən. Əlaqələrin yaradılması və qurulması

İndi biz əsas vəziyyətləri və vəziyyətləri idarə etmək üçün istifadə olunan üsulları gördük, gəlin protokolun necə işlədiyinə dair bir neçə nümunəni bir az daha ətraflı nəzərdən keçirək.
Normal şəraitdə məlumat ötürmə diaqramı:.Net üçün Etibarlı Udp protokolunun həyata keçirilməsi

Yaradıcılığı ətraflı nəzərdən keçirin əlaqə qeydi qoşulmaq və ilk paketi göndərmək üçün. Köçürmə həmişə göndərmə mesajı API-ni çağıran proqram tərəfindən başlanır. Sonra, yeni mesaj üçün məlumatların ötürülməsinə başlayan ötürmə idarəetmə blokunun StartTransmission metodu işə salınır.
Gedən əlaqə yaratmaq:

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

İlk paketin göndərilməsi (FirstPacketSending vəziyyəti):

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

İlk paketi göndərdikdən sonra göndərən dövlətə daxil olur SendingCycle – paketin çatdırılmasının təsdiqini gözləyin.
Qəbul edən tərəf EndReceive metodundan istifadə edərək göndərilən paketi qəbul edir, yenisini yaradır əlaqə qeydi və bu paketi əvvəlcədən təhlil edilmiş başlıqla emal üçün dövlətin ReceivePacket metoduna keçir İlk Paket Qəbul edildi
Qəbul edən tərəfdə əlaqə yaratmaq:

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

İlk paketin qəbulu və təsdiqin göndərilməsi (FirstPacketReceived status):

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

Kodu daha dərindən. Vaxt aşımında əlaqənin bağlanması

Zaman aşımı ilə işləmə Etibarlı UDP-nin vacib hissəsidir. Aralıq qovşağın uğursuz olduğu və hər iki istiqamətdə məlumatların çatdırılmasının qeyri-mümkün olduğu bir nümunəyə nəzər salın.
Bağlantının fasilə ilə bağlanması diaqramı:.Net üçün Etibarlı Udp protokolunun həyata keçirilməsi

Diaqramdan göründüyü kimi, göndəricinin iş taymeri paketlər blokunu göndərdikdən dərhal sonra işə başlayır. Bu, dövlətin SendPacket metodunda baş verir SendingCycle.
İş taymerinin aktivləşdirilməsi (SendingCycle vəziyyəti):

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

Taymer dövrləri əlaqə yaradıldıqda təyin edilir. Varsayılan ShortTimerPeriod 5 saniyədir. Nümunədə 1,5 saniyəyə təyin edilmişdir.

Daxil olan əlaqə üçün taymer sonuncu daxil olan məlumat paketini aldıqdan sonra işə başlayır, bu vəziyyətin ReceivePacket metodunda baş verir. Quraşdırma
İş taymerinin işə salınması (quraşdırma vəziyyəti):

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

İş taymerini gözləyərkən daxil olan əlaqəyə daha çox paket gəlmədi. Taymer söndü və itirilmiş paketlərin tapıldığı və ilk dəfə çatdırılma sorğularının göndərildiyi ProcessPackets metodunu çağırdı.
Yenidən çatdırılma sorğularının göndərilməsi (yığılma vəziyyəti):

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

TimerSecondTry dəyişəni təyin edilib doğru. Bu dəyişən iş taymerini yenidən işə salmaqdan məsuldur.

Göndərən tərəfdə iş taymeri də işə salınır və sonuncu göndərilən paket yenidən göndərilir.
Bağlantının bağlanması taymerinin aktivləşdirilməsi (SendingCycle vəziyyəti):

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

Bundan sonra, gedən əlaqədə əlaqənin bağlanması taymeri başlayır.
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);
}

Bağlantının bağlanması taymerinin fasilə müddəti standart olaraq 30 saniyədir.

Qısa müddətdən sonra alıcının tərəfindəki iş taymeri yenidən işə düşür, sorğular yenidən göndərilir, bundan sonra daxil olan əlaqə üçün əlaqəni bağlama taymeri başlayır.

Yaxın taymerlər işə salındıqda, hər iki əlaqə qeydinin bütün resursları buraxılır. Göndərən yuxarı proqrama çatdırılma uğursuzluğunu bildirir (bax Etibarlı UDP API).
Bağlantı qeydi resurslarının buraxılması:

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

Kodu daha dərindən. Məlumat ötürülməsi bərpa olunur

Paket itkisi halında məlumat ötürülməsinin bərpası diaqramı:.Net üçün Etibarlı Udp protokolunun həyata keçirilməsi

Bağlantının bağlanması zamanı artıq müzakirə edildiyi kimi, iş taymerinin vaxtı bitdikdə, qəbuledici itirilmiş paketləri yoxlayacaq. Paket itirildiyi halda, alıcıya çatmayan paketlərin sayının siyahısı tərtib ediləcək. Bu nömrələr xüsusi əlaqənin LostPackets massivinə daxil edilir və yenidən çatdırılma sorğuları göndərilir.
Paketləri yenidən çatdırmaq üçün sorğuların göndərilməsi (yığılma vəziyyəti):

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

Göndərən yenidən çatdırılma sorğusunu qəbul edəcək və çatışmayan paketləri göndərəcək. Qeyd etmək lazımdır ki, bu anda göndərən əlaqəni bağlama taymerini artıq işə salıb və sorğu qəbul edildikdə, o, sıfırlanır.
İtirilmiş paketlərin yenidən göndərilməsi (SendingCycle vəziyyəti):

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

Yenidən göndərilən paket (diaqramda №3 paket) gələn əlaqə tərəfindən qəbul edilir. Qəbul pəncərəsinin dolu olub olmadığını və normal məlumat ötürülməsinin bərpa olunduğunu yoxlamaq üçün yoxlama aparılır.
Qəbul pəncərəsində hitlərin yoxlanılması (yığılma vəziyyəti):

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

Etibarlı UDP API

Məlumat ötürmə protokolu ilə qarşılıqlı əlaqə yaratmaq üçün ötürmə idarəetmə blokunun üzərində sarğı olan açıq Etibarlı Udp sinfi mövcuddur. Budur sinfin ən vacib üzvləri:

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

Mesajlar abunə ilə qəbul edilir. Geri çağırış metodu üçün nümayəndə imzası:

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

Mesaj:

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

Müəyyən bir mesaj növünə və/və ya xüsusi göndərənə abunə olmaq üçün iki əlavə parametr istifadə olunur: ReliableUdpMessageTypes messageType və IPEndPoint ipEndPoint.

Mesaj növləri:

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

Mesaj asinxron şəkildə göndərilir; bunun üçün protokol asinxron proqramlaşdırma modelini həyata keçirir:

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

Mesajın göndərilməsinin nəticəsi doğru olacaq - əgər mesaj alıcıya uğurla çatıbsa və yanlışdırsa - əlaqə fasilə ilə bağlanıbsa:

public bool EndSendMessage(IAsyncResult asyncResult)

Nəticə

Bu məqalədə çox şey təsvir edilməmişdir. Mövzunun uyğunlaşdırılması mexanizmləri, istisna və səhvlərin idarə edilməsi, asinxron mesaj göndərmə üsullarının həyata keçirilməsi. Lakin protokolun əsas mahiyyəti, paketlərin işlənməsi, əlaqənin qurulması və fasilələrin idarə edilməsi üçün məntiqin təsviri sizə aydın olmalıdır.

Etibarlı çatdırılma protokolunun nümayiş olunmuş versiyası əvvəllər müəyyən edilmiş tələblərə cavab vermək üçün kifayət qədər möhkəm və çevikdir. Ancaq əlavə etmək istəyirəm ki, təsvir edilən tətbiq təkmilləşdirilə bilər. Məsələn, ötürmə qabiliyyətini artırmaq və taymer dövrlərini dinamik şəkildə dəyişdirmək üçün sürüşmə pəncərəsi və RTT kimi mexanizmlər protokola əlavə edilə bilər, həmçinin əlaqə qovşaqları arasında MTU-nun müəyyən edilməsi mexanizmini tətbiq etmək faydalı olacaq (lakin yalnız böyük mesajlar göndərildikdə) .

Diqqətiniz üçün təşəkkür edirəm, şərhlərinizi və şərhlərinizi gözləyirəm.

PS Təfərrüatlarla maraqlananlar və ya sadəcə protokolu sınamaq istəyənlər üçün GitHube-da layihəyə keçid:
Etibarlı UDP layihəsi

Faydalı bağlantılar və məqalələr

  1. TCP protokolunun spesifikasiyası: ingilis dilində и rus dilində
  2. UDP protokolunun spesifikasiyası: ingilis dilində и rus dilində
  3. RUDP protokolunun müzakirəsi: layihə-ietf-sigtran-etibarlı-udp-00
  4. Etibarlı Məlumat Protokolu: RF 908 и RF 1151
  5. UDP üzərindən çatdırılma təsdiqinin sadə tətbiqi: .NET və UDP ilə Şəbəkənizə Tam Nəzarət Edin
  6. NAT keçid mexanizmlərini təsvir edən məqalə: Şəbəkə Ünvan Tərcüməçiləri Arasında Peer-to-Peer Əlaqəsi
  7. Asinxron proqramlaşdırma modelinin həyata keçirilməsi: CLR Asinxron Proqramlaşdırma Modelinin həyata keçirilməsi и IAsyncResult dizayn nümunəsini necə həyata keçirmək olar
  8. Asinxron proqramlaşdırma modelini vəzifəyə əsaslanan asinxron modelə köçürmək (TAP-da APM):
    TPL və Ənənəvi .NET Asinxron Proqramlaşdırma
    Digər Asinxron Nümunələr və Növlər ilə qarşılıqlı əlaqə

Yeniləmə: Təşəkkür edirəm mayorovp и sidristij interfeysə tapşırıq əlavə etmək ideyası üçün. Kitabxananın köhnə əməliyyat sistemləri ilə uyğunluğu pozulmur, çünki 4-cü çərçivə həm XP, həm də 2003 serverini dəstəkləyir.

Mənbə: www.habr.com

Добавить комментарий