最近公司项目需要使用KCP通信,网上查找了下资料,介绍的挺多,但含有demo的却没有,kcp官网上的demo又高大上,对于新手极不友好,于是自己边学习边研究,文末附上自己写的demo,不足之处,欢迎斧正。
KCP是一个快速可靠的协议,能以比TCP浪费10%-20%的带宽的代价,换取平均延迟降低30%-40%,并且最大延迟降低三倍的传输效果。纯算法实现,并不负责任协议(如UDP)的收发,需要用户自己定义的下层数据包的发送方式,以回调的方式提供给KCP。连时钟都需要外部传递进来,内部不会有任何一次系统调用。
整个协议只有ikcp.h,ikcp.c两个源文件,可以方便的集成到用户自己的协议栈中。也许你实现了一个P2P,或者基于UDP的协议,而缺乏一套完善的ARQ可靠协议实现,那么简单的副本这两个文件到现有项目中,只需编写两行代码,即可使用。
技术特性
TCP是为流量设计的(每秒内可以传输多少KB的数据),讲究的是充分利用带宽。而KCP是为流量设计的(从数据包从一端发送到一端需要多少时间),以10%- TCP通道是一条非常慢,但连续流量很大的大运河,而KCP是水流湍急的小激流。KCP有正常的状态。TCP快30%-40%的传输速度。模式和快速模式两种,通过以下策略达到提高幅度的结果:
RTO翻倍vs不翻倍:
TCP超时计算是RTOx2,这样连续丢三次包就变成RTOx8了,十分恐怖,而KCP启动快速模式后不x2,只是x1.5(实验证明1.5这个值相对比较好),提高了传输速度。
选择性重传vs全部重传:
TCP丢包时会全部重传从丢的那个包开始以后的数据,KCP是选择性重传,只重传真正丢失的数据包。
快速重传:
发送端发送了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:
ARQ模型响应有两种,UNA(此编号前所有包已收到,如TCP)和ACK(该编号包已收到),光用UNA将导致全部重传,光用ACK则丢失成本太高,以前的协议都是二选其一,而KCP协议中,除去单独的ACK包外,所有包都有UNA信息。
非退让流控:
KCP正常模式同TCP一样使用公平退让法则,即发送窗口大小由:发送缓存大小,接收端剩余接收缓存大小,丢包退让以及慢启动这四个要素决定。但传输及时性要求很高的小数据时,,通过配置跳过后两步,仅用前一部分来控制发送频率。以牺牲部分公平性及带宽消耗之代价,换取了开着BT能够流畅传输的效果。
//初始化kcp对象,conv为一个表示会话编号的整数,和tcp的conv一样,通信双
//方需保证conv相同,相互的数据包才能够被认可,用户是给给函数的指针
ikcpcb * kcp = ikcp_create(conv,user);
// KCP的下层协议输出函数,KCP需要发送数据时会调用它
// buf / len表示缓存和长度
//用户指针为kcp对象创建时的值,用于
区分多个KCP对象int udp_output( const char * buf, int len,ikcpcb * kcp, void * user)
{
…
}
//设置变量函数
kcp-> output = udp_output;
//以一定频率调用ikcp_update来更新kcp状态,并连接到当前时钟(毫秒单位)
//如10ms调用一次,或使用ikcp_check确定再次调用更新的时间不必调用
ikcp_update(kcp,millisec);
//收到一个下层数据包(一个UDP包)时需要调用:
ikcp_input(kcp,received_udp_packet,received_udp_size);
处理了下层协议的输出/输入KCP协议就可以正常工作了,使用ikcp_send来向直接发送数据。而另一端使用ikcp_recv(kcp,ptr,size)来接收数据。
协议配置
协议默认模式是一个标准的ARQ,需要通过配置打开相应的加速开关:
工作模式:
int ikcp_nodelay(ikcpcb * kcp,int nodelay,int间隔,int重发,int nc)
nodelay:是否启用nodelay模式,0不启用; 1启用。
interval:协议内部工作的interval,单位毫秒,例如10ms或20ms
重新发送:快速重传模式,交替0关闭,可以设置2(2次ACK跨越将会直接重传)
nc:是否关闭流控,默认为0代表不关闭,1代表关闭。
普通模式:ikcp_nodelay(kcp,0,40,0,0);
极速模式:ikcp_nodelay(kcp,1,10,2,1);
最大窗口:
int ikcp_wndsize(ikcpcb * kcp,int sndwnd,int rcvwnd);
该调用将会设置协议的最大发送窗口和最大接收窗口大小,至少为32。这个可以理解为TCP的SND_BUF和RCV_BUF,只不过单位不一样SND / RCV_BUF单位是字节,这个单位是包。
最大传输单元:
纯算法协议不一定负责探测MTU,至少mtu是1400字节,可以使用ikcp_setmtu来设置该值。该值将会影响数据包归并和分片时候的最大传输单元。
最小RTO:
不论是TCP还是KCP计算RTO时都有最小RTO的限制,甚至计算出来RTO为40ms,由于替换的RTO是100ms,协议只有在100ms后才能检测到丢包,快速模式下为30ms,可以手动更改该值:
kcp-> rx_minrto = 10 ;
https://github.com/bamboo-blue/KCP_Learn