及时性的通用解决方案-可靠UDP
经过前两篇文章的学习,我们已经对网络同步中实现一致性的两种方式——帧同步和状态同步有了一定了解。现在我们需要考虑一下“及时性”,为同步算法选择一个恰当的网络协议了。传输层有两个协议:TCP
和 UDP
。在能保证 UDP 传输可靠性的前提下,我们可以有“可靠UDP(RUDP)”。那么,该选择 TCP 还是 RUDP 呢?
可以看到,在不同的网络环境下,RUDP 的平均延迟都是要好于 TCP 的,这是为什么呢?
- TCP 虽然以稳定著称,但因为 TCP 的 Nagle 算法,默认情况下会收集尽量多的小包,然后再一次性发送。这样虽然这样可以减少带宽,但会导致及时性变差(不过我们可以通过
TCP_NODELAY
选项去关闭这个功能)。 - TCP 具有超时重传机制。当客户端或服务端有一帧的包没有收到的话,直到这个包被重传到端之前,游戏的逻辑是无法正常执行的。即使使用 TCP 的快重传机制(即收到三个重复ACK时立马重传),产生的时延相比UDP来说还是太久了。
- 当出现丢包之后,TCP 的拥塞控制机制会触发慢启动和拥塞避免,这会限制发包的速度。
因此在实际开发的过程中,一般选择 RUDP 作为传输层协议。那么,应该怎么实现可靠UDP呢?
基于ARQ(自动重传请求)
首先,我们需要了解以下几个概念:
- RTT(Round Trip Time)
RTT指的是从客户端发送数据包到客户端收到服务端返回确认数据包(ACK包)的时间间隔,这段时间被称为往返时间(RTT)。
- RTO(Retransmission Timeout)
RTO指的是超时重传时间,即在客户端发送数据包后,在RTO时间内未收到服务端的ACK包,就会重新发送一个数据包过去。一般来说,RTO = 1.5RTT。
- 最小丢包延时
当丢包发生时,接收方最终收到发送的数据包的最小延时(也就是RTO后发送的包到达接收方的时间)。一般来说,最小丢包延时 = 2RTT。
基于上面的三个概念,我们可以发展出以下三种实现RUDP的方式:
- 等待式
每次客户端发送一个包,都要等到收到服务端的ACK包后才发送下一个。这种实现模式对信道的浪费比较严重,大部分时间信道都很空闲。
- 后退N步
客户端每次发一堆包,收到服务器的一堆ACK包后检查有没有哪个漏了,有的话从漏掉的包开始重新发送(服务器会丢弃缺漏包后的所有包)。这种模式对流量的占用较大,导致信道繁忙。
- 选择重发
和后退N步类似,但只选择性地重发未收到ACK包的包,服务器不会丢弃其他包。
在ARQ中,已经有了大量现成的解决方案(如KCP),但其 最小丢包时延较高。关于KCP有篇很好的文章:https://luyuhuang.tech/2020/12/09/kcp.html#发送-接收与重传
1 | KCP报文结构: |
- conv 4 字节: 连接标识.
- cmd 1 字节: Command.
- frg 1 字节: 分片数量,表示随后还有多少个报文属于同一个包.
- wnd 2 字节: 发送方剩余接收窗口的大小,有助于对端控制发送速率.
- ts 4 字节: 时间戳,可以用来估算 RTT 和 RTO.
- sn 4 字节: 报文编号.
- una 4 字节: 发送方的接收缓冲区中最小还未收到的报文段的编号. 也就是说, 编号比它小的报文段都已全部接收.
- len 4 字节: 数据段长度.
- data: 数据段. 只有数据报文会有这个字段
基于FEC(前向冗余纠错)
在FEC中,最小丢包延迟比ARQ的小,但它只适用于帧同步中,并且实现算法比较复杂、没有现成的实现方案(需要自己造轮子)。
丢包时应该怎么办?
丢包分为两种:相关性丢包和随机性丢包。
- 相关性丢包
这种丢包通常是由链路拥塞、路由器负载过高、无线信号衰减、基站/场景切换等程序员无法干预的情况导致的。我们知道丢包的原因,但无法解决。 - 随机性丢包
这种丢包通常是由于二进制信道噪声产生的,完全是随机出现的,将近90%的丢包都属于这种类型。
基于吉尔伯特模型,我们可以计算出连续K次丢包的概率(随着k的增加,概率是会越来越低的)。因此,为了避免随机性丢包,我们可以连发2次同一个包(如果2次不够就发送3次)。
UDP包与MTU
由于网络信道存在MTU的概念,当一个UDP包的大小超过MTU时,打包就会被拆成IP分片小包,在收到时再重组成UDP大包。
但分成小包进行传输,其实是很容易遇到丢包的情况的。一旦IP分片中有一个包丢失了,系统就会丢弃整个UDP大包。
因此,我们有时候需要人为地将一个UDP大包拆成小包发送,并留意信道的MTU大小。经验MTU大小为470字节。
参考资料
https://gameinstitute.qq.com/course/detail/10242
https://www.youxituoluo.com/528021.html
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 几/何/冰/川!
评论