行文前先安利下《再深谈TCP/IP三步握手&四步挥手原理及衍生问题—长文解剖IP 》、《再谈UDP协议—浅入理解深度记忆》
KCP是一个快速可靠协议,能以比 TCP浪费10%-20%的带宽的代价,换取平均延迟降低 30%-40%,且最大延迟降低三倍的传输效果。
纯算法实现,并不负责底层协议(如UDP)的收发,需要使用者自己定义下层数据包的发送方式,以 callback的方式提供给 KCP。 连时钟都需要外部传递进来,内部不会有任何一次系统调用。本文传输协议之考虑UDP的情况。
整个KCP协议主要依靠一个循环ikcp_update来驱动整个算法的运转,所有的数据发送,接收,状态变化都依赖于此,所以如果有操作占用每一次update的周期过长,或者设置内部刷新的时间间隔过大,都会导致整个算法的效率降低。在ikcp_update中最终调用的是ikcp_flush,这是协议中的一个核心函数,将数据,确认包,以及窗口探测和应答发送到对端。
KCP使用ikcp_send发送数据,该函数调用ikcp_output发送数据,实际上最终调用事先注册的发送回调发送数据。KCP通过ikcp_recv将数据接收出来,如果被分片发送,将在此自动重组,数据将与发送前保持一致。
首先要看TCP与UDP的区别,TCP与UDP都是传输层的协议,比较两者的区别主要应该是说TCP比UDP多了什么?
随着网络技术飞速发展,网速已不再是传输的瓶颈,CDN服务商Akamai报告从2008年到2015年7年时间,各个国家网络平均速率由1.5Mbps提升为5.1Mbps,网速提升近4倍。网络环境变好,网络传输的延迟、稳定性也随之改善,UDP的丢包率低于5%,如果再使用应用层重传,能够完全确保传输的可靠性。
KCP协议就是在保留UDP快的基础上,提供可靠的传输,应用层使用更加简单——TCP可靠简单,但是复杂无私,所以速度慢。KCP尽可能保留UDP快的特点下,保证可靠。
TCP信道是一条流速很慢,但每秒流量很大的大运河,而KCP是水流湍急的小激流。
MOBA类和“吃鸡”游戏多使用帧同步为主要同步算法,竞技性也较高,无论从流畅性,还是从公平性要求来说,对响应延迟的要求都最高,根据业内经验,当客户端与服务器的网络延迟超过150ms时,会开始出现卡顿,当延迟超过250ms时,会对玩家操作造成较大影响,游戏无法公平进行。类似地,“吃鸡”游戏(如《绝地求生》)玩法对玩家坐标、动作的同步要求极高,延迟稍大导致的数据不一致对体验都会造成较大影响,其实时性要求接近MOBA类游戏。而对于传统mmorpg来说,多采用状态同步算法,以属性养成和装备获取为关注点,也有一定竞技性,出于对游戏流畅性的要求,对延迟也有一定要求,同步算法的优化程度不一样,这一要求也不一样,一般情况下为保证游戏正常进行,需要响应延迟保持在300ms以下。相比之下,对于炉石传说、斗地主、梦幻西游等回合制游戏来说,同时只有一个玩家在操作双方数据,无数据竞争,且时间粒度较粗,甚至可通过特效掩盖延迟,因此对网络延迟的要求不高,即便延迟达到500ms~1000ms,游戏也能正常进行
不同传输层协议在可靠性、流量控制等方面都有差别,而这些技术细节会对延迟造成影响。
tcp追求的是完全可靠性和顺序性,丢包后会持续重传直至该包被确认,否则后续包也不会被上层接收,且重传采用指数避让策略,决定重传时间间隔的RTO(retransmission timeout)不可控制,linux内核实现中最低值为200ms,这样的机制会导致丢包率短暂升高的情况下应用层消息响应延迟急剧提高,并不适合实时性高、网络环境复杂的游戏。
基于udp定制传输层协议,引入顺序性和适当程度或者可调节程度的可靠性,修改流控算法。适当放弃重传,如:设置最大重传次数,即使重传失败,也不需要重新建立连接。比较知名的tcp加速开源方案有:quic、enet、kcp、udt。
先安利下《浅谈QUIC协议原理与性能分析及部署方案》,
其实kcp不能和quic对比(quic vs enet),只是讲到UDP的时候,顺带搭上QUIC协议,类似的还有WebRTC
为什么采用UDP,而不是其他的协议呢?比如SCTP天生就具备TCP/UDP所不具备的各种优点(支持多宿主多流分帧可无序抗syn flooding),但是就比如Windows系统,各种路由器、网关都不支持,无法铺开(除非在私有网络或者专用网络中用)。况且,TCP/UDP的各种问题很多都已经通过技术或技巧给解决了。
在网络中,我们认为传输是不可靠的,而在很多场景下我们需要的是可靠的数据,所谓的可靠,指的是数据能够正常收到,且能够顺序收到,于是就有了ARQ协议,TCP之所以可靠就是基于此。
ARQ协议(Automatic Repeat-reQuest),即自动重传请求,是传输层的错误纠正协议之一,它通过使用确认和超时两个机制,在不可靠的网络上实现可靠的信息传输。
同步请求响应模式,基于超时重传保证可靠。
所以可靠的TCP有32位序列号和32位确认号,TCP和UDP都有16位校验和。
可以连续发送多个分组,而不必每发完一个分组就停下来等待对方确认。
是不是想到了HTTP1.1中的管道模式与HTTP1.0停等模式,但这里有些许区别,HTTP1.1是中服务器按照顺序响应客户端请求,但连续ARQ协议不会响应每个数据段,而是仅仅响应编号最大的这个数据段,表示之前的数据都收到了,这个叫做UNA模式,而停等ARQ协议可以看作是ACK模式。
现在已经能够在不可靠的网络中传输可靠的数据,但这不意味着可以随意发送数据,带宽是有限的,接收方的负载也是有限的,所以引入了窗口协议,做流量控制。
防止过多的数据注入到网络中,这样可以使网络中的路由器 和链路不至于过载。
与拥塞控制相关的有慢启动、退半避让、快重传、快恢复等。
慢启动是在刚开始发送数据时让窗口缓慢扩张,退半避让是在网络拥堵时窗口大小减半,快重传是在网络恢复时及时给予响应,与之配合的就是快恢复。
接收方告知发送方自己可以接收缓冲区的大小,通常与连续ARQ协议配合使用。
TCP协议中的16位窗口大小就是为窗口协议提供支持的。而UDP协议的目标是尽最大努力交付,不管你收到没有,所以没有该字段。
TCP协议是面向连接的协议,在数据传输前通过三次握手建立连接,传输完成后通过四次挥手断开连接,整个过程表示一次完整的数据传输,所以需要4位头长告知哪些是正在传输的数据。
UDP协议是无连接的,两次数据传输没有任何联系,所以需要16位长度告知本次传输的数据有多少。同时注意,UDP协议每次传输的数据量并不是2^16 - 1 - 8 - 20(8表示UDP头长,20表示IP头长),而是与MTU有关,即数据链路层的最大传输单元(Maximum Transmission Unit),值是1500。
TCP协议中的8位标志位表示不同的功能,例如当SYN = 1时表示建立连接时让ack = seq + 1而不做任何验证,当URG = 1时16位紧急指针生效,紧急指针表示正常数据的起始位置,而之前的数据则表示额外的紧要数据,可以被尽快处理。
当清楚TCP和UDP的工作流程,KCP就很容易理解了。
KCP协议默认模式是一个标准的 ARQ,需要通过配置打开各项加速开关:
int ikcp_nodelay(ikcpcb *kcp, int nodelay, int interval, int resend, int nc)
KCP有正常模式和快速模式两种,通过以下策略达到提高流速的结果:
int ikcp_wndsize(ikcpcb *kcp, int sndwnd, int rcvwnd);
该调用将会设置协议的最大发送窗口和最大接收窗口大小,默认为32. 这个可以理解为 TCP的 SND_BUF 和 RCV_BUF,只不过单位不一样 SND/RCV_BUF 单位是字节,这个单位是包。
纯算法协议并不负责探测 MTU,默认 mtu是1400字节,可以使用ikcp_setmtu来设置该值。该值将会影响数据包归并及分片时候的最大传输单元。
TCP超时计算是RTOx2,这样连续丢三次包就变成RTOx8了,十分恐怖,而KCP启动快速模式后不x2,只是x1.5(实验证明1.5这个值相对比较好),提高了传输速度
与TCP相同,都是通过累计确认实现的,发送端发送了1,2,3,4,5几个包,然后收到远端的ACK:1,3,4,5,当收到ACK = 3时,KCP知道2被跳过1次,收到ACK = 4时,知道2被跳过了2次,此时可以认为2号丢失,不用等超时,直接重传2号包,大大改善了丢包时的传输速度。1字节cmd = 81时,sn相当于TCP中的seq,cmd = 82 时,sn相当于TCP中的ack。cmd相当于WebSocket协议中的openCode,即操作码。
TCP在连续ARQ协议中,不会将一连串的每个数据都响应一次,而是延迟发送ACK,即上文所说的UNA模式,目的是为了充分利用带宽,但是这样会计算出较大的RTT时间,延长了丢包时的判断过程,而KCP的ACK是否延迟发送可以调节。
ARQ模型响应有两种,UNA(此编号前所有包已收到,如TCP)和ACK(该编号包已收到),光用UNA将导致全部重传,光用ACK则丢失成本太高,以往协议都是二选其一,而 KCP协议中,除去单独的 ACK包外,所有包都有UNA信息。
KCP正常模式同TCP一样使用公平退让法则,即发送窗口大小由:发送缓存大小、接收端剩余接收缓存大小、丢包退让及慢启动这四要素决定。但传送及时性要求很高的小数据时,可选择通过配置跳过后两步,仅用前两项来控制发送频率。以牺牲部分公平性及带宽利用率之代价,换取了开着BT都能流畅传输的效果
在传输及时性要求很高的小数据时,可以通过配置忽略上文所说的窗口协议中的拥塞窗口机制,而仅仅依赖于滑动窗口。2字节wnd与TCP协议中的16位窗口大小意义相同,值得一提的是,KCP协议的窗口控制还有其它途径,当cmd = 83时,表示询问远端窗口大小,当cmd = 84时,表示告知远端窗口大小。
4字节conv表示会话匹配数字,为了在KCP基于UDP实现时,让无连接的协议知道哪个是哪个,相当于WEB系统HTTP协议中的SessionID。
1字节frg表示拆数据时的编号,4字节len表示整个数据的长度,相当于WebSocket协议中的len。
IKCPCB是KCP中最重要的结构,也是在会话开始就创建的对象,代表着这次会话,所以这个结构体体现了一个会话所需要涉及到的所有组件。其中一些参数在IKCPSEG中已经描述,不再多说。
参考文章:
在网络中狂奔:KCP协议 https://zhuanlan.zhihu.com/p/112442341
可靠UDP,KCP协议快在哪? https://wetest.qq.com/lab/view/391.html
KCP 协议与源码分析(一) https://github.com/skywind3000/kcp
网络编程懒人入门(五):快速理解为什么说UDP有时比TCP更有优势 http://www.52im.net/thread-1277-1-1.html
转载本站文章《KCP协议:从TCP到UDP家族QUIC/KCP/ENET》, 请注明出处:https://www.zhoulujun.net/html/theory/ComputerScienceTechnology/network/2016_0106_387.html
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。