UE 可靠UDP实现原理

发送

我们的消息发送都是通过 UChannel 来处理的,通过调用 UChannel::SendBunch 统一处理。 发送的 Bunch 是以 FOutBunch 的形式存在的。当 bReliable 为 True 的时候,表示 Bunch 是可靠的。

发送逻辑直接从UChannel::SendBunch处开始分析

1、大小限制

UE的Bunch大小有限制, 最大Bunch为64K,SendBunch 的时候会去判断当前 Bunch 的大小是否超出限制。
UE 可靠UDP实现原理_第1张图片

2、Bunch处理(合并小Bunch,拆分大Bunch)

同一个Channel通道,可靠性一样,合并后没有超过单个 Bunch 的限制,可以合并为一个 Bunch。
如果是Actor初始化的包,需要同步CDO信息, 就不能合并。
UE 可靠UDP实现原理_第2张图片
同理,如果Bunch过大就会拆分,这些被拆分的 Bunch 的 bPartial 字段为1,表示这只是一个包的片段, 收包的时候会根据这个字段进行组包,拆分后的第一个 Bunch 的bPartialInitial 值为1,表示这是拆分后的第一个包, 最后一个包的 bPartialFinal 为 1。
UE 可靠UDP实现原理_第3张图片
UE 可靠UDP实现原理_第4张图片

3、可靠Bunch数量限制

如果设置了可靠 Bunch 上限 GCVarNetPartialBunchReliableThreshold,当本次要发送的 OutgoingBunches 的数量和之前 没收到Ack包的数量 超过阈值时,会暂停复制,直到收到了所有可靠消息的 Ack;
当可靠列表溢出的时候,连接会关闭。NumOutRec 为当前可靠的 Bunch 的数量,所以可靠 Bunch 的数量最多256个。
UE 可靠UDP实现原理_第5张图片

4、可靠 Bunch 预处理

在PrepBunch中对可靠包进行预处理

  • OutReliable 保存着每个 Channel 的可靠 Bunch 数量,会去初始化 Bunch 的通道序列号 ChSequence,可以看出每个通道的可靠 Bunch 序列号是递增的。
  • 每次发送一个可靠包时 NumOutRec(当前未收到Ack的可靠包数量) 会+1
  • 将Bunch加入到 OutRec(发送的未确认的可靠消息数据)中,用于收到Nak后重传。只保存可靠的 Bunch。
    UE 可靠UDP实现原理_第6张图片

5、UChannel::SendRawBunch

将ReceivedAck标记置为0
UE 可靠UDP实现原理_第7张图片

6、UNetConnection::SendRawBunch

设置 TimeSensitive 为1,先判断SendBuffer(存储BunchHeader和Bunch数据)是否可以装的下这次的Bunch+Header,如果装不下会调用 FlushNet 立即发送出去。然后将Header+Bunch写入SendBuffer中。
UE 可靠UDP实现原理_第8张图片

7、UNetConnection::Tick

如果有敏感标记TimeSensitive或者超时的时候会直接发送
UE 可靠UDP实现原理_第9张图片

8、UNetConnection::FlushNet

重置 TimeSensitive ,并且发送Packet。
UE 可靠UDP实现原理_第10张图片

接收

1、UNetConnection::ReceivedPacket

这一步进行了丢包检测。读取数据包头信息,并根据包头携带的序列号信息和最后一个成功接收到的序列号去判断序列号的增量,正常情况下,所有数据包都会按发出的顺序接收,所有增量会相差1。如果小于1,说明接收到的数据包发生了失序,引擎发送的每一个数据包序列号都是唯一的,不会重用,这种情况下引擎会忽略无效的数据包。
UE 可靠UDP实现原理_第11张图片
UE 可靠UDP实现原理_第12张图片

如果大于1,说明发生了丢包,不会立即处理当前的数据,会把当前的数据包加入队列 PacketOrderCache 中,等待收到差值为1的包再一起处理。
UE 可靠UDP实现原理_第13张图片

2、解析数据包头

然后调用PacketNotify.Update
UE 可靠UDP实现原理_第14张图片

每个到来的数据包都需要到 PacketNotify 中更新序列号信息。
1、根据包头携带的序列号数据计算出当前确认的序列号数量,然后根据 AckRecord 去更新 InAckSeqAck
2、如果超出数量上限 SequenceHistoryT::Size = 256,则视为收到 Nak
3、从序列号历史记录(History Storage)中判断是 Ack 还是 Nak,然后调用对应的处理函数
UE 可靠UDP实现原理_第15张图片

3、ReceivedAck

当接收到 Ack 的时候,会对当前确认的包 id 相同的 bunch 修改标志位 ReceivedAck,并且从 OutRec 列表中删除已确认的消息 bunch。
UE 可靠UDP实现原理_第16张图片

4、ReceivedNak

当我们发送一个可靠的 Bunch 的时候,会把它添加到 OutRec 中,这是一个已发送的未确认的可靠消息列表。当接收到 Nak 的时候,会为每个通道的包 id 为 NakPacketId 的未确认的可靠数据重新发送一次。丢包发生的时候,只会按 Bunch 去重新发送,Bunch 序列号还是原来的 Channel 序列号,而之前的 Packet 是不会重用的,只会生成新的 Packet,以及最新的 PacketId。意味着不会重新发送之前发送的数据包,也不会重用数据包序列号,数据包的发送每一次都是新生成的数据包,数据包序列号都是递增的,不会重复。
1、由于 OutRec 只保存了可靠的数据包,如果是不可靠的消息发生了丢包,引擎是不会重新发送它们的。
2、这里保存的是 RawBunch,如果 Bunch 是拆分的,丢弃了一部分,会导致整个 Bunch 的重新发送。
UE 可靠UDP实现原理_第17张图片

5、UChannel::ReceivedRawBunch

1、如果是可靠的消息,但是通道序列号不是有序的,则放入接收可靠消息列表 InRec 中,并按通道序列号 ChSequence 顺序存储,同样的,接收的可靠消息列表数量 NumInRec 一样不能超过可靠缓冲区大小256(RELIABLE_BUFFER)。
2、调用 ReceivedNextBunch 接收完之后,会再处理之前缓存的可靠消息列表 InRec,按顺序处理。

6、发送Ack、Nak消息

当收到一份数据的时候,我们会对数据进行确认,会回复 Ack 或者 Nak,写入到序列号历史记录中,由于历史记录最多 256 位,所以当 Ack 累计超过之后,会调用 FlushNet 立即发送。同时改变敏感标志位 TimeSensitive。
UE 可靠UDP实现原理_第18张图片
UE 可靠UDP实现原理_第19张图片

你可能感兴趣的:(udp,ue5)