kcp协议是传输层的一个具有可靠性的传输层ARQ协议。它的设计是为了解决在网络拥堵情况下tcp协议的网络速度慢的问题。kcp力求在保证可靠性的情况下提高传输速度。kcp协议的关注点主要在控制数据的可靠性和提高传输速度上面,因此kcp没有规定下层传输协议,一般用udp作为下层传输协议,kcp层协议的数据包在udp数据报文的基础上增加控制头。当用户数据很大,大于一个udp包能承担的范围时(大于mss),kcp会将用户数据分片存储在多个kcp包中。因此每个kcp包称为一个分片。
为了提供可靠性,kcp采用了重传机制。为实现重传机制,kcp为每个分片分配一个唯一标识,接收方收到一个包后告知发送方接到的包的序号,发送方接到确认后再继续发送。而如果发送方在一定时间内(超时重传时间)没有接到确认,就说明数据包丢失了,发送方需要重传丢失的数据包,所以发送方会把待确认的数据缓存起来,方便重传。
停等的重传机制发送一个包后必须等待确认后再发下一个包,传输速度较慢,所以为了提高发送速度,发送方可以不必再每发送一个包后就进行等待确认,而是可以发送多个包出去,然后等待接收方一一确认。又由于接收方不可能同时处理无限多的数据,因此需要限制发送方往网络中发送的数据数量。因此接收方限制发送方在未收到确认之前只能发送wnd大小的数据,这个机制叫做滑动窗口机制。kcp采用滑动窗口机制来提高发送速度。由于UDP在网络中的传输是不可靠的,因此会出现丢包和包的乱序。kcp是可靠的保证数据有序的协议,所以为了纠正包的乱序。接收方维护一个接收窗口。接收窗口有一个起始序号rcv_nxt以及尾序号rcv_nxt+rcv_wnd。如果接收窗口收到序号为rcv_nxt的分片那么rcv_nxt就加一,形象一点的说法是滑动窗口右移,并把该数据放入接收队列供应用层取用。如果收到的数据在窗口范围内但不是rcv_nxt那么就把数据保存起来,等收到rcv_nxt序号的分片时再一并放入接收队列供应用层取用。
当网络拥堵严重时,会发生丢包,丢包发生时kcp为了保证可靠性需要重传数据。而发送方需要判断什么时候发生了丢包,以及丢了哪些包。为了解决这个问题,发送方为缓存队列中的每个包设置了包序号和超时重传时间。当检测到当前时间超过了分片的超时重传时间,该分片还没有得到确认时就会触发该分片的超时重传。
数据在网络中的传输时间是不固定的,因此超时重传时间比较长。而为了尽早地判断出数据包的丢失,kcp引入了快速重传机制。快速重传机制工作原理是,当发送方发送了n,n+1,n+2...等等包出去后,接收方没有接收到n,而接收到n+1,n+2..等等n号包之后的包,这时因为n号包之后的包都已经接收到了,而n号包还没有接收到,所以可以认为n号包已经丢失了,告知发送方可以进行快速重传。kcp为了支持快速重传,接收方需要告诉发送方,哪些包已经成功收到了,哪些包没有收到。因此接收方返回发送方的确认数据(ack)中包含以下信息:接收窗口左端的序号rcv_nxt,接收到的大于rcv_nxt的包序号sn。rcv_nxt的含义是接收方已经成功按顺序接收了rcv_nxt序号之前的所有包,大于rcv_nxt的序号sn表示的是在接收窗口内的不连续的包。发送方接收到接收方发过来的数据时,首先解析rcv_nxt,把发送缓存中所有小于rcv_nxt序号的包全部移除掉(因为这些包全都都已经正确接收了)。然后再解析sn,遍历发送缓存,找到所有序号小于sn的包,这些包就是可能在网络中已经丢掉了的包,只是可能,因为有可能这些包只是拥堵在了网络中,需要更长的时间到达,所以这里我们设置一个快速重传的门限,对每个分片维护一个快速重传的计数,每收到一个ack解析sn后找到了一个分片,就把该分片的快速重传的计数加一,如果该计数达到了快速重传门限,那么就认为该分片已经丢失,可以触发快速重传,该门限值在kcp中可以设置,tcp中是3。
丢包发生时,由于滑动窗口的存在,假设第n个包丢失了,但是此时n+1,n+2号包却已经传输成功了,此时最好只重传丢失的n号包,而不重传成功传输的n+1,n+2号包,这个机制叫做选择重传,选择重传的关键在于接收方要告知发送方哪些包已经收到了,哪些包没有收到,为了最小化数据量,接收方可以告诉发送方哪些包已经按序收到了,哪些包是收到的但是不连续。所以返回的ack中包含rcv_nxt和sn。rcv_nxt代表收到的所有连续的包,sn代表哪些不连续的包收到了,那么根据这两个参数可以计算出来没有收到的包的序号。
当网络实在很拥堵的时候(一般由于网络消息太多,堵车了),kcp会限制发送方发送的数据量,这叫做拥塞控制,拥塞控制就是告诉发送方,网络太堵了,应该少发一些数据,因此在滑动窗口的机制上引入了拥塞窗口,也就是说发送发发送的数据不得超过拥塞窗口,拥塞窗口的大小会随网络情况而变快,网络快拥塞窗口就大,反之同理。
那么拥塞窗口应该等于多少呢?解决这一问题的原则是,让网络充分被利用,但是不能堵塞,这里引入了慢启动机制,慢启动也就是控制拥塞窗口从0开始增长,随着数据不断地成功传输,拥塞窗口逐渐增大,直至达到饱和,也就是网络的收发平衡。为了快速达到网络的收发平衡,拥塞窗口采用倍数增长。也就是每成功发送一个数据拥塞窗口加一,举个例子,窗口大小为1时,发送一个数据,成功后窗口变成2,之后发送两个数据出去,成功接收后窗口大小变为4。为了方便让更多的用户连入网络时,网络能有足够的流量提供给用户,还可以设置拥塞门限,拥塞门限值就是当用户拥塞窗口快速增长到门限值后就减慢增加速度,缓慢增长,腾出流量给其它用户。
但是当网络很拥堵的情况下,导致发送数据出现重传时,这时说明网络中消息太多了,用户应该减少发送的数据,也就是拥塞窗口应该减小。怎么减小呢,在快速重传的情况下,有包丢失了但是有后续的包收到了,说明网络还是通的,这时采取拥塞窗口的退半避让,拥塞窗口减半,拥塞门限减半。减小网络流量,缓解拥堵。当出现超时重传的时候,说明网络很可能死掉了,因为超时重传会出现,原因是有包丢失了,并且该包之后的包也没有收到,这很有可能是网络死了,这时候,拥塞窗口直接变为1,不再发送新的数据,直到丢失的包传输成功。
在上述原理之下,kcp为了提高传输速度,还可以有许多选项供用户选择:
kcp的拥塞控制可以取消;
ack回复可以设置成无延迟ack回复;
kcp的快速重传门限可以控制;
另外,kcp有流模式和消息模式,其中
流模式:
更高的网络利用率;
更大的传输速度;
解析数据相对更复杂;
消息模式:
更小的网络利用率;
更小的传输速度;
解析数据相对更简单;
总之,kcp采取一系列措施尽量提高网络传输速率,在网络实时性和可靠性要求比较高的场景下可以考虑kcp协议代替tcp协议。