UDP与KCP详解

发布时间 2024-01-12 13:55:20作者: Flamesky

UDP

以及TCP是什么。我们知道传输层中有TCP和UDP两种网络协议,这节就讲UDP是什么。

Internet协议集支持一个无连接的传输协议,该协议称为用户数据报协议(UDP,User Datagram Protocol)。UDP为应用程序提供了一种无需建立连接就可以发送封装的IP数据包的方法。RFC 768描述了UDP。

UDP API

图片来自网络

TCP与UDP的不同

UDP和TCP协议的主要区别是两者在如何实现信息的可靠传递方面不同。TCP协议中包含了专门的传递保证机制,当数据接收方收到发送方传来的信息时,会自动向发送方发出确认消息;发送方只有在接收到该确认消息之后才继续传送其它信息,否则将一直等待直到收到确认信息为止。与TCP不同,UDP协议并不提供数据传送的保证机制。如果在从发送方到接收方的传递过程中出现数据包的丢失,协议本身并不能做出任何检测或提示。因此,通常人们把UDP协议称为不可靠的传输协议。

  • TCP是面向连接的传输控制协议,而UDP提供了无连接的数据报服务;
  • TCP 具有高可靠性,确保传输数据的正确性,不出现丢失或乱序;UDP 在传输数据前不建立连接,不对数据报进行检查与修改,无须等待对方的应答,所以会出现分组丢失、重复、乱序,应用程序需要负责传输可靠性方面的所有工作;
  • UDP具有较好的实时性,工作效率较TCP协议高;
  • UDP段结构比 TCP的段结构简单,因此网络开销也小。
  • TCP协议可以保证接收端毫无差错地接收到发送端发出的字节流,为应用程序提供可靠的通信服务。对可靠性要求高的通信系统往往使用TCP传输数据。

KCP

什么是KCP

TCP优缺点:
我们知道TCP有超时重传和滑动窗口机制提供了TCP的可靠性和流控特性,滑动窗口和拥塞控制可以使得TCP做到流量控制。但是TCP协议是从大局上考虑的,大公无私,经常牺牲自己速度来减少网络拥塞。且TCP高度自治,很多参数没法配置。
UDP优缺点:
UDP协议简单,所以它更快。但是,UDP毕竟是不可靠的,应用层收到的数据可能是缺失、乱序的。

不管是端游还是手游,对于延时的要求都同样重要,因为延时直接关系到游戏的体验。并且不同的游戏对延时的忍受程度不尽相同,例如:FPS延时超过了100ms体验就不怎么好了,Moba在这种程度却觉得很好,MMO甚至到了200ms还可以愉快玩耍,但不管什么游戏,延时越低,体验就越好。
TCP的特性导致网络在不好的时候,延迟会变的很高。UDP的延迟很低,但是不可靠,乱序。

KCP是一种网络传输协议(A Fast and Reliable ARQ Protocol),纯算法实现,并不负责底层协议(如UDP)的收发,需要使用者自己定义下层数据包的发送方式,以callback的方式提供给KCP。连时钟都需要外部传递进来,内部不会有任何一次系统调用。本文传输协议只考虑UDP的情况。
KCP基于UDP协议,在尽可能保留UDP快的前提下,借鉴了TCP的机制来保证可靠。KCP相比TCP浪费了10%~20%的带宽代价,换取了平均延迟降低30%~40%,且最大延迟降低三倍的传输效果。

KCP是自私的,它只顾自己的传输效率,从不管整个网络的拥塞情况。举个例子,TCP检测到丢包的时候,首先想到的是网络拥塞了,要放慢自己的速度别让网络更糟,而KCP想到的赶紧重传别耽误事。

对比KCP和TCP:

  • KCP尽可能保留UDP快的特点下,保证可靠;TCP保证网络绝对的可靠,所以设计复杂,速度慢。
  • KCP是为流速设计的,重点是降低应用网络延迟;TCP是为流量设计的,可以充分利用带宽。
  • KCP是应用层协议,底层是UDP;TCP是传输层协议。

用个比喻来形容就是TCP是条流速较慢但是流量很大的运河,而KCP是条水流湍急的小激流。

KCP协议只有 ikcp.h, ikcp.c两个源文件,可以方便的集成到用户自己的协议栈中。
图片来自网络:

KCP工作流程

KCP的工作流程如下图所示:

 

图中的send_queue是发送队列,send_buf是发送缓冲区,rcv_buf是接收缓冲区,rcv_queue是接收队列。队列和缓冲区的实现都是双向循环列表,通过宏定义来实现。queue和buff的结点就是一个kcp报文。

  • ikcp_update:循环定时调用

发送工作流程:

  • ikcp_send: 将用户待发送的数据填充成KCP报文段,放入snd_queue。
  • ikcp_flush: 调用输出回调将发送缓冲区中的数据发送出去。Ikcp_send不会将数据直接发送出去,只会将报文段放入snd_queue,等待下一次ikcp_update来调用ikcp_flush,将数据从snd_queue中移动到snd_buf后再发送数据。具体哪些报文可以发送,需要通过滑动窗口来控制,避免一次性发送太多的数据导致拥塞。

通过发送工作流程,可以发现发送数据需要等待flush,有发送延迟问题。普遍的解决方案是send之后立即调用一次update,但这样做会带来较大的系统开销。

接收工作流程:

  • ikcp_input: 接收传输层数据,包括用户数据报文段以及ACK报文;接收完后放入rcv_buff,等到接收完一段完整有序的报文之后,才会移动到rcv_queue中供接收方处理。
  • ikcp_recv:从rcv_queue中读取数据,触发接收逻辑。

可靠性

ARQ协议(Automatic Repeat-reQuest),即自动重传请求,是传输层的错误纠正协议之一,它通过使用确认和超时两个机制,在不可靠的网络上实现可靠的信息传输。TCP就是使用的ARQ协议来保证了数据的可靠。
ARQ协议有两种模式:

  • ACK模式(停等ARQ协议),同步请求响应模式,基于超时重传保证可靠。
  • UNA模式(连续ARQ协议),可以连续发送多个分组,而不必每发完一个分组就停下来等待对方确认,TCP就是如此。连续ARQ协议不会响应每个数据段,而是仅仅响应编号最大的这个数据段,表示之前的数据都收到了。

KCP的ARQ:光用UNA如果丢包将导致全部重传,光用ACK则丢失成本太高。以往协议都是二选其一,而KCP有单独ACK包,且数据包和ACK包都带UNA信息,有效降低ACK丢失成本。

确认与重传

KCP 确认重传实现机制:ACK+UNA;超时重传+快速重传;选择重传

  • 在TCP中,有超时重传机制,KCP中设置快速模式,可控制RTO成*1.5
  • 由于KCP的ARQ模式是ACK+UNA,所以KCP除了超时重传还有选择重传机制
  • 选择重传:返回的ACK中包含rcv_nxt和sn。rcv_nxt代表收到的所有连续的包,sn代表哪些不连续的包收到了,那么根据这两个参数可以计算出来没有收到的包的序号。发送方接收到接收方发过来的数据时,首先解析rcv_nxt,把所有小于rcv_nxt序号的包从发送缓存队列中移除。然后再解析sn(大于rcv_nxt),遍历发送缓存队列,找到所有序号小于sn的包,就是被选择重传的包。
  • 快速重传:根据我们设置的快速重传的门限,对每个分片维护一个快速重传的计数。每收到一个ACK解析sn后找到了一个分片,就把该分片的快速重传的计数加一。如果该计数达到了快速重传门限,那么就认为该分片已经丢失,可以触发快速重传。
  • KCP在发生快速重传且数据包乱序时,采用的是TCP快恢复的策略。控制窗口调整为已经发送没有接收到ack的数据包数目的一半+resent。

流量控制

流量控制有两机制:滑动窗口和拥塞控制。

滑动窗口的接收方告知发送方自己可以接收缓冲区的大小,通常与连续ARQ协议配合使用。滑动窗口就是TCP头部的16位大小窗口字段,而UDP没有这个字段。

拥塞控制的关键是四个算法:慢开始、拥塞避免、快速重传、快速恢复。

KCP滑动窗口:
滑动窗口默认大小为32,即可以接收最大为32*MTU=43.75kB。KCP采用update的方式,更新间隔为10ms,那么KCP限定了你最大传输速率为4375kB/s,在高网速传输大内容的情况下需要调用ikcp_wndsize调整接收与发送窗口。建议从应用侧更好的控制发送流量与网络速度持平,避免缓存堆积延迟。

KCP拥塞控制:
KCP的拥塞控制可以关闭。KCP的优势在于可以完全关闭拥塞控制,非常自私的进行发送。KCP采用的拥塞控制策略为TCP最古老的策略,无任何优势。完全关闭拥塞控制,也不是一个最优策略,它全是会造成更为拥堵的情况。

KCP VS TCP

  • TCP的RTO是直接翻倍,但是试验测试发现RTO*=1.5比RTO*=2要好,所以KCP选择了RTO*=1.5,提高了丢包时的传输速度。
  • 快速重传:发送端发送了1,2,3,4,5几个包,然后收到远端的ACK: 1, 3, 4, 5,当收到ACK3时,KCP知道2被跳过1次,收到ACK4时,知道2被跳过了2次,此时可以认为2号丢失,不用等超时,直接重传2号包,大大改善了丢包时的传输速度。
  • 延迟ACK vs 非延迟ACK :TCP为了充分利用带宽,延迟发送ACK(NODELAY都没用),这样超时计算会算出较大RTT时间,延长了丢包时的判断过程。KCP的ACK是否延迟发送可以调节。
  • UNA vs ACK+UNA :TCP使用的UNA模式,有丢包全部重传问题;KCP有单独ACK,且数据包和ACK包都带UNA信息,有效降低ACK丢失成本。
  • TCP丢包时会全部重传从丢的那个包开始以后的数据;由于KCP使用了ACK+UNA模式,KCP是选择性重传,只重传真正丢失的数据包。
  • 非退让流控:KCP正常模式同TCP一样使用公平退让法则,即发送窗口大小由:发送缓存大小、接收端剩余接收缓存大小、丢包退让及慢启动这四要素决定。但传送及时性要求很高的小数据时,可选择通过配置跳过后两步,仅用前两项来控制发送频率。以牺牲部分公平性及带宽利用率之代价,换取了开着BT都能流畅传输的效果。(KCP非退让流控可以理解为对及时性要求很高的小数据只考虑滑动窗口,看你发送窗口和接收窗口是否允许我传输

其他

  • 当用户数据很大,大于一个UDP包能承担的范围时(大于mss),KCP会将用户数据分片存储在多个KCP包中。因此每个KCP包称为一个分片。
  • 用户控制:拥塞控制可以取消、ACK回复可以设置成无延迟ACK回复、KCP的RTO可以控制成*1.5
  • KCP只是一套基于无连接的数据报文之上的连接和拥塞控制协议,对底层无连接的数据报文没有具体的限制,可以基于UDP,也可以基于伪造的 TCP/ICMP等,也可以基于某些特殊环境的非internet网络(比如各种现场通信总线)