.Net 的可靠 Udp 协议的实现

互联网很久以前就已经发生了变化。 UDP 是互联网的主要协议之一,应用程序不仅使用它来传送数据报和广播,而且还提供网络节点之间的“点对点”连接。 由于其简单的设计,该协议有许多以前未计划的用途,但是该协议的缺点(例如缺乏保证交付)并没有消失。 本文介绍了基于 UDP 的保证传送协议的实现。
内容:输入
协议要求
可靠的 UDP 标头
协议的一般原则
超时和协议计时器
可靠UDP传输状态图
更深入地了解代码。 传输控制单元
更深入地了解代码。 状态

更深入地了解代码。 创建和建立连接
更深入地了解代码。 超时关闭连接
更深入地了解代码。 恢复数据传输
可靠的UDP API
结论
有用的链接和文章

输入

互联网的原始架构假定了一个同质的地址空间,其中每个节点都有一个全局且唯一的IP地址,并且可以直接与其他节点通信。 事实上,现在的互联网具有不同的架构 - 一个全球 IP 地址区域和许多隐藏在 NAT 设备后面的私有地址区域。在这种架构中,只有全局地址空间中的设备才能轻松地与网络上的任何人进行通信,因为它们具有唯一的、全局可路由的 IP 地址。 私有网络上的节点可以连接到同一网络上的其他节点,也可以连接到全局地址空间中的其他已知节点。 这种交互的实现很大程度上得益于网络地址转换机制。 NAT 设备(例如 Wi-Fi 路由器)为传出连接创建特殊的转换表条目,并修改数据包中的 IP 地址和端口号。 这允许从专用网络到全局地址空间中的主机的传出连接。 但与此同时,NAT 设备通常会阻止所有传入流量,除非为传入连接设置单独的规则。

互联网的这种架构对于客户端-服务器通信来说足够正确,其中客户端可以位于专用网络中,而服务器具有全局地址。 但这给两个节点之间的直接连接带来了困难 不同 专用网络。 两个节点之间的直接连接对于语音传输 (Skype)、远程访问计算机 (TeamViewer) 或在线游戏等点对点应用程序非常重要。

在不同专用网络上的设备之间建立对等连接的最有效方法之一称为打洞。 此技术最常用于基于 UDP 协议的应用程序。

但是,如果您的应用程序需要有保证的数据传送,例如您在计算机之间传输文件,那么使用 UDP 将会遇到很多困难,因为 UDP 不是有保证的传送协议,并且与 TCP 不同,它不提供数据包按顺序传送协议。

在这种情况下,为了确保有保证的数据包传送,需要实现一个应用层协议,该协议提供必要的功能并通过 UDP 工作。

我想立即指出,有一种 TCP 打洞技术用于在不同专用网络的节点之间建立 TCP 连接,但由于许多 NAT 设备缺乏对它的支持,因此通常不将其视为主要连接方式这样的节点。

在本文的其余部分中,我将仅关注保证交付协议的实现。 UDP打洞技术的实现将在后面的文章中介绍。

协议要求

  1. 通过正反馈机制(即所谓的正确认)实现可靠的数据包传送
  2. 大数据高效传输的需求,即协议必须避免不必要的数据包中继
  3. 应该可以取消传送确认机制(作为“纯”UDP协议发挥作用的能力)
  4. 能够实现命令模式,并确认每条消息
  5. 协议上数据传输的基本单位必须是消息

这些要求在很大程度上与可靠数据协议中描述的要求一致 射频908 и 射频1151,我在开发这个协议时依赖于这些标准。

为了理解这些要求,让我们看看使用 TCP 和 UDP 协议的两个网络节点之间的数据传输时序。 在这两种情况下我们都会丢失一个数据包。
通过 TCP 传输非交互式数据:.Net 的可靠 Udp 协议的实现

从图中可以看出,如果发生丢包,TCP 会检测到丢失的数据包,并通过询问丢失的报文段的编号来报告给发送方。
通过UDP协议传输数据:.Net 的可靠 Udp 协议的实现

UDP 不采取任何丢失检测步骤。 UDP 协议中传输错误的控制完全由应用程序负责。

TCP 协议中的错误检测是通过与端节点建立连接、存储该连接的状态、指示每个数据包标头中发送的字节数以及使用确认号通知接收来实现的。

此外,为了提高性能(即发送多个数据段而不收到确认),TCP 协议使用所谓的传输窗口 - 数据段发送方期望接收的数据字节数。

有关 TCP 协议的更多信息,请参见 射频793,从 UDP 到 射频768事实上,它们是在哪里定义的。

由上可知,为了创建一个可靠的基于UDP的消息传递协议(以下简称UDP) 可靠的UDP),需要实现类似TCP的数据传输机制。 即:

  • 保存连接状态
  • 使用段编号
  • 使用特殊的确认包
  • 使用简化的窗口机制来提高协议吞吐量

此外,您还需要:

  • 发出消息开始信号,为连接分配资源
  • 发出消息结束信号,将收到的消息传递给上游应用程序并释放协议资源
  • 允许特定于连接的协议禁用传递确认机制以充当“纯”UDP

可靠的 UDP 标头

回想一下,UDP 数据报封装在 IP 数据报中。 可靠 UDP 数据包被适当地“包装”到 UDP 数据报中。
可靠的UDP报头封装:.Net 的可靠 Udp 协议的实现

可靠 UDP 标头的结构非常简单:

.Net 的可靠 Udp 协议的实现

  • Flags - 包控制标志
  • MessageType - 上游应用程序用于订阅特定消息的消息类型
  • TransmissionId - 传输的编号,与接收方的地址和端口一起唯一标识连接
  • PacketNumber - 数据包编号
  • 选项 - 附加协议选项。 对于第一个数据包,它用于指示消息的大小

标志如下:

  • FirstPacket - 消息的第一个数据包
  • NoAsk - 消息不需要启用确认机制
  • LastPacket - 消息的最后一个数据包
  • RequestForPacket - 确认数据包或请求丢失的数据包

协议的一般原则

由于可靠UDP专注于保证两个节点之间的消息传输,因此它必须能够与另一方建立连接。 为了建立连接,发送方发送带有 FirstPacket 标志的数据包,对该数据包的响应将意味着连接已建立。 所有响应数据包,或者换句话说,确认数据包,始终将 PacketNumber 字段的值设置为比成功接收的数据包的最大 PacketNumber 值大 XNUMX。 发送的第一个数据包的选项字段是消息的大小。

类似的机制用于终止连接。 LastPacket 标志设置在消息的最后一个数据包上。 在响应报文中,指示了最后一个报文的编号+1,这对于接收方来说意味着消息发送成功。
连接建立和终止示意图:.Net 的可靠 Udp 协议的实现

连接建立后,数据传输开始。 数据以数据包块的形式传输。 除最后一个块外,每个块都包含固定数量的数据包。 它等于接收/发送窗口大小。 最后一个数据块可能具有较少的数据包。 发送每个块后,发送方等待传送确认或重新传送丢失数据包的请求,使接收/发送窗口保持打开状态以接收响应。 收到块传送确认后,接收/发送窗口移动并发送下一个数据块。

接收端接收数据包。 检查每个数据包以查看其是否落入传输窗口内。 未落入窗口的数据包和重复数据包将被过滤掉。 因为如果窗口的大小是固定的,并且对于接收方和发送方来说是相同的,那么在一个数据包被无丢失地传送的情况下,窗口被移动以接收下一个数据块的数据包,并且传送确认是发送。 如果在工作定时器设置的时间内窗口未填满,则将开始检查哪些数据包尚未发送,并发送重新发送请求。
重传图:.Net 的可靠 Udp 协议的实现

超时和协议计时器

无法建立连接的原因有多种。 例如,如果接收方离线。 在这种情况下,当尝试建立连接时,连接将因超时而关闭。 可靠 UDP 实现使用两个计时器来设置超时。 第一个是工作计时器,用于等待远程主机的响应。 如果它在发送方触发,则重新发送最后发送的数据包。 如果接收方的计时器到期,则会检查丢失的数据包并发送重新传送请求。

如果节点之间缺乏通信,则需要第二个计时器来关闭连接。 对于发送端来说,工作定时器超时后立即启动,等待远端节点的响应。 如果在指定时间内没有响应,则连接终止并释放资源。 对于接收端,连接关闭定时器在工作定时器两次超时后启动。 这是为了防止确认数据包丢失所必需的。 当定时器到期时,连接也被终止并释放资源。

可靠UDP传输状态图

该协议的原理是在有限状态机中实现的,每个状态机负责一定的数据包处理逻辑。
可靠的UDP状态图:

.Net 的可靠 Udp 协议的实现

闭馆 - 并不是真正的状态,它是自动机的起点和终点。 对于国家 闭馆 接收到传输控制块,该传输控制块实现异步 UDP 服务器,将数据包转发到适当的连接并开始状态处理。

首包发送 – 发送消息时传出连接的初始状态。

在此状态下,发送正常消息的第一个数据包。 对于没有发送确认的消息,这是发送整个消息的唯一状态。

发送周期 – 用于传输消息包的基态。

从国家过渡到它 首包发送 在发送消息的第一个数据包后执行。 所有确认和重传请求都是在这种状态下发出的。 在两种情况下可以退出 - 成功传递消息或超时。

收到第一个数据包 – 消息接收者的初始状态。

它检查传输开始的正确性,创建必要的结构,并发送第一个数据包收到的确认。

对于由单个数据包组成且在未使用传递证明的情况下发送的消息,这是唯一的状态。 处理完此类消息后,连接将关闭。

组装 – 接收消息包的基本状态。

它将数据包写入临时存储,检查数据包丢失,发送数据包块和整个消息传送的确认,并发送重新传送丢失数据包的请求。 如果成功接收到整个消息,连接将进入状态 完成,否则超时。

完成 – 如果成功收到整个消息,则关闭连接。

此状态对于消息的组装以及消息的传递确认在发往发送者的途中丢失的情况是必需的。 该状态因超时而退出,但连接被视为成功关闭。

更深入地了解代码。 传输控制单元

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

更深入地了解代码。 状态

状态实现可靠 UDP 协议的状态机,其中进行数据包的主要处理。 抽象类 ReliableUdpState 提供了状态接口:

.Net 的可靠 Udp 协议的实现

协议的整个逻辑由上面提供的类以及提供静态方法的辅助类一起实现,例如从连接记录构造 ReliableUdp 标头。

接下来,我们将详细考虑决定协议基本算法的接口方法的实现。

通过超时处理方法

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:

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

状况良好 发送周期 该方法仅在计时器上调用,并负责重新发送最后一条消息,以及启用连接关闭计时器。
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);
}

状况良好 完成 该方法停止正在运行的计时器并将消息发送给订阅者。
已完成.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);
  }
}

状况良好 发送周期 重写此方法以接受传送确认和重传请求。
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));
}

状况良好 组装 在 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);
  }
}

状况良好 完成 该方法的唯一任务是发送消息成功传递的重新确认。
已完成。接收数据包:

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

状况良好 发送周期 在此方法中,发送一个数据包块。
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 );
  }
}

更深入地了解代码。 创建和建立连接

现在我们已经了解了基本状态和用于处理状态的方法,让我们更详细地分解一些示例来说明该协议如何工作。
正常情况下数据传输图:.Net 的可靠 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 的可靠 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 变量设置为 true。 该变量负责重新启动工作计时器。

在发送方,工作定时器也会被触发,并重新发送上次发送的数据包。
启用连接关闭计时器(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 的可靠 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 对于那些对细节感兴趣或只是想测试协议的人,GitHube 上该项目的链接:
可靠的UDP项目

有用的链接和文章

  1. TCP协议规范: 英文 и нарусском
  2. UDP协议规范: 英文 и нарусском
  3. RUDP协议讨论: 草案-ietf-sigtran-reliable-udp-00
  4. 可靠的数据协议: 射频908 и 射频1151
  5. 通过 UDP 发送确认的简单实现: 使用 .NET 和 UDP 完全控制您的网络
  6. 描述NAT穿越机制的文章: 跨网络地址转换器的点对点通信
  7. 异步编程模型的实现: 实现 CLR 异步编程模型 и 如何实现 IAsyncResult 设计模式
  8. 将异步编程模型移植到基于任务的异步模式(TAP 中的 APM):
    TPL 和传统的 .NET 异步编程
    与其他异步模式和类型的互操作

更新:谢谢 马约罗夫 и 西德里斯蒂 对于在界面上添加任务的想法。 不违反该库与旧操作系统的兼容性,因为第四个框架支持XP和4服务器。

来源: habr.com

添加评论