.Net için Güvenilir Udp protokolünün uygulanması

İnternet uzun zamandır değişti. Ana İnternet protokollerinden biri olan UDP, uygulamalar tarafından yalnızca veri birimleri ve yayınlar sunmak için değil, aynı zamanda ağ düğümleri arasında eşler arası bağlantılar sağlamak için de kullanılır. Basit tasarımı nedeniyle bu protokolün daha önce planlanmamış birçok kullanımı vardır; ancak protokolün garantili teslimatın olmaması gibi eksiklikleri ortadan kalkmamıştır. Bu makalede, Garantili Teslimat Protokolünün UDP üzerinden uygulanması açıklanmaktadır.
İçindekiler:Giriş
Protokol gereksinimleri
Güvenilir UDP başlığı
Protokolün genel ilkeleri
Protokol zaman aşımları ve zamanlayıcılar
Güvenilir UDP İletim Durumu Şeması
Kodun derinliklerine inin. Şanzıman kontrol ünitesi
Kodun derinliklerine inin. Devletler

Kodun derinliklerine inin. Bağlantı oluşturma ve kurma
Kodun derinliklerine inin. Zaman aşımı nedeniyle bağlantının kapatılması
Kodun derinliklerine inin. Veri aktarımı kurtarma
Güvenilir UDP API'si
Sonuç
Yararlı bağlantılar ve makaleler

Giriş

İnternetin orijinal mimarisi, her düğümün küresel ve benzersiz bir IP adresine sahip olduğu ve diğer düğümlerle doğrudan iletişim kurabildiği tek tip bir adres alanı öngörüyordu. Artık İnternet aslında farklı bir mimariye sahip; küresel IP adreslerinden oluşan bir alan ve NAT cihazlarının arkasına gizlenmiş özel adreslere sahip birçok alan.Bu mimaride, yalnızca global adres alanındaki cihazlar ağdaki herhangi biriyle kolayca iletişim kurabilir çünkü benzersiz, global olarak yönlendirilebilir bir IP adresine sahiptirler. Özel bir ağda bulunan bir düğüm, aynı ağdaki diğer düğümlere ve ayrıca küresel adres alanındaki diğer iyi bilinen düğümlere bağlanabilir. Bu etkileşim büyük ölçüde ağ adresi çeviri mekanizması sayesinde elde edilir. Wi-Fi yönlendiricileri gibi NAT cihazları, giden bağlantılar için özel çeviri tablosu girişleri oluşturur ve paketlerdeki IP adreslerini ve bağlantı noktası numaralarını değiştirir. Bu, özel bir ağdan küresel adres alanındaki ana bilgisayarlara giden bağlantıların yapılmasına olanak tanır. Ancak aynı zamanda NAT cihazları, gelen bağlantılar için ayrı kurallar belirlenmedikçe genellikle gelen tüm trafiği engeller.

Bu İnternet mimarisi, istemcilerin özel ağlarda bulunabildiği ve sunucuların genel bir adrese sahip olduğu istemci-sunucu etkileşimi için yeterince doğrudur. Ancak iki düğümü doğrudan birbirine bağlamakta zorluklar yaratır. farklı özel ağlar. İki düğümün doğrudan bağlantısı, ses iletimi (Skype), uzaktan bilgisayar erişimi (TeamViewer) veya çevrimiçi oyunlar gibi eşler arası uygulamalar için önemlidir.

Farklı özel ağlarda bulunan cihazlar arasında eşler arası bağlantı kurmanın en etkili yöntemlerinden birine "delik açma" adı verilir. Bu teknik çoğunlukla UDP protokolünü temel alan uygulamalarda kullanılır.

Ancak uygulamanız garantili veri teslimatı gerektiriyorsa, örneğin bilgisayarlar arasında dosya aktarıyorsanız, UDP'nin kullanılması, UDP'nin garantili bir teslimat protokolü olmaması ve paketlerin sırayla teslim edilmesini sağlamaması nedeniyle birçok zorluk yaratacaktır. TCP protokolü.

Bu durumda paketlerin garantili teslimatını sağlamak için gerekli işlevselliği sağlayan ve UDP üzerinde çalışan uygulama düzeyinde bir protokolün uygulanması gerekir.

Farklı özel ağlardaki düğümler arasında TCP bağlantıları kurmak için bir TCP delik açma tekniği bulunduğunu hemen belirtmek isterim, ancak birçok NAT cihazının bunu desteklememesi nedeniyle genellikle ana yöntem olarak kabul edilmez. bu tür düğümleri bağlamak.

Bu makalenin ilerleyen kısımlarında yalnızca garantili teslimat protokolünün uygulanmasını ele alacağım. UDP delik delme tekniğinin uygulanması aşağıdaki makalelerde anlatılacaktır.

Protokol gereksinimleri

  1. Olumlu bir geri bildirim mekanizması (olumlu onay olarak adlandırılan) yoluyla uygulanan güvenilir paket teslimatı
  2. Büyük verilerin verimli bir şekilde aktarılması ihtiyacı, ör. protokol gereksiz paket yeniden iletimlerinden kaçınmalıdır
  3. Teslimat onay mekanizmasını geçersiz kılmak mümkün olmalıdır (“saf” bir UDP protokolü olarak işlev görme yeteneği)
  4. Her mesajın onaylanmasıyla komut modunu uygulama imkanı
  5. Protokol kapsamında veri iletiminin temel birimi bir mesaj olmalıdır

Bu gereksinimler büyük ölçüde Güvenilir Veri Protokolü'nün gereksinimleriyle örtüşmektedir. rfc 908 и rfc 1151ve bu protokolü geliştirmek için bu standartlara güvendim.

Bu gereksinimleri anlamak için, TCP ve UDP protokollerini kullanan iki ağ düğümü arasındaki veri aktarımının zamanlama diyagramlarına bakalım. Her iki durumda da bir paketi kaybedelim.
Etkileşimli olmayan verileri TCP üzerinden aktarma:.Net için Güvenilir Udp protokolünün uygulanması

Diyagramdan da görebileceğiniz gibi paket kaybı durumunda TCP kayıp paketi algılayacak ve kayıp segment numarasını isteyerek göndericiye bildirecektir.
UDP protokolü aracılığıyla veri aktarımı:.Net için Güvenilir Udp protokolünün uygulanması

UDP, kayıpları tespit etmek için herhangi bir adım atmaz. UDP protokolündeki iletim hatalarının kontrolü tamamen uygulamanın sorumluluğundadır.

TCP protokolünde hata tespiti, bir uç düğümle bağlantı kurulması, bu bağlantının durumunun saklanması, her paket başlığında gönderilen bayt sayısının belirtilmesi ve alındıların bir onay numarası kullanılarak bildirilmesiyle gerçekleştirilir.

Ek olarak, performansı artırmak için (yani, bir onay almadan birden fazla segment göndermek), TCP protokolü, segmenti gönderenin almayı beklediği veri bayt sayısı olan iletim penceresi adı verilen bir pencereyi kullanır.

TCP protokolü hakkında daha fazla ayrıntıyı şurada bulabilirsiniz: rfc 793, UDP ile rfc 768aslında tanımlandıkları yer.

Yukarıdakilerden, UDP üzerinden güvenilir bir mesaj dağıtım protokolü oluşturmak için (bundan sonra arayacağız) açıktır. Güvenilir UDP), TCP'ye benzer veri aktarım mekanizmalarının uygulanması gerekmektedir. Yani:

  • bağlantı durumunu kaydet
  • bölüm numaralandırmasını kullan
  • özel onay paketlerini kullanın
  • protokol verimini artırmak için basitleştirilmiş bir pencere mekanizması kullanın

Ek olarak gerekli:

  • Bağlantıya kaynak tahsis etmek için bir mesajın başlangıcını işaret edin
  • Alınan mesajı yukarı akışlı bir uygulamaya iletmek ve protokol kaynaklarını serbest bırakmak için mesajın sonunu bildirir
  • bağlantıya özgü bir protokolün teslimat onayı mekanizmasının "saf" UDP olarak işlev görmesini devre dışı bırakmasına izin verir

Güvenilir UDP başlığı

Bir UDP datagramının bir IP datagramı içinde kapsüllendiğini hatırlayın. Güvenilir UDP paketi buna göre bir UDP datagramına "sarılır".
Güvenilir UDP başlık kapsüllemesi:.Net için Güvenilir Udp protokolünün uygulanması

Güvenilir UDP başlık yapısı oldukça basittir:

.Net için Güvenilir Udp protokolünün uygulanması

  • Bayraklar – paket kontrol bayrakları
  • Mesaj Türü - yukarı akış uygulamaları tarafından belirli mesajlara abone olmak için kullanılan mesaj türü
  • TransmissionId - iletim numarası, alıcının adresi ve bağlantı noktasıyla birlikte bağlantıyı benzersiz şekilde tanımlar
  • PacketNumber – paket numarası
  • Seçenekler – ek protokol seçenekleri. İlk paket durumunda mesaj boyutunu belirtmek için kullanılır

Bayraklar aşağıdaki gibidir:

  • FirstPacket - mesajın ilk paketi
  • NoAsk - mesaj, onay mekanizmasının etkinleştirilmesini gerektirmez
  • LastPacket - son mesaj paketi
  • requestForPacket - onay paketi veya kayıp paket talebi

Protokolün genel ilkeleri

Güvenilir UDP, bir mesajın iki düğüm arasında iletilmesini garanti etmeye odaklandığından, diğer tarafla bağlantı kurabilmelidir. Bir bağlantı kurmak için, gönderen taraf FirstPacket bayrağını içeren bir paket gönderir; bu pakete verilen yanıt, bağlantının kurulduğu anlamına gelir. Tüm yanıt paketleri veya başka bir deyişle onay paketleri, PacketNumber alanının değerini her zaman başarıyla gelen paketlerin en büyük PacketNumber değerinden bir büyük olacak şekilde ayarlar. Gönderilen ilk paketin Seçenekler alanı mesaj boyutunu kaydeder.

Bağlantıyı tamamlamak için benzer bir mekanizma kullanılır. LastPacket bayrağı son mesaj paketinde ayarlanır. Yanıt paketi, son paketin numarasını +1 gösterir; bu, alıcı taraf için mesajın başarıyla iletilmesi anlamına gelir.
Bağlantı kurma ve sonlandırma şeması:.Net için Güvenilir Udp protokolünün uygulanması

Bağlantı kurulduğunda veri aktarımı başlar. Veriler paket blokları halinde iletilir. Son blok hariç her blok sabit sayıda paket içerir. Alma/iletme penceresinin boyutuna eşittir. Son veri bloğunda daha az paket bulunabilir. Her bloğu gönderdikten sonra, gönderen taraf teslimatın onaylanmasını veya kayıp paketlerin yeniden teslim edilmesi talebini bekler ve yanıtları almak için alma/iletme penceresini açık bırakır. Blok teslimatının onayını aldıktan sonra alım/iletim penceresi değişir ve bir sonraki veri bloğu gönderilir.

Alıcı taraf paketleri alır. Her paketin iletim aralığına girip girmediği kontrol edilir. Pencereye düşmeyen paketler ve kopyalar elenir. Çünkü Pencerenin boyutu alıcı ve gönderen için sabit ve aynı olduğundan, bir paket bloğunun kayıpsız teslimi durumunda pencere bir sonraki veri bloğunun paketlerini alacak şekilde kaydırılır ve bir teslimat onayı gönderilir. . Pencerenin çalışma zamanlayıcısı tarafından belirlenen süre içinde dolmaması durumunda, hangi paketlerin teslim edilmediğini görmek için bir kontrol başlatılacak ve yeniden teslimat talepleri gönderilecektir.
Yeniden iletim şeması:.Net için Güvenilir Udp protokolünün uygulanması

Protokol zaman aşımları ve zamanlayıcılar

Bağlantı kurulamamasının birkaç nedeni vardır. Örneğin, alıcı taraf çevrimdışıysa. Bu durumda bağlantı kurmaya çalışırken zaman aşımı nedeniyle bağlantı kapatılacaktır. Güvenilir UDP uygulaması, zaman aşımlarını ayarlamak için iki zamanlayıcı kullanır. İlki, çalışma zamanlayıcısı, uzak ana bilgisayardan yanıt beklemek için kullanılır. Gönderen tarafta tetiklenirse, gönderilen son paket yeniden gönderilir. Alıcıda zamanlayıcı tetiklenirse kayıp paketler için kontrol yapılır ve yeniden teslimat talepleri gönderilir.

Düğümler arasında iletişim yoksa bağlantıyı kapatmak için ikinci zamanlayıcı gereklidir. Gönderen taraf için, çalışma zamanlayıcısının süresi dolduktan hemen sonra başlar ve uzak düğümden yanıt bekler. Belirtilen süre içerisinde yanıt alınamaması durumunda bağlantı sonlandırılır ve kaynaklar serbest bırakılır. Alıcı tarafı için bağlantı kapatma zamanlayıcısı, çalışma zamanlayıcısı iki kez tetiklendikten sonra başlar. Bu, onay paketinin kaybolmasını önlemek için gereklidir. Zamanlayıcı etkinleştiğinde bağlantı da sonlandırılır ve kaynaklar serbest bırakılır.

Güvenilir UDP İletim Durumu Şeması

Protokolün çalışma prensipleri, her durumu belirli bir paket işleme mantığından sorumlu olan sonlu durum makinesinde uygulanır.
Güvenilir UDP durum şeması:

.Net için Güvenilir Udp protokolünün uygulanması

Kapalı – aslında bir durum değil, makinenin başlangıç ​​ve bitiş noktasıdır. Devlet için Kapalı Eşzamansız bir UDP sunucusu uygulayan, paketleri uygun bağlantılara yönlendiren ve durum işlemeyi başlatan bir iletim kontrol bloğu alınır.

İlk PaketGönderme – mesaj gönderilirken giden bağlantının başlangıç ​​durumu.

Bu durumda normal mesajlar için ilk paket gönderilir. Gönderim onayı olmayan mesajlar için tek durum budur - mesajın tamamı bu şekilde gönderilir.

Gönderme Döngüsü – mesaj paketlerinin iletilmesi için temel durum.

Devletten buna geçiş İlk PaketGönderme ilk mesaj paketinin gönderilmesinden sonra gerçekleştirilir. Tüm onaylar ve yeniden iletim talepleri bu durumda gelir. Bundan iki durumda çıkmak mümkündür - mesajın başarıyla iletilmesi durumunda veya zaman aşımı nedeniyle.

İlk Paket Alındı – mesaj alıcısının başlangıç ​​durumu.

İletim başlangıcının doğruluğunu kontrol eder, gerekli yapıları oluşturur ve ilk paketin alındığına dair bir onay gönderir.

Tek paketten oluşan ve teslim onayı kullanılmadan gönderilen mesaj için tek durum budur. Böyle bir mesajın işlenmesinden sonra bağlantı kapatılır.

birleştirme – mesaj paketlerinin alınmasına ilişkin temel durum.

Paketleri geçici depolamaya yazar, paket kaybını kontrol eder, bir paket bloğunun ve tüm mesajın teslimine ilişkin onayları gönderir ve kayıp paketlerin yeniden teslimi için talepler gönderir. Mesajın tamamı başarıyla alınırsa bağlantı Tamamlandıaksi takdirde zaman aşımı çıkışı gerçekleşir.

Tamamlandı – mesajın tamamı başarıyla alınırsa bağlantının kapatılması.

Bu durum, mesajın birleştirilmesi ve mesajın teslim onayının gönderene giderken kaybolduğu durumlar için gereklidir. Bu durumdan bir zaman aşımı ile çıkılır, ancak bağlantı başarıyla kapatılmış olarak kabul edilir.

Kodun derinliklerine inin. Şanzıman kontrol ünitesi

Güvenilir UDP'nin temel unsurlarından biri iletim kontrol bloğudur. Bu bloğun amacı mevcut bağlantıları ve yardımcı elemanları depolamak, gelen paketleri uygun bağlantılara dağıtmak, bağlantıya paket göndermek için bir arayüz sağlamak ve protokol API'sini uygulamaktır. İletim kontrol bloğu, paketleri UDP katmanından alır ve bunları işlenmek üzere durum makinesine yönlendirir. Paketleri almak için eşzamansız bir UDP sunucusu uygular.
ReliableUdpConnectionControlBlock sınıfının bazı üyeleri:

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

Eşzamansız bir UDP sunucusunun uygulanması:

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

Her mesaj iletimi için bağlantı bilgilerini içeren bir yapı oluşturulur. Bu yapıya denir bağlantı kaydı.
ReliableUdpConnectionRecord sınıfının bazı üyeleri:

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

Kodun derinliklerine inin. Devletler

Devletler, paketlerin ana işlenmesinin gerçekleştiği Güvenilir UDP protokolünün durum makinesini uygular. ReliableUdpState soyut sınıfı durum için bir arayüz sağlar:

.Net için Güvenilir Udp protokolünün uygulanması

Protokolün tüm mantığı, yukarıda sunulan sınıflar tarafından, örneğin bağlantı kaydından ReliableUdp başlığının oluşturulması gibi statik yöntemler sağlayan bir yardımcı sınıfla birlikte uygulanır.

Daha sonra, protokolün temel algoritmalarını tanımlayan arayüz yöntemlerinin uygulanmasını ayrıntılı olarak ele alacağız.

DisposeByTimeout Yöntemi

DisposeByTimeout yöntemi, zaman aşımı süresi dolduğunda bağlantı kaynaklarının serbest bırakılmasından ve mesaj tesliminin başarılı/başarısız olduğunu bildirmekten sorumludur.
ReliableUdpState.DisposeByTimeout:

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

Yalnızca eyalette geçersiz kılınır Tamamlandı.
Tamamlandı.DisposeByTimeout:

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

ProcessPackets yöntemi

ProcessPackets yöntemi, paketin veya paketlerin ek işlenmesinden sorumludur. Doğrudan veya paket bekleme zamanlayıcısı aracılığıyla çağrılır.

Durumda birleştirme yöntem geçersiz kılınır ve kayıp paketlerin kontrol edilmesinden ve duruma geçişten sorumludur Tamamlandı, son paketin alınması ve kontrolün başarıyla geçmesi durumunda
Montaj.İşlemPaketleri:

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

Durumda Gönderme Döngüsü bu yöntem yalnızca bir zamanlayıcıda çağrılır ve son mesajın yeniden gönderilmesinden ve bağlantı kapanma zamanlayıcısının başlatılmasından sorumludur.
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);
}

Durumda Tamamlandı Yöntem, çalışan zamanlayıcıyı durdurur ve abonelere bir mesaj iletir.
Tamamlandı. Süreç Paketleri:

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

ReceivePacket yöntemi

Durumda İlk Paket Alındı Yöntemin asıl görevi, ilk mesaj paketinin gerçekten arayüze ulaşıp ulaşmadığını belirlemek ve ayrıca tek paketten oluşan bir mesajı bir araya getirmektir.
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);
  }
}

Durumda Gönderme Döngüsü bu yöntem, teslimat onaylarını ve yeniden iletim isteklerini kabul etmek için geçersiz kılınır.
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));
}

Durumda birleştirme ReceivePacket yönteminde asıl iş, gelen paketlerden mesajın bir araya getirilmesidir.
Montaj.AlmaPaketi:

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

Durumda Tamamlandı Yöntemin tek görevi, mesajın başarıyla iletildiğine dair tekrarlanan bir onay göndermektir.
Tamamlandı. Paketin Alınması:

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

SendPacket yöntemi

Durumda İlk PaketGönderme Bu yöntem, ilk veri paketini veya mesaj teslim onayı gerektirmiyorsa mesajın tamamını gönderir.
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);
}

Durumda Gönderme Döngüsü bu yöntemde bir paket bloğu gönderilir.
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 );
  }
}

Kodun derinliklerine inin. Bağlantı oluşturma ve kurma

Artık temel durumlara ve durumları işlemek için kullanılan yöntemlere aşina olduğumuza göre, protokolün işleyişinin çeşitli örneklerine biraz daha ayrıntılı olarak bakabiliriz.
Normal koşullar altında veri aktarım şeması:.Net için Güvenilir Udp protokolünün uygulanması

Yaradılışa daha yakından bakalım bağlantı kaydı İlk paketi bağlamak ve göndermek için. Aktarım her zaman mesajı göndermek için API yöntemini çağıran uygulama tarafından başlatılır. Daha sonra, yeni mesaj için veri iletimini başlatarak iletim kontrol bloğunun StartTransmission yöntemi kullanılır.
Giden bağlantı oluşturma:

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önderilmesi (FirstPacketSending durumu):

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önderdikten sonra gönderen duruma girer Gönderme Döngüsü – Paket teslimatının onaylanmasını bekleyin.
Alıcı taraf EndReceive yöntemini kullanarak gönderilen paketi kabul eder ve yeni bir paket oluşturur. bağlantı kaydı ve bu paketi, önceden ayrıştırılmış bir başlıkla, işlenmek üzere ReceivePacket durumuna iletir. İlk Paket Alındı
Alıcı tarafta bir bağlantı oluşturma:

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 alınması ve onay gönderilmesi (FirstPacketReceived durumu):

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

Kodun derinliklerine inin. Zaman aşımı nedeniyle bağlantının kapatılması

Zaman aşımlarının işlenmesi Güvenilir UDP'nin önemli bir parçasıdır. Bir ara düğümün arızalandığı ve her iki yönde de veri iletiminin imkansız hale geldiği bir örneği düşünün.
Bir bağlantıyı zaman aşımına göre kapatma şeması:.Net için Güvenilir Udp protokolünün uygulanması

Diyagramdan da görülebileceği gibi gönderenin çalışma zamanlayıcısı, paket bloğu gönderildikten hemen sonra başlar. Bu durumun SendPacket yönteminde gerçekleşir Gönderme Döngüsü.
Çalışma zamanlayıcısını etkinleştirme (SendingCycle durumu):

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

Bağlantı oluşturulduğunda zamanlayıcı süreleri ayarlanır. Varsayılan olarak ShortTimerPeriod 5 saniyedir. Örnekte 1,5 saniyeye ayarlanmıştır.

Gelen bir bağlantı için zamanlayıcı, alınan son veri paketini aldıktan sonra başlar; bu, durumun ReceivePacket yönteminde gerçekleşir. birleştirme
Çalışma zamanlayıcısının etkinleştirilmesi (Montaj durumu):

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

Gelen bağlantı, çalışma zamanlayıcısının bekleme süresi boyunca daha fazla paket almadı. Zamanlayıcı, kayıp paketleri algılayan ve ilk kez yeniden teslimat istekleri gönderen ProcessPackets yöntemini çalıştırdı ve çağırdı.
Yeniden teslimat isteklerinin gönderilmesi (Montaj durumu):

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 değişkeni şu şekilde ayarlanır: gerçek. Bu değişken çalışma zamanlayıcısının yeniden başlatılmasından sorumludur.

Gönderen tarafında ayrıca bir çalışma zamanlayıcısı tetiklenir ve son gönderilen paket yeniden gönderilir.
Bağlantı kapatma zamanlayıcısını etkinleştirme (SendingCycle durumu):

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

Bundan sonra giden bağlantıda bağlantı kapatma zamanlayıcısı başlatılı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ı kapatma zamanlayıcısının varsayılan zaman aşımı süresi 30 saniyedir.

Kısa bir süre sonra alıcı taraftaki çalışma zamanlayıcısı tekrar tetiklenir, istekler tekrar gönderilir ve ardından gelen bağlantı için bağlantı kapatma zamanlayıcısı başlatılır.

Kapanış zamanlayıcıları etkinleştiğinde her iki bağlantı kaydının tüm kaynakları serbest bırakılır. Gönderen, teslimat hatasını yukarı akış uygulamasına bildirir (API Güvenilir UDP'ye bakın).
Bağlantı kaydı kaynaklarının serbest bırakı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);
  }
}

Kodun derinliklerine inin. Veri aktarımı kurtarma

Paket kaybı durumunda veri iletimini kurtarma şeması:.Net için Güvenilir Udp protokolünün uygulanması

Bağlantının zaman aşımı nedeniyle kapatılması konusunda daha önce tartışıldığı gibi, çalışma zamanlayıcısının süresi dolduğunda, alıcı kayıp paketleri kontrol edecektir. Paket kayıpları olması durumunda alıcıya ulaşmayan paketlerin sayılarından oluşan bir liste derlenecektir. Bu numaralar belirli bir bağlantının LostPackets dizisine girilir ve yeniden dağıtım talepleri gönderilir.
Paketleri yeniden teslim etmek için istek gönderme (Birleştirme durumu):

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önderen, yeniden teslimat talebini kabul edecek ve eksik paketleri yeniden gönderecektir. Şu anda gönderenin zaten bir bağlantı kapatma zamanlayıcısını başlattığını ve bir istek alındığında sıfırlandığını belirtmekte fayda var.
Kayıp paketleri yeniden gönderme (SendingCycle durumu):

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

Yeniden gönderilen paket (şemadaki paket#3) gelen bağlantı tarafından alınır. Alma penceresinin dolu olduğundan ve normal veri iletiminin geri yüklendiğinden emin olmak için bir kontrol yapılır.
Alma penceresindeki isabetlerin kontrol edilmesi (Birleştirme durumu):

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

Güvenilir UDP API'si

Veri aktarım protokolüyle etkileşim kurmak için, iletim kontrol bloğunun üzerinde bir sarmalayıcı olan açık sınıf Reliable Udp vardır. İşte en önemli sınıf üyeleri:

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

Mesaj alımı abonelikle gerçekleştirilir. Geri çağırma yöntemi için temsilci 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; }
}

Belirli bir mesaj türüne ve/veya belirli bir gönderene abone olmak için iki isteğe bağlı parametre kullanılır: ReliableUdpMessageTypes messageType ve IPEndPoint ipEndPoint.

Mesaj türleri:

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

Mesaj eşzamansız olarak gönderilir; bu amaçla protokol eşzamansız bir programlama modeli uygular:

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

Mesaj göndermenin sonucu, eğer mesaj alıcıya başarılı bir şekilde ulaştıysa doğru, bağlantı zaman aşımı nedeniyle kapatılmışsa yanlış olacaktır:

public bool EndSendMessage(IAsyncResult asyncResult)

Sonuç

Bu makale kapsamında pek fazla şey anlatılmadı. İş parçacığı koordinasyon mekanizmaları, istisna ve hata yönetimi, asenkron mesaj gönderme yöntemlerinin uygulanması. Ancak protokolün özü, yani paketleri işleme, bağlantı kurma ve zaman aşımlarını giderme mantığının açıklaması sizin için daha net hale gelecektir.

Güvenilir dağıtım protokolünün gösterilen versiyonu oldukça sağlam ve esnektir ve daha önce tanımlanan gereksinimleri karşılar. Ancak anlatılan uygulamanın geliştirilebileceğini eklemek istiyorum. Örneğin, verimi artırmak ve zamanlayıcı sürelerini dinamik olarak değiştirmek için protokole kayan pencere ve RTT gibi mekanizmalar ekleyebilirsiniz; bağlantı düğümleri arasındaki MTU'yu belirlemek için bir mekanizma uygulamak da yararlı olacaktır (ancak yalnızca gönderme durumunda) büyük mesajlar).

İlginiz için teşekkür ederim, yorumlarınızı ve yorumlarınızı bekliyorum.

Not: Ayrıntılarla ilgilenenler veya sadece protokolü test etmek isteyenler için GitHube'daki projenin bağlantısı burada:
Güvenilir UDP Projesi

Yararlı bağlantılar ve makaleler

  1. TCP Protokolü Belirtimi: ingilizce и на русском
  2. UDP protokolü spesifikasyonu: ingilizce и на русском
  3. RUDP protokolünün tartışılması: taslak-ietf-sigtran-güvenilir-udp-00
  4. Güvenilir Veri Protokolü: rfc 908 и rfc 1151
  5. UDP üzerinden teslimat onayının basit bir uygulaması: .NET ve UDP ile Ağınızın Tam Kontrolünü Elinize Alın
  6. NAT'ların üstesinden gelmeye yönelik mekanizmaları açıklayan bir makale: Ağ Adresi Çevirmenleri Arasında Eşler Arası İletişim
  7. Asenkron programlama modelinin uygulanması: CLR Eşzamansız Programlama Modelinin Uygulanması и IAsyncResult tasarım modeli nasıl uygulanır?
  8. Eşzamansız programlama modelini görev tabanlı eşzamansız modele (APM'den TAP'a) taşıma:
    TPL ve Geleneksel .NET Asenkron Programlama
    Diğer Eşzamansız Modeller ve Türlerle Birlikte Çalışma

Güncelleme: Teşekkürler belediye başkanı и Sidristij arayüze bir görev ekleme fikri için. Kitaplığın eski işletim sistemleriyle uyumluluğu etkilenmez çünkü 4. çerçeve hem XP hem de 2003 sunucusunu destekler.

Kaynak: habr.com

Yorum ekle