.Net 的 Reliable Udp 協議的實現

互聯網早已改變。 Internet 的主要協議之一 - UDP 被應用程序不僅用於傳送數據報和廣播,而且還用於提供網絡節點之間的“點對點”連接。 由於其簡單的設計,該協議有許多以前計劃外的用途,但是,該協議的缺點,如缺乏保證交付,並沒有在任何地方消失。 本文描述了基於 UDP 的保證傳送協議的實現。
內容:條目
協議要求
可靠的 UDP 標頭
議定書的一般原則
超時和協議計時器
可靠UDP傳輸狀態圖
深入代碼。 傳輸控制單元
深入代碼。 狀態

深入代碼。 創建和建立連接
深入代碼。 超時關閉連接
深入代碼。 恢復數據傳輸
可靠的 UDP API
結論
有用的鏈接和文章

條目

Internet 的原始架構假定一個同構的地址空間,其中每個節點都有一個全球唯一的 IP 地址,並且可以直接與其他節點通信。 事實上,現在的互聯網已經有了不同的架構——一個全球 IP 地址區域和許多私有地址隱藏在 NAT 設備後面的區域。在這種架構中,只有全球地址空間中的設備才能輕鬆地與網絡上的任何人通信,因為它們具有唯一的、全球可路由的 IP 地址。 私有網絡上的節點可以連接到同一網絡上的其他節點,也可以連接到全局地址空間中的其他知名節點。 這種交互的實現主要是由於網絡地址轉換機制。 NAT 設備(例如 Wi-Fi 路由器)會為傳出連接創建特殊的轉換錶條目,並修改數據包中的 IP 地址和端口號。 這允許從專用網絡到全局地址空間中的主機的傳出連接。 但與此同時,NAT 設備通常會阻止所有傳入流量,除非為傳入連接設置了單獨的規則。

這種 Internet 架構對於客戶端-服務器通信來說足夠正確,其中客戶端可以在專用網絡中,而服務器具有全局地址。 但這給兩個節點之間的直接連接帶來了困難 各種 專用網絡。 兩個節點之間的直接連接對於點對點應用程序非常重要,例如語音傳輸 (Skype)、遠程訪問計算機 (TeamViewer) 或在線遊戲。

在不同專用網絡上的設備之間建立對等連接的最有效方法之一稱為打孔。 此技術最常用於基於 UDP 協議的應用程序。

但是如果你的應用程序需要保證數據的傳遞,例如,你在計算機之間傳輸文件,那麼使用 UDP 就會有很多困難,因為 UDP 不是一個有保證的傳遞協議,並且不像 TCP 那樣提供按順序的數據包傳遞協議。

在這種情況下,為確保有保證的數據包交付,需要實施提供必要功能並在 UDP 上工作的應用層協議。

馬上要說明的是,有一種TCP打洞技術可以在不同私有網絡的節點之間建立TCP連接,但是由於許多NAT設備不支持它,所以通常不認為它是連接的主要方式這樣的節點。

對於本文的其餘部分,我將只關注有保證的交付協議的實現。 UDP打洞技術的實現將在後續文章中介紹。

協議要求

  1. 通過正反饋機制(所謂的正確認)實現可靠的數據包傳遞
  2. 需要有效傳輸大數據,即該協議必須避免不必要的數據包中繼
  3. 應該可以取消傳遞確認機制(作為“純”UDP協議運行的能力)
  4. 能夠實現命令模式,確認每條消息
  5. 通過協議傳輸數據的基本單位必須是消息

這些要求在很大程度上與中描述的可靠數據協議要求一致 RFC 908 и RFC 1151,我在開發此協議時依賴於這些標準。

為了理解這些要求,讓我們看看使用 TCP 和 UDP 協議在兩個網絡節點之間傳輸數據的時間。 在這兩種情況下,我們都會丟失一個數據包。
通過 TCP 傳輸非交互式數據:.Net 的 Reliable Udp 協議的實現

從圖中可以看出,在丟包的情況下,TCP 會檢測到丟失的數據包,並通過詢問丟失段的編號將其報告給發送方。
通過 UDP 協議傳輸數據:.Net 的 Reliable Udp 協議的實現

UDP 不採取任何丟失檢測步驟。 UDP 協議中傳輸錯誤的控製完全是應用程序的責任。

TCP 協議中的錯誤檢測是通過與端節點建立連接、存儲該連接的狀態、指示每個數據包標頭中發送的字節數以及使用確認號通知收據來實現的。

此外,為了提高性能(即在沒有收到確認的情況下發送多個數據段),TCP 協議使用所謂的傳輸窗口 - 數據段的發送方期望接收的數據字節數。

有關 TCP 協議的詳細信息,請參閱 RFC 793, 從 UDP 到 RFC 768實際上,它們是在哪裡定義的。

由上可見,為了創建一個基於UDP的可靠消息傳遞協議(以下簡稱 可靠的UDP),需要實現類似TCP的數據傳輸機制。 即:

  • 保存連接狀態
  • 使用段編號
  • 使用特殊的確認包
  • 使用簡化的窗口機制來增加協議吞吐量

此外,您需要:

  • 發出消息開始信號,為連接分配資源
  • 發出消息結束信號,將接收到的消息傳遞給上游應用程序並釋放協議資源
  • 允許特定於連接的協議禁用傳遞確認機制以充當“純”UDP

可靠的 UDP 標頭

回想一下 UDP 數據報封裝在 IP 數據報中。 可靠的 UDP 數據包被適當地“包裝”到 UDP 數據報中。
可靠的 UDP 頭封裝:.Net 的 Reliable Udp 協議的實現

Reliable UDP 報頭的結構非常簡單:

.Net 的 Reliable Udp 協議的實現

  • Flags - 包控制標誌
  • MessageType - 上游應用程序用於訂閱特定消息的消息類型
  • TransmissionId - 傳輸的編號,連同接收方的地址和端口,唯一標識連接
  • PacketNumber - 包號
  • 選項 - 附加協議選項。 在第一個數據包的情況下,它用於指示消息的大小

標誌如下:

  • FirstPacket - 消息的第一個數據包
  • NoAsk - 消息不需要啟用確認機制
  • LastPacket - 消息的最後一個數據包
  • RequestForPacket - 確認包或請求丟失的包

議定書的一般原則

由於 Reliable UDP 側重於保證兩個節點之間的消息傳輸,因此它必須能夠與另一端建立連接。 為建立連接,發送方發送一個帶有 FirstPacket 標誌的數據包,對該標誌的響應將意味著連接已建立。 所有響應數據包,或者換句話說,確認數據包,總是將 PacketNumber 字段的值設置為比成功接收的數據包的最大 PacketNumber 值大 XNUMX。 發送的第一個數據包的選項字段是消息的大小。

類似的機制用於終止連接。 LastPacket 標誌設置在消息的最後一個數據包上。 在響應數據包中,表示最後一個數據包的編號+1,這對於接收方來說意味著消息傳遞成功。
連接建立和終止示意圖:.Net 的 Reliable Udp 協議的實現

連接建立後,數據傳輸開始。 數據以數據包塊的形式傳輸。 除最後一個塊外,每個塊都包含固定數量的數據包。 它等於接收/發送窗口大小。 最後一個數據塊可能有更少的數據包。 發送每個塊後,發送方等待發送確認或重新發送丟失數據包的請求,使接收/發送窗口保持打開狀態以接收響應。 在收到塊交付確認後,接收/發送窗口移動並發送下一個數據塊。

接收方接收數據包。 檢查每個數據包以查看它是否落在傳輸窗口內。 不落入窗口的數據包和重複數據包將被過濾掉。 因為如果窗口的大小是固定的,並且對於接收方和發送方來說是相同的,那麼在一個數據包塊被無丟失地傳送的情況下,窗口被移動以接收下一個數據塊的數據包並且傳送確認是發送。 如果窗口在工作計時器設置的時間內沒有填滿,則將開始檢查哪些數據包尚未交付,並發送重新交付請求。
重傳圖:.Net 的 Reliable Udp 協議的實現

超時和協議計時器

無法建立連接的原因有多種。 例如,如果接收方不在線。 在這種情況下,當嘗試建立連接時,連接將被超時關閉。 Reliable UDP 實現使用兩個計時器來設置超時。 第一個是工作計時器,用於等待遠程主機的響應。 如果它在發送方觸發,那麼最後發送的數據包將被重新發送。 如果計時器在接收方超時,則執行丟失數據包檢查並發送重新投遞請求。

如果節點之間缺乏通信,則需要第二個計時器來關閉連接。 對於發送端,在工作定時器超時後立即啟動,等待遠程節點的響應。 如果在指定時間內沒有響應,則終止連接並釋放資源。 對於接收方,連接關閉定時器在工作定時器超時兩次後啟動。 這是確保確認數據包不會丟失所必需的。 當計時器到期時,連接也將終止並釋放資源。

可靠UDP傳輸狀態圖

協議的原理在有限狀態機中實現,每個狀態機負責數據包處理的特定邏輯。
可靠的 UDP 狀態圖:

.Net 的 Reliable Udp 協議的實現

關閉 - 不是真正的狀態,它是自動機的起點和終點。 對於狀態 關閉 接收到一個傳輸控制塊,它實現了一個異步 UDP 服務器,將數據包轉發到適當的連接並開始狀態處理。

第一個數據包發送 – 發送消息時傳出連接的初始狀態。

在這種狀態下,發送正常消息的第一個數據包。 對於沒有發送確認的消息,這是發送整個消息的唯一狀態。

發送周期 – 消息包傳輸的基態。

從狀態過渡到它 第一個數據包發送 在發送消息的第一個數據包後執行。 正是在這種狀態下,所有的確認和重傳請求都到來了。 在兩種情況下可以退出 - 在成功傳遞消息或超時的情況下。

收到第一個數據包 – 消息接收者的初始狀態。

它檢查傳輸開始的正確性,創建必要的結構,並發送第一個數據包的接收確認。

對於由單個數據包組成且未使用交付證明發送的消息,這是唯一的狀態。 處理完此類消息後,連接將關閉。

組裝 – 接收消息包的基本狀態。

它將數據包寫入臨時存儲,檢查數據包丟失,發送數據包塊和整個消息的傳送確認,並發送重新傳送丟失數據包的請求。 如果成功接收到整個消息,連接將進入狀態 完成,否則超時。

完成 – 在成功接收到整個消息的情況下關閉連接。

此狀態對於消息的組裝以及消息的傳遞確認在發送給發件人的途中丟失的情況是必需的。 超時退出此狀態,但連接被認為已成功關閉。

深入代碼。 傳輸控制單元

Reliable UDP 的關鍵要素之一是傳輸控制塊。 該塊的任務是存儲當前連接和輔助元素,將傳入的數據包分發到相應的連接,提供向連接發送數據包的接口,並實現協議 API。 傳輸控制塊從 UDP 層接收數據包,並將它們轉發給狀態機進行處理。 為了接收數據包,它實現了一個異步 UDP 服務器。
ReliableUdpConnectionControlBlock 類的一些成員:

internal class ReliableUdpConnectionControlBlock : IDisposable
{
  // массив байт для указанного ключа. Используется для сборки входящих сообщений    
  public ConcurrentDictionary<Tuple<EndPoint, Int32>, byte[]> IncomingStreams { get; private set;}
  // массив байт для указанного ключа. Используется для отправки исходящих сообщений.
  public ConcurrentDictionary<Tuple<EndPoint, Int32>, byte[]> OutcomingStreams { get; private set; }
  // connection record для указанного ключа.
  private readonly ConcurrentDictionary<Tuple<EndPoint, Int32>, ReliableUdpConnectionRecord> m_listOfHandlers;
  // список подписчиков на сообщения.
  private readonly List<ReliableUdpSubscribeObject> m_subscribers;    
  // локальный сокет    
  private Socket m_socketIn;
  // порт для входящих сообщений
  private int m_port;
  // локальный IP адрес
  private IPAddress m_ipAddress;    
  // локальная конечная точка    
  public IPEndPoint LocalEndpoint { get; private set; }    
  // коллекция предварительно инициализированных
  // состояний конечного автомата
  public StatesCollection States { get; private set; }
  // генератор случайных чисел. Используется для создания TransmissionId
  private readonly RNGCryptoServiceProvider m_randomCrypto;    	
  //...
}

異步UDP服務器的實現:

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

對於每個消息傳輸,都會創建一個結構,其中包含有關連接的信息。 這樣的結構稱為 連接記錄.
ReliableUdpConnectionRecord 類的一些成員:

internal class ReliableUdpConnectionRecord : IDisposable
{    
  // массив байт с сообщением    
  public byte[] IncomingStream { get; set; }
  // ссылка на состояние конечного автомата    
  public ReliableUdpState State { get; set; }    
  // пара, однозначно определяющая connection record
  // в блоке управления передачей     
  public Tuple<EndPoint, Int32> Key { get; private set;}
  // нижняя граница приемного окна    
  public int WindowLowerBound;
  // размер окна передачи
  public readonly int WindowSize;     
  // номер пакета для отправки
  public int SndNext;
  // количество пакетов для отправки
  public int NumberOfPackets;
  // номер передачи (именно он и есть вторая часть Tuple)
  // для каждого сообщения свой	
  public readonly Int32 TransmissionId;
  // удаленный IP endpoint – собственно получатель сообщения
  public readonly IPEndPoint RemoteClient;
  // размер пакета, во избежание фрагментации на IP уровне
  // не должен превышать MTU – (IP.Header + UDP.Header + RelaibleUDP.Header)
  public readonly int BufferSize;
  // блок управления передачей
  public readonly ReliableUdpConnectionControlBlock Tcb;
  // инкапсулирует результаты асинхронной операции для BeginSendMessage/EndSendMessage
  public readonly AsyncResultSendMessage AsyncResult;
  // не отправлять пакеты подтверждения
  public bool IsNoAnswerNeeded;
  // последний корректно полученный пакет (всегда устанавливается в наибольший номер)
  public int RcvCurrent;
  // массив с номерами потерянных пакетов
  public int[] LostPackets { get; private set; }
  // пришел ли последний пакет. Используется как bool.
  public int IsLastPacketReceived = 0;
  //...
}

深入代碼。 狀態

狀態實現 Reliable UDP 協議的狀態機,其中主要處理數據包。 抽像類 ReliableUdpState 為狀態提供了一個接口:

.Net 的 Reliable Udp 協議的實現

協議的整個邏輯由上面介紹的類以及提供靜態方法的輔助類一起實現,例如,從連接記錄構造 ReliableUdp 標頭。

接下來,我們將詳細考慮確定協議基本算法的接口方法的實現。

DisposeByTimeout 方法

DisposeByTimeout 方法負責在超時後釋放連接資源,並發出成功/不成功消息傳遞的信號。
ReliableUdpState.DisposeByTimeout:

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

它僅在狀態下被覆蓋 完成.
完成.DisposeByTimeout:

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

ProcessPackets 方法

ProcessPackets 方法負責對一個或多個包進行額外處理。 直接調用或通過數據包等待計時器調用。

有能力的 組裝 該方法被覆蓋並負責檢查丟失的數據包並轉換到狀態 完成, 如果收到最後一個數據包並通過成功檢查
組裝.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);
  }
}

有能力的 發送周期 此方法僅在計時器上調用,負責重新發送最後一條消息,以及啟用連接關閉計時器。
發送周期.ProcessPackets:

public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
  if (connectionRecord.IsDone != 0)
    return;        
  // отправляем повторно последний пакет 
  // ( в случае восстановления соединения узел-приемник заново отправит запросы, которые до него не дошли)        
  ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.RetransmissionCreateUdpPayload(connectionRecord, connectionRecord.SndNext - 1));
  // включаем таймер CloseWait – для ожидания восстановления соединения или его завершения
  StartCloseWaitTimer(connectionRecord);
}

有能力的 完成 該方法停止正在運行的計時器並將消息發送給訂閱者。
完成.ProcessPackets:

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

接收數據包方法

有能力的 收到第一個數據包 該方法的主要任務是判斷第一個消息包是否真正到達接口,同時收集由單個數據包組成的消息。
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);
  }
}

有能力的 發送周期 重寫此方法以接受傳送確認和重傳請求。
發送周期.接收包:

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

有能力的 組裝 在 ReceivePacket 方法中,主要工作是從傳入的數據包中組裝消息。
組裝.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);
  }
}

有能力的 完成 該方法的唯一任務是發送消息成功傳遞的重新確認。
完成.ReceivePacket:

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

發包方式

有能力的 第一個數據包發送 此方法發送第一個數據包,或者,如果消息不需要傳遞確認,則發送整個消息。
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);
}

有能力的 發送周期 在這種方法中,發送了一個數據包塊。
發送周期.發送數據包:

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

深入代碼。 創建和建立連接

現在我們已經了解了基本狀態和用於處理狀態的方法,讓我們更詳細地分解一些協議如何工作的示例。
正常情況下的數據傳輸圖:.Net 的 Reliable Udp 協議的實現

詳細考慮創建 連接記錄 連接並發送第一個數據包。 傳輸始終由調用發送消息 API 的應用程序發起。 接下來,調用傳輸控制塊的 StartTransmission 方法,開始傳輸新消息的數據。
創建傳出連接:

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

發送第一個數據包(FirstPacketSending 狀態):

public override void SendPacket(ReliableUdpConnectionRecord connectionRecord)
{
  connectionRecord.PacketCounter = 0;
  connectionRecord.SndNext = 0;
  connectionRecord.WindowLowerBound = 0;       
  // ... 
  // создаем заголовок пакета и отправляем его 
  ReliableUdpHeader header = ReliableUdpStateTools.CreateReliableUdpHeader(connectionRecord);
  ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.CreateUdpPayload(connectionRecord, header));
  // увеличиваем счетчик
  connectionRecord.SndNext++;
  // сдвигаем окно
  connectionRecord.WindowLowerBound++;
  // переходим в состояние SendingCycle
  connectionRecord.State = connectionRecord.Tcb.States.SendingCycle;
  // Запускаем таймер
  connectionRecord.WaitForPacketsTimer = new Timer(CheckByTimer, connectionRecord, connectionRecord.ShortTimerPeriod, -1);
}

發送第一個數據包後,發送方進入狀態 發送周期 – 等待確認包裹送達。
接收端,使用EndReceive方法,接收發送過來的數據包,新建一個 連接記錄 並將這個帶有預解析頭的數據包傳遞給狀態的 ReceivePacket 方法進行處理 收到第一個數據包
在接收端創建連接:

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

接收第一個數據包並發送確認(FirstPacketReceived 狀態):

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  if (!header.Flags.HasFlag(ReliableUdpHeaderFlags.FirstPacket))
    // отбрасываем пакет
    return;
  // ...
  // by design все packet numbers начинаются с 0;
  if (header.PacketNumber != 0)          
    return;
  // инициализируем массив для хранения частей сообщения
  ReliableUdpStateTools.InitIncomingBytesStorage(connectionRecord, header);
  // записываем данные пакет в массив
  ReliableUdpStateTools.WritePacketData(connectionRecord, header, payload);
  // считаем кол-во пакетов, которые должны прийти
  connectionRecord.NumberOfPackets = (int)Math.Ceiling((double) ((double) connectionRecord.IncomingStream.Length/(double) connectionRecord.BufferSize));
  // записываем номер последнего полученного пакета (0)
  connectionRecord.RcvCurrent = header.PacketNumber;
  // после сдвинули окно приема на 1
  connectionRecord.WindowLowerBound++;
  // переключаем состояние
  connectionRecord.State = connectionRecord.Tcb.States.Assembling;  
  if (/*если не требуется механизм подтверждение*/)
  // ...
  else
  {
    // отправляем подтверждение
    ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
    connectionRecord.WaitForPacketsTimer = new Timer(CheckByTimer, connectionRecord, connectionRecord.ShortTimerPeriod, -1);
  }
}

深入代碼。 超時關閉連接

超時處理是 Reliable UDP 的重要組成部分。 考慮一個示例,其中一個中間節點發生故障並且無法雙向傳輸數據。
超時關閉連接示意圖:.Net 的 Reliable Udp 協議的實現

從圖中可以看出,發送方的工作定時器在發送完一個數據塊後立即啟動。 這發生在狀態的 SendPacket 方法中 發送周期.
啟用工作定時器(SendingCycle 狀態):

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

定時器週期是在創建連接時設置的。 默認的 ShortTimerPeriod 是 5 秒。 在示例中,它設置為 1,5 秒。

對於傳入連接,計時器在收到最後一個傳入數據包後啟動,這發生在狀態的 ReceivePacket 方法中 組裝
啟用工作計時器(組裝狀態):

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

在等待工作計時器時,沒有更多數據包到達傳入連接。 計時器關閉並調用 ProcessPackets 方法,在該方法中找到丟失的數據包並首次發送重新傳送請求。
發送重新投遞請求(組裝狀態):

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 變量設置為 . 該變量負責重新啟動工作定時器。

在發送端,工作定時器也被觸發,最後發送的數據包被重發。
啟用連接關閉定時器(SendingCycle 狀態):

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

之後,連接關閉計時器在傳出連接中啟動。
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);
}

連接關閉定時器超時時間默認為 30 秒。

短時間後,接收方的工作計時器再次觸發,再次發送請求,之後連接關閉計時器為傳入連接啟動

當關閉計時器觸發時,兩個連接記錄的所有資源都將被釋放。 發送方向上游應用報告投遞失敗(請參閱可靠的 UDP API).
釋放連接記錄資源:

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

深入代碼。 恢復數據傳輸

數據傳輸丟包恢復示意圖:.Net 的 Reliable Udp 協議的實現

正如在超時關閉連接中討論的那樣,當工作計時器到期時,接收方將檢查丟失的數據包。 在數據包丟失的情況下,將編譯未到達接收方的數據包數量列表。 這些數字被輸入到特定連接的 LostPackets 數組中,並發送重新投遞請求。
發送重新交付包的請求(組裝狀態):

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

發件人將接受重新投遞請求並發送丟失的數據包。 值得注意的是,此時發送方已經啟動了連接關閉定時器,當收到請求時,它會被重置。
重新發送丟失的數據包(SendingCycle 狀態):

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  // ...
  connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
  // сброс таймера закрытия соединения 
  if (connectionRecord.CloseWaitTimer != null)
    connectionRecord.CloseWaitTimer.Change(-1, -1);
  // ...
  // это запрос на повторную передачу – отправляем требуемый пакет          
  else
    ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.RetransmissionCreateUdpPayload(connectionRecord, header.PacketNumber));
}

重新發送的數據包(圖中的數據包 #3)由傳入連接接收。 檢查接收窗口是否已滿並恢復正常的數據傳輸。
檢查接收窗口中的命中(組裝狀態):

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  // ...
  // увеличиваем счетчик пакетов        
  connectionRecord.PacketCounter++;
  // записываем в массив управления окном текущий номер пакета        
  connectionRecord.WindowControlArray[header.PacketNumber - connectionRecord.WindowLowerBound] = header.PacketNumber;
  // устанавливаем наибольший пришедший пакет        
  if (header.PacketNumber > connectionRecord.RcvCurrent)
    connectionRecord.RcvCurrent = header.PacketNumber;
  // перезапускам таймеры        
  connectionRecord.TimerSecondTry = false;
  connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
  if (connectionRecord.CloseWaitTimer != null)
    connectionRecord.CloseWaitTimer.Change(-1, -1);
  // ...
  // если нам пришли все пакеты окна, то сбрасываем счетчик
  // и высылаем пакет подтверждение
  else if (connectionRecord.PacketCounter == connectionRecord.WindowSize)
  {
    // сбрасываем счетчик.      
    connectionRecord.PacketCounter = 0;
    // сдвинули окно передачи
    connectionRecord.WindowLowerBound += connectionRecord.WindowSize;
    // обнуление массива управления передачей
    connectionRecord.WindowControlArray.Nullify();
    ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
  }
  // ...
}

可靠的 UDP API

為了與數據傳輸協議交互,有一個開放的 Reliable Udp 類,它是傳輸控制塊的包裝器。 以下是班級中最重要的成員:

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

通過訂閱接收消息。 回調方法的委託簽名:

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

信息:

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

要訂閱特定消息類型和/或特定發送者,使用兩個可選參數:ReliableUdpMessageTypes messageType 和 IPEndPoint ipEndPoint。

消息類型:

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

消息是異步發送的;為此,協議實現了一個異步編程模型:

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

發送消息的結果將為 true - 如果消息成功到達收件人和 false - 如果連接因超時而關閉:

public bool EndSendMessage(IAsyncResult asyncResult)

結論

本文中沒有描述太多。 線程匹配機制、異常和錯誤處理、異步消息發送方法的實現。 但是協議的核心,處理數據包、建立連接和處理超時的邏輯描述,你應該很清楚。

可靠交付協議的演示版本足夠健壯和靈活,可以滿足先前定義的要求。 但我想補充一點,所描述的實現可以改進。 例如,為了增加吞吐量和動態改變定時器週期,可以在協議中添加滑動窗口和 RTT 等機制,實現連接節點之間確定 MTU 的機制也將很有用(但僅在發送大消息時) .

感謝您的關注,期待您的評論和評價。

PS 對於那些對細節感興趣或只是想測試協議的人,請訪問 Gituhe 上的項目鏈接:
可靠的 UDP 項目

有用的鏈接和文章

  1. TCP協議規範: 用英語 и 俄語
  2. UDP協議規範: 用英語 и 俄語
  3. RUDP協議的討論: 草案-ietf-sigtran-reliable-udp-00
  4. 可靠的數據協議: RFC 908 и RFC 1151
  5. 通過 UDP 發送確認的簡單實現: 使用 .NET 和 UDP 全面控制您的網絡
  6. 描述NAT穿越機制的文章: 跨網絡地址轉換器的點對點通信
  7. 異步編程模型的實現: 實現 CLR 異步編程模型 и 如何實現 IAsyncResult 設計模式
  8. 將異步編程模型移植到基於任務的異步模式(APM in TAP):
    TPL 和傳統 .NET 異步編程
    與其他異步模式和類型的互操作

更新:謝謝 市長 и 錫德里斯蒂 對於在界面中添加任務的想法。 不違反庫與舊操作系統的兼容性,因為第 4 個框架同時支持 XP 和 2003 服務器。

來源: www.habr.com

添加評論