可靠UDP,KCP协议快在哪?-程序员宅基地

WeTest 导读

云真机已经支持手机端的画面投影。云真机实时操作,对延迟的要求比远程视频对话的要求更高(100ms以内)。在无线网络下,如何更实时、更可靠的传输视频流就成了一个挑战。通过websocket、RTMP、UDP的比较,最后选择了可靠的UDP协议KCP来进行实时音视频的传输。

 


 

1 简介

KCP是一个快速可靠协议,能以比 TCP浪费10%-20%的带宽的代价,换取平均延迟降低 30%-40%,且最大延迟降低三倍的传输效果。纯算法实现,并不负责底层协议(如UDP)的收发,需要使用者自己定义下层数据包的发送方式,以 callback的方式提供给 KCP。 连时钟都需要外部传递进来,内部不会有任何一次系统调用。本文传输协议之考虑UDP的情况。

 

名词说明(源码字段):
用户数据:应用层发送的数据,如一张图片2Kb的数据
MTU:最大传输单元。即每次发送的最大数据
RTO:Retransmission TimeOut,重传超时时间。
cwnd:congestion window,拥塞窗口,表示发送方可发送多少个KCP数据包。与接收方窗口有关,与网络状况(拥塞控制)有关,与发送窗口大小有关。
rwnd:receiver window,接收方窗口大小,表示接收方还可接收多少个KCP数据包
snd_queue:待发送KCP数据包队列
snd_nxt:下一个即将发送的kcp数据包序列号
snd_una:下一个待确认的序列号

 

1.1 使用方式

1. 创建 KCP对象:

// 初始化 kcp对象,conv为一个表示会话编号的整数,和tcp的 conv一样,通信双

// 方需保证 conv相同,相互的数据包才能够被认可,user是一个给回调函数的指针

ikcpcb *kcp = ikcp_create(conv, user);

2. 设置传输回调函数(如UDP的send函数):

// KCP的下层协议输出函数,KCP需要发送数据时会调用它

// buf/len 表示缓存和长度

// user指针为 kcp对象创建时传入的值,用于区别多个 KCP对象

int udp_output(const char *buf, int len, ikcpcb *kcp, void *user)

{

  .... 

}

// 设置回调函数

kcp->output = udp_output;

3. 循环调用 update:

// 以一定频率调用 ikcp_update来更新 kcp状态,并且传入当前时钟(毫秒单位)

// 如 10ms调用一次,或用 ikcp_check确定下次调用 update的时间不必每次调用

ikcp_update(kcp, millisec);

4. 输入一个应用层数据包(如UDP收到的数据包):

// 收到一个下层数据包(比如UDP包)时需要调用:ikcp_input(kcp,received_udp_packet,received_udp_size);

处理了下层协议的输出/输入后 KCP协议就可以正常工作了,使用 ikcp_send 来向
远端发送数据。而另一端使用 ikcp_recv(kcp, ptr, size)来接收数据。

[ kcp源码流程图 ]

 

 

总结:UDP收到的包,不断通过kcp_input喂给KCP,KCP会对这部分数据(KCP协议数据)进行解包,重新封装成应用层用户数据,应用层通过kcp_recv获取。应用层通过kcp_send发送数据,KCP会把用户数据拆分kcp数据包,通过kcp_output,以UDP(send)的方式发送。

 

1.2 KCP的配置模式

这部分KCP文档有介绍,理解KCP协议无需过于关注。协议默认模式是一个标准的 ARQ,需要通过配置打开各项加速开关:

 

1. 工作模式:

int ikcp_nodelay(ikcpcb *kcp, int nodelay, int interval, int resend, int nc)

  • nodelay :是否启用 nodelay模式,0不启用;1启用。

  • interval :协议内部工作的 interval,单位毫秒,比如 10ms或者 20ms

  • resend :快速重传模式,默认0关闭,可以设置2(2次ACK跨越将会直接重传)

  • nc :是否关闭流控,默认是0代表不关闭,1代表关闭。

     

 

普通模式: ikcp_nodelay(kcp, 0, 40, 0, 0);

极速模式: ikcp_nodelay(kcp, 1, 10, 2, 1)

 

1. 最大窗口

int ikcp_wndsize(ikcpcb *kcp, int sndwnd, int rcvwnd);

该调用将会设置协议的最大发送窗口和最大接收窗口大小,默认为32. 这个可以理解为 TCP的 SND_BUF 和 RCV_BUF,只不过单位不一样 SND/RCV_BUF 单位是字节,这个单位是包。

 

2. 最大传输单元:

纯算法协议并不负责探测 MTU,默认 mtu是1400字节,可以使用ikcp_setmtu来设置该值。该值将会影响数据包归并及分片时候的最大传输单元。

 

3. 最小RTO:

不管是 TCP还是 KCP计算 RTO时都有最小 RTO的限制,即便计算出来RTO为40ms,由于默认的 RTO是100ms,协议只有在100ms后才能检测到丢包,快速模式下为30ms,可以手动更改该值:
kcp->rx_minrto = 10;

 

1.3 KCP为什么存在?

首先要看TCP与UDP的区别,TCP与UDP都是传输层的协议,比较两者的区别主要应该是说TCP比UDP多了什么?


面向连接:TCP接收方与发送方维持了一个状态(建立连接,断开连接),双方知道对方还在。
可靠的:发送出去的数据对方一定能够接收到,而且是按照发送的顺序收到的。
流量控制与拥塞控制:TCP靠谱通过滑动窗口确保,发送的数据接收方来得及收。TCP无私,发生数据包丢失的时候认为整个网络比较堵,自己放慢数据发送速度。


TCP协议的可靠与无私让使用TCP开发更为简单,同时它的这种设计也导致了慢的特点。UDP协议简单,所以它更快。但是,UDP毕竟是不可靠的,应用层收到的数据可能是缺失、乱序的。KCP协议就是在保留UDP快的基础上,提供可靠的传输,应用层使用更加简单。

 

其他差别,TCP以字节流的形式,UDP以数据包的形式。很多人以为,UDP是不可靠的,所以sendto(1000),接收端recvfrom(1000)可能会收到900。这个是错误的。所谓数据包,就是说UDP是有界的,sendto(300),sendto(500);接收到,recvfrom(1000),recvfrom(1000)那么可能会收到300,500或者其中一个或者都没收到。UDP应用层发送的数据,在接收缓存足够的情况下,要么收到全的,要么收不到。

 

总结:TCP可靠简单,但是复杂无私,所以速度慢。KCP尽可能保留UDP快的特点下,保证可靠。

 

2 KCP原理

2.1 协议简介

KCP是一个可靠的传输协议,UDP本身是不可靠的,所以需要额外信息来保证传输数据的可靠性。因此,我们需要在传输的数据上增加一个包头。用于确保数据的可靠、有序。

0                         4      5     6      8 (BYTE)

+-------------------+----+----+----+ 

|      conv      | cmd | frg |  wnd | 

+-------------------+----+----+----+   8

|         ts         |             sn           | 

+-------------------+----------------+  16

|       una       |             len          |

+-------------------+----------------+   24

|                                                   |

|            DATA (optional)          |

|                                                   |

+-------------------------------------+

conv:连接号。UDP是无连接的,conv用于表示来自于哪个客户端。对连接的一种替代
cmd:命令字。如,IKCP_CMD_ACK确认命令,IKCP_CMD_WASK接收窗口大小询问命令,IKCP_CMD_WINS接收窗口大小告知命令,
frg:分片,用户数据可能会被分成多个KCP包,发送出去
wnd:接收窗口大小,发送方的发送窗口不能超过接收方给出的数值
ts:时间序列
sn:序列号
una:下一个可接收的序列号。其实就是确认号,收到sn=10的包,una为11
len:数据长度
data:用户数据

 

后面的讲解,主要以极速模式: ikcp_nodelay(kcp, 1, 10, 2, 1)为主,启用nodelay设置,刷新间隔控制在10ms,开启快速重传模式,关闭流量控制。

 

2.2 数据发送过程

2.2.1 数据发送准备

用户发送数据的函数为ikcp_send。
ikcp_send(ikcpcb kcp, const char buffer, int len)
该函数的功能非常简单,把用户发送的数据根据MSS进行分片。如上图,用户发送1900字节的数据,MTU为1400byte。因此,该函数会把1900byte的用户数据分成两个包,一个数据大小为1400,头frg设置为1,len设置为1400;第二个包,头frg设置为0,len设置为500。切好KCP包之后,放入到名为snd_queue的待发送队列中。

注:流模式情况下,kcp会把两次发送的数据衔接为一个完整的kcp包。非流模式下,用户数据%MSS的包,也会作为一个包发送出去。

MTU,数据链路层规定的每一帧的最大长度,超过这个长度数据会被分片。通常MTU的长度为1500字节,IP协议规定所有的路由器均应该能够转发(512数据+60IP首部+4预留=576字节)的数据。MSS,最大输出大小(双方的约定),KCP的大小为MTU-kcp头24字节。IP数据报越短,路由器转发越快,但是资源利用率越低。传输链路上的所有MTU都一至的情况下效率最高,应该尽可能的避免数据传输的工程中,再次被分。UDP再次被分的后(通常1分为2),只要丢失其中的任意一份,两份都要重新传输。因此,合理的MTU应该是保证数据不被再分的前提下,尽可能的大。
以太网的MTU通常为1500字节-IP头(20字节固定+40字节可选)-UDP头8个字节=1472字节。KCP会考虑多传输协议,但是在UDP的情况下,设置为1472字节更为合理。

 

2.2.2 实际发送

KCP会不停的进行update更新最新情况,数据的实际发送在update时进行。发送过程如下图所示:

[ KCP 发送过程 ]

 

步骤1:待发送队列移至发送队列
KCP会把snd_queue待发送队列中的kcp包,移至snd_buf发送队列。移动的包的数量不会超过snd_una+cwnd-snd_nxt,确保发送的数据不会让接收方的接收队列溢出。该功能类似于TCP协议中的滑动窗口。cwnd=min(snd_wnd,rmt_wnd,kcp->cwnd)的最小值决定,snd_wnd,rmt_wnd比较好理解可发送的数据,可发送的数据最大值,应该是发送方可以发送的数据和接收方可以接收的数据的最小值。kcp->cwnd是拥塞控制的一个值,跟网络状况相关,网络状况差的时候,KCP认为应该降低发送的数据,后面会有详细的介绍。
如上图中,snd_queue待发送队列中有4个KCP包等待发送,这个时候snd_nxt下一个发送的kcp包序列号为11,snd_una下一个确认的KCP包为9(8已经确认,9,10已经发送但是还没得到接收方的确认)。因为cwnd=5,发送队列中还有2个发送了但是还未得到确认,所以可以从待发送队列中取前面的3个KCP包放入到发送队列中,序列号分别设置为11,12,13。

 

步骤2:发送发送队列的数据
发送队列中包含两种类型的数据,已发送但是尚未被接收方确认的数据,没被发送过的数据。没发送过的数据比较好处理,直接发送即可。重点在于已经发送了但是还没被接收方确认的数据,该部分的策略直接决定着协议快速、高效与否。KCP主要使用两种策略来决定是否需要重传KCP数据包,超时重传、快速重传、选择重传。

 

1、超时重传
TCP超时计算是RTOx2,这样连续丢三次包就变成RTOx8了,而KCP非快速模式下每次+RTO,急速模式下+0.5RTO(实验证明1.5这个值相对比较好),提高了传输速度。

 

[ RTO算法对比图 ]

 

2、快速重传
发送端发送了1,2,3,4,5几个包,然后收到远端的ACK: 1, 3, 4, 5,当收到ACK3时,KCP知道2被跳过1次,收到ACK4时,知道2被跳过了2次,此时可以认为2号丢失,不用等超时,直接重传2号包,大大改善了丢包时的传输速度。TCP有快速重传算法,TCP包被跳过3次之后会进行重传。
注:可以通过统计错误重传(重传的包实际没丢,仅乱序),优化该设置。

 

3、选择重传
老的TCP丢包时会全部重传从丢的那个包开始以后的数据,KCP是选择性重传,只重传真正丢失的数据包。但是,目前大部分的操作系统,linux与android手机均是支持SACK选择重传的。

 

步骤3:数据发送
通过步骤2判定,kcp包是否需要发送,如果需要发送的kcp包则通过,kcp_setoutput设置的发送接口进行发送,UDP通常为sendto。步骤3,会对较小的kcp包进行合并,一次性发送提高效率

 

2.3 数据接收过程

KCP的接收过程是将UDP收到的数据进行解包,重新组装顺序的、可靠的数据后交付给用户。

 

2.3.1 KCP数据包接收

 

kcp_input输入UDP收到的数据包。kcp包对前面的24个字节进行解压,包括conv、 frg、 cmd、 wnd、 ts、 sn、 una、 len。根据una,会删除snd_buf中,所有una之前的kcp数据包,因为这些数据包接收者已经确认。根据wnd更新接收端接收窗口大小。根据不同的命令字进行分别处理。数据接收后,更新流程如下所示:

[ 接收处理流程图 ]

 

1、IKCP_CMD_PUSH数据发送命令
a、KCP会把收到的数据包的sn及ts放置在acklist中,两个相邻的节点为一组,分别存储sn和ts。update时会读取acklist,并以IKCP_CMD_ACK的命令返回确认包。如下图中,收到了两个kpc包,acklist中会分别存放10,123,11,124。
b、kcp数据包放置rcv_buf队列。丢弃接收窗口之外的和重复的包。然后将rcv_buf中的包,移至rcv_queue。原来的rcv_buf中已经有sn=10和sn=13的包了,sn=10的kcp包已经在rcv_buf中了,因此新收到的包会直接丢弃掉,sn=11的包放置至rcv_buf中。
c、把rcv_buf中前面连续的数据sn=11,12,13全部移动至rcv_queue,rcv_nxt也变成14。

rcv_queue的数据是连续的,rcv_buf可能是间隔的
d、kcp_recv函数,用户获取接收到数据(去除kcp头的用户数据)。该函数根据frg,把kcp包数据进行组合返回给用户。

 

2、IKCP_CMD_ACK数据确认包
两个使命:1、RTO更新,2、确认发送包接收方已接收到。

 

正常情况:收到的sn为11,una为12。表示sn为11的已经确认,下一个等待接收的为12。发送队列中,待确认的一个包为11,这个时候snd_una向后移动一位,序列号为11的包从发送队列中删除。

[ 数据确认包处理流程 ]

 

异常情况:如下图所示,sn!=11的情况均为异常情况。sn<11表示,收到重复确认的包,如本来以为丢失的包重新又收到了,所以产生重复确认的包;sn>17,收到没发送过的序列号,概率极低,可能是conv没变重启程序导致的;112,则启动快速重传

[ KCP快速确认 ]

 

确认包发送,接收到的包会全部放在acklist中,以IKCP_CMD_ACK包发送出去

 

3 流量控制与拥塞控制

 

3.1 RTO计算(与TCP完全一样)

RTT:一个报文段发送出去,到收到对应确认包的时间差。
SRTT(kcp->rx_srtt):RTT的一个加权RTT平均值,平滑值。
RTTVAR(kcp->rx_rttval):RTT的平均偏差,用来衡量RTT的抖动。

 

3.2 流量控制

流量控制是点对点的通信量的控制,是一个端到端的问题。总结起来,就是发送方的速度要匹配接收方接收(处理)数据的速度。发送方要抑制自身的发送速率,以便使接收端来得及接收。


KCP的发送机制采用TCP的滑动窗口方式,可以非常容易的控制流量。KCP的头中包含wnd,即接收方目前可以接收的大小。能够发送的数据即为snd_una与snd_una+wnd之间的数据。接收方每次都会告诉发送方我还能接收多少,发送方就控制下,确保自己发送的数据不多于接收端可以接收的大小。

 

KCP默认为32,即可以接收最大为32*MTU=43.75kB。KCP采用update的方式,更新间隔为10ms,那么KCP限定了你最大传输速率为4375kB/s,在高网速传输大内容的情况下需要调用ikcp_wndsize调整接收与发送窗口。

 

KCP的主要特色在于实时性高,对于实时性高的应用,如果发生数据堆积会造成延迟的持续增大。建议从应用侧更好的控制发送流量与网络速度持平,避免缓存堆积延迟。(详见参考资料)

 

3.3 拥塞控制(KCP可关闭)

KCP的优势在于可以完全关闭拥塞控制,非常自私的进行发送。KCP采用的拥塞控制策略为TCP最古老的策略,无任何优势。完全关闭拥塞控制,也不是一个最优策略,它全是会造成更为拥堵的情况。
网络中链路的带宽,与整条网络中的交换节点(路由器、交换机、基站等)有关。如果,所有使用该链路的流量超出了,该链路所能提供的能力,就会发生拥塞。车多路窄,就会堵车,车越多堵的越厉害。因此,TCP作为一个大公无私的协议,当网络上发送拥堵的时候会降低自身发送数据的速度。拥塞控制是整个网络的事情,流量控制是发送和接收两方的事情。
当发送方没有按时接收到确认包,就认为网络发生了拥堵行为。TCP拥塞控制的方式,归结为慢开始、拥塞避免,如下图所示

[ 拥塞控制算法 ]

 

KCP发生丢包的情况下的拥塞控制策略与TCP Tahoe版本的策略一致。TCP Reno版本已经使用快恢复策略。因此,丢包的情况下,其实KCP拥塞控制策略比TCP更为苛刻。

KCP在发生快速重传,数据包乱序时,采用的是TCP快恢复的策略。控制窗口调整为已经发送没有接收到ack的数据包数目的一半+resent。

注:目前kernel 3.2以上的linux,默认采用google改进的拥塞控制算法,Proportional Rate Reduction for TCP。该算法的主要特点为,的cwnd如下图所示:

 

 

 

 

手机投射静态演示

 


 

目前,“WeTest助手”的手机控制器已经上线,完美复制真机体验,体验畅快淋漓的操作!

 

点击链接:http://wetest.qq.com/cloud/help/effective 即可下载“WeTest助手”。

 

 

 

如果使用当中有任何疑问,欢迎咨询腾讯WeTest企业QQ:800024531

 

  腾讯WeTest有奖征文活动进行中,欢迎投稿!了解详情:
http://wetest.qq.com/lab/view/379.html

 

转载于:https://www.cnblogs.com/wetest/p/9190786.html

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/diezhuzhuo3333/article/details/101900619

智能推荐

spring源码 - 条件注解@ConditionnalOnClass的原理分析_conditionalonclass-程序员宅基地

文章浏览阅读3.9k次,点赞5次,收藏9次。用过springboot的小伙伴们都知道,相比于spring,它最大的优势是帮我们省去了一大堆超大一堆繁琐的配置。比如在spring中,当我们需要在项目中整合第三方插件(如redis、mybatis、rabbitmq)时,往往需要在xml配置文件中去配置这些插件的等将其与spring进行整合。而在springboot中,他会根据项目中引入哪些插件自动地将插件进行整合,这都得益于springboot的自动装配或称为自动配置。_conditionalonclass

【附源码】基于flask框架基于高校大学生校园社交系统 (python+mysql+论文)-程序员宅基地

文章浏览阅读838次,点赞21次,收藏9次。因此,开发一款基于高校大学生校园社交系统,旨在为大学生提供一个更加贴心、便捷的社交平台,满足他们在校园生活中的各种社交需求。综上所述,基于高校大学生校园社交系统的开发不仅可以满足大学生在校园生活中的社交需求,还可以为他们提供一个展示自我、锻炼能力的舞台,同时也有助于提高校园信息的透明度和便利性。在数据库管理工具的选择上,使用了Navicat 11,这是一个用户友好且功能强大的数据库管理软件,它支持多种数据库系统,包括MySQL,并提供了图形化界面,使得数据库的管理和维护工作更加便捷。系统的选题背景和意义。

Windows 7 驱动开发-程序员宅基地

文章浏览阅读112次。 本文是对Win7(64)+VS2010+WDK7.1.0(WinDDK\7600.16385.1)开发驱动的小结。 一、系统工具 1、Win7(amd64位)系统注:已装系统后,管理员身份运行cmd命令,查看bcdedit /set testsigning true、bcdedit /debug on 等命令是否运行成功。若失败,请将bcdedit命令所在文件夹boo..._win7 开发驱动

关于reids-程序员宅基地

文章浏览阅读62次。redis 官网(英文):https://redis.io/ redis 手册(中文): http://doc.redisfans.com/ redis 中文网(中文) : http://www.redis.net.cn/ redis 教程(中文) : http://www.redis.net.cn/tutorial/3501.html开始做,坚持做,...

ANT DESIGN VUE 中树形控件勾选框改造,多选变单选_antd 点选框 正方形-程序员宅基地

文章浏览阅读5.1k次,点赞2次,收藏14次。一、遇到问题:有个需求,是要把属性控件的勾选框改变,从正方形的多选变成圆形单选。话不多说,上图1、官方文档案例:这是很明显的多选,正方形勾选框。详细地址:Ant Design Vue2、需求文档案例:我这里采取的是改变树形组件勾选框的样式,并且通过api提供的信息设置成单选,结果如上图。父子之间不关联。3、实现下面是调研时候的html代码,我这里使用的是可以拖拽的树形控件,加上单选功能html部分<a-tree clas_antd 点选框 正方形

关于System.Runtime.InteropServices.SEHException的异常处理_system.runtime.interopservices.sehexception 初始化失败-程序员宅基地

文章浏览阅读520次,点赞8次,收藏8次。System.Runtime.InteropServices.SEHException异常处理_system.runtime.interopservices.sehexception 初始化失败

随便推点

【配置环境】在虚拟机的Ubuntu下安装VS Code并配置C/C++运行环境_vmware下ubuntu系统中安装vscode-程序员宅基地

文章浏览阅读3.7k次,点赞2次,收藏26次。Ubuntu下VSCode安装和配置C/C++运行环境,保姆级详细!!!_vmware下ubuntu系统中安装vscode

Web背景铺满屏幕 属性:background-image_怎么webstrom背景铺满网页-程序员宅基地

文章浏览阅读605次。记录:学习web中背景铺满且固定位置。_怎么webstrom背景铺满网页

【模型制作】如何把模之屋的模型导入UE_模之屋的模型怎么导入blender-程序员宅基地

文章浏览阅读1.3k次,点赞11次,收藏8次。blender去官网下载,我下载的是此时最新版本3.6版本,如果上不了github,可以从这里下载插件:链接:https://pan.baidu.com/s/1FnEb5W_y8Wap3IVyydCqvQ?编辑-偏好设置-插件-安装,直接点击压缩包安装即可。(此步可以略过)点击骨骼,左上角点击姿态模式,(鼠标中键转动视角,shirf+鼠标中键移动视角),点击骨骼,按对应的快捷键检查骨骼。6,点击这个,取消勾选fix mmd(如果是mmd文件则不需要取消勾选),然后点击Fix Model。_模之屋的模型怎么导入blender

Windows安装RedisJSON 模块_windows安装json-程序员宅基地

文章浏览阅读936次。RedisJSON 是使用 C 语言编写的,因此需要通过 Visual Studio Build Tools 来编译源代码。其中,/path/to/redisjson.dll 是 redisjson.dll 的绝对路径。这会在 RedisJSON 源代码根目录下的 build 目录中生成一个 redisjson.dll 文件,这个文件就是 RedisJSON 模块的库文件。你可以从 RedisJSON 的官方库中下载其源代码,在 Windows 上建议使用 Git Bash 或者是使用 Git 命令下载。_windows安装json

Lua之table新增整数键值-程序员宅基地

文章浏览阅读1.6k次。作者:糊涂小蜗牛链接:https://www.jianshu.com/p/42d7d0b82708来源:简书lua表分为数组和散列表部分,数组部分从 1 开始作为第一个索引,散列表部分要求键值不能为 nil。因为表包括散列表和数组两部分数据,所以一个以正整数作为键值的数据写入lua表时不确定是写入了数组还是散列表中。接下来讨论这部分的操作原理。虽然正整数作为键值的数据写入lua表时不确定...

分享找不到xinput1_3.dll丢失的5个修复方法-程序员宅基地

文章浏览阅读803次,点赞20次,收藏24次。我们可以下载一个dll修复工具,使用dll修复工具进行修复操作非常简单(亲测可以修复),它可以自动检测电脑缺失或者损坏的dll文件,如果xinput1_3.dll缺失,dll修复工具检测到以后,便会自动安装xinput1_3.dll文件。1. 游戏控制器无法正常工作:xinput1_3.dll负责处理游戏控制器的输入,如果该文件丢失,游戏控制器可能无法正常工作,影响游戏体验。3. 系统功能受限:xinput1_3.dll是一个重要的系统文件,它的丢失可能会导致系统功能受限,甚至无法正常启动。

推荐文章

热门文章

相关标签