• 基于UDP实现可靠传输


    UDP要想可靠,就要接收方收到UDP之后回复个确认包,发送方有个机制,收不到确认包就要重新发送,每个包有递增的序号,接收方发现中间丢了包就要发重传请求,当网络太差时候频繁丢包,防止越丢包越重传的恶性循环,要有个发送窗口的限制,发送窗口的大小根据网络传输情况调整,调整算法要有一定自适应性。恭喜你, 你在应用层重新实现了TCP!来自知乎-UDP如何实现可靠传输?姚冬的回答
    也就是需要序列号、确认应答、超时重传、以及发送窗口和拥塞窗口,这不就是TCP吗,为什么还要自己实现一遍呢?

    为什么需要用UDP实现可靠传输

    1. TCP建立连接慢,需要三次握手,对于大量的短连接更加不划算
    2. 如果你建立一次通讯只为了传输很少量的一整块数据,那么明显是一种浪费。这也是为什么 google 的 QUIC 对传统的 http over TCP 有改善的空间。
    3. TCP主要慢在拥塞控制上,一是慢启动拥塞窗口初始值太小,二是丢包以后拥塞窗口变得太小,但这有一些新的拥塞控制算法bbr等
    4. RTO翻倍vs不翻倍:
      TCP超时计算是RTOx2,这样连续丢三次包就变成RTOx8了,十分恐怖,而KCP启动快速模式后不x2,只是x1.5(实验证明1.5这个值相对比较好),提高了传输速度。
    5. 选择性重传 vs 全部重传:
      TCP 的重传机制在数据包丢失时可能会重新传输已经成功接收的数据段,造成带宽的浪费;
      TCP丢包时会全部重传从丢的那个包开始以后的数据,KCP是选择性重传,只重传真正丢失的数据包。
    6. 快速重传:
      发送端发送了1,2,3,4,5几个包,然后收到远端的ACK: 1, 3, 3,当第一次收到ACK3时,KCP知道2被跳过1次,收到再ACK3时,知道2被跳过了2次,此时可以认为2号丢失,不用等超时,直接重传2号包,大大改善了丢包时的传输速度。(和TCP类似,只是从3次变成2次)
    7. 延迟ACK vs 非延迟ACK:
      TCP为了充分利用带宽,延迟发送ACK(NODELAY都没用),这样超时计算会算出较大 RTT时间,延长了丢包时的判断过程。KCP的ACK是否延迟发送可以调节。

    Delay Ack
    简单的说,Delay Ack就是延时发送ACK,在收到数据包的时候,会检查是否需要发送ACK,如果需要的话,进行快速ACK还是延时ACK,在无法使用快速确认的条件下,就会使用Delay Ack。

    TCP在何时发送ACK的时候有如下规定:
    1.当有响应数据发送的时候,ACK会随着数据一块发送
    2.如果没有响应数据,ACK就会有一个延迟,以等待是否有响应数据一块发送,但是这个延迟一般在40ms~500ms之间,一般情况下在40ms左右,如果在40ms内有数据发送,那么ACK会随着数据一块发送,对于这个延迟的需要注意一下,这个延迟并不是指的是收到数据到发送ACK的时间延迟,而是内核会启动一个定时器,每隔200ms就会检查一次,比如定时器在0ms启动,200ms到期,180ms的时候data来到,那么200ms的时候没有响应数据,ACK仍然会被发送,这个时候延迟了20ms.
    3.如果在等待发送ACK期间,第二个数据又到了,这时候就要立即发送ACK!

    优点:减少了数据段的个数,提高了发送效率
    缺点:过多的delay会拉长RTT

    1. UNA vs ACK+UNA:
      ARQ模型响应有两种,UNA(此编号前所有包已收到,如TCP)和ACK(该编号包已收到),光用UNA将导致全部重传,光用ACK则丢失成本太高,以往协议都是二选其一,而 KCP协议中,除去单独的 ACK包外,所有包都有UNA信息。

    连续ARQ协议不会响应每个数据段,而是仅仅响应编号最大的这个数据段,表示之前的数据都收到了,这个叫做UNA模式,而停等ARQ协议可以看作是ACK模式。

    1. 非退让流控:
      TCP 的拥塞控制在发生丢包时会进行退让,减少能够发送的数据段数量,但是丢包并不一定意味着网络拥塞,更多的可能是网络状况较差;
      KCP正常模式同TCP一样使用公平退让法则,即发送窗口大小由:发送缓存大小、接收端剩余接收缓存大小、丢包退让及慢启动这四要素决定。但传送及时性要求很高的小数据时,可选择通过配置跳过后两步,仅用前两项来控制发送频率(相当于忽略拥塞窗口)。以牺牲部分公平性及带宽利用率之代价,换取了开着BT都能流畅传输的效果。

    2. 缓存控制:避免缓存积累延迟
      参考kcp-如何避免缓存积累延迟
      当前发送且没有得到 ACK/UNA确认的数据,都会滞留在发送缓存中,一旦滞留数据超过了发送窗口大小限制,则该链接的 tcp send 调用将会 被阻塞,或者返回:EAGAIN / EWOULDBLOCK,这时候说明当前 tcp 信道可用带宽已经赶不上你的发送速度了。
      可用带宽 = min(本地可用发送窗口大小,远端可用接收窗口大小) * (1 - 丢包率) / RTT
      重设窗口大小
      要解决上面的问题首先对你的使用带宽有一个预计,并根据上面的公式重新设置发送窗口和接收窗口大小。你写后端,想追求tcp的性能,也会需要重新设置tcp的 sndbuf, rcvbuf 的大小,KCP 默认发送窗口和接收窗口大小都比较小而已。

    • 不设置的话,如果默认 snd_wnd 太小,网络不是那么顺畅,你越来越多的数据会滞留在 snd_queue里得不到发送,你的延迟会越来越大。
    • 设定了 snd_wnd,远端的 rcv_wnd 也需要相应扩大,并且不小于发送端的 snd_wnd 大小,否则设置没意义。
      其次对于成熟的后端业务,不管用 TCP还是 KCP,你都需要实现相关缓存控制策略:
      缓存控制:传送文件
      你用 tcp传文件的话,当网络没能力了,你的 send调用要不就是阻塞掉,要不就是 EAGAIN,然后需要通过 epoll 检查 EPOLL_OUT事件来决定下次什么时候可以继续发送。
      KCP 也一样,如果 ikcp_waitsnd 超过阈值,比如2倍 snd_wnd,那么停止调用 ikcp_send,ikcp_waitsnd的值降下来,当然期间要保持 ikcp_update 调用。
      同时,如果你能做的更好点,waitsnd 超过阈值了,代表一段时间内网络传输能力下降了,此时你应该动态降低视频质量,减少码率,等网络恢复了你再恢复。
      缓存控制:游戏控制数据
      大部分逻辑严密的 TCP游戏服务器,都是使用无阻塞的 tcp链接配套个 epoll之类的东西,当后端业务向用户发送数据时会追加到用户空间的一块发送缓存,比如 ring buffer 之类,当 epoll 到 EPOLL_OUT 事件时(其实也就是tcp发送缓存有空余了,不会EAGAIN/EWOULDBLOCK的时候),再把 ring buffer 里面暂存的数据使用 send 传递给系统的 SNDBUF,直到再次 EAGAIN。
      那么 TCP SERVER的后端业务持续向客户端发送数据,而客户端又迟迟没能力接收怎么办呢?此时 epoll 会长期不返回 EPOLL_OUT事件,数据会堆积再该用户的 ring buffer 之中,如果堆积越来越多,ring buffer 会自增长的话就会把 server 的内存给耗尽。因此成熟的 tcp 游戏服务器的做法是:当客户端应用层发送缓存(非tcp的sndbuf)中待发送数据超过一定阈值,就断开 TCP链接,因为该用户没有接收能力了,无法持续接收游戏数据。
      使用 KCP 发送游戏数据也一样,当 ikcp_waitsnd 返回值超过一定限度时,你应该断开远端链接,因为他们没有能力接收了。
      但是需要注意的是,KCP的默认窗口都是32,比tcp的默认窗口低很多,实际使用时应提前调大窗口,但是为了公平性也不要无止尽放大(不要超过1024)。
      总结
      缓存积累这个问题,不管是 TCP还是 KCP你都要处理,因为TCP默认窗口比较大,因此可能很多人并没有处理的意识。
      当你碰到缓存延迟时:
    1. 检查 snd_wnd, rcv_wnd 的值是否满足你的要求,根据上面的公式换算,每秒钟要发多少包,当前 snd_wnd满足条件么?
    2. 确认打开了 ikcp_nodelay,让各项加速特性得以运转,并确认 nc参数是否设置,以关闭默认的类 tcp保守流控方式。
    3. 确认 ikcp_update 调用频率是否满足要求(比如10ms一次)。
    4. 当ikcp_waitsnd超过阈值时停止发送,或降低视频码率,或断开连接等,根据不同场景采取不同策略

    参考链接

    1. TCP-IP详解:Delay ACK
    2. 为什么 TCP 协议有性能问题
    3. [https://github.com/skywind3000/kcp]
    4. 基于UDP实现的可靠传输协议(比如uTP),与TCP协议相比有什么优缺点? - 韦易笑的回答 - 知乎
    个性签名:时间会解决一切
  • 相关阅读:
    AcWing 1027. 方格取数 dp
    AcWing 1014. 登山 dp
    acwing 482. 合唱队形 dp
    LeetCode 1463. 摘樱桃II dp
    LeetCode 100. 相同的树 树的遍历
    LeetCode 336. 回文对 哈希
    LeetCode 815. 公交路线 最短路 哈希
    算法问题实战策略 DARPA大挑战 二分
    算法问题实战策略 LUNCHBOX 贪心
    AcWing 1100. 抓住那头牛 BFS
  • 原文地址:https://www.cnblogs.com/lfri/p/15762428.html
Copyright © 2020-2023  润新知