UDP如何做到可靠性传输
从UDP的特性我们可以得知,udp是一种无连接的不可靠的传输,如果要想让udp实现可靠性传输,就需要以下几点
1*.建立ACK确认应答机制*,可以检测到在传输的过程中有无丢包的现象
2.如果出现丢包的情况则需要建立对应的重传机制,将丢失的部分进行重新发送,以及要建立相应的重传策略 比如每隔100ms检测丢包情况并进行传输。
3.因为UDP并没有序号机制,且在传输的过程中收到数据包的顺序与数据包发送的顺序不一定相同,(3 2 1 —> 2 3 1)此时 就需要建立对应的序号机制,以防止收到的报文出现乱序的情况。
4.为了解决可能出现的乱序问题,就需要根据已经建立的序号机制来建立一个重排机制,将收到的数据包按照他们的序号进行重新排列。
5.除此之外,需要建立一个流量控制的机制
ARQ协议
ARQ协议既是自动重传请求,是传输层的错误纠正协议之一
ARQ协议主要有以下三种模式
(1)即停等式(stop-and-wait)ARQ
(2)回退n帧(go-back-n)ARQ
(3)选择性重传(selective repeat)ARQ
1.急停等式
如上图所示每个报文在发送以后 都需要等到接受方发回确认ack信息以后才会发送下一个数据,这在大量数据的网络传输中无疑是大量耗费时间的,所以这个方式很少被使用。
2、回退n帧
如上图中所示 将数据包2~8封装到一起, 如果在发送数据中,封装到一起的数据包中有任何一个没有收到 则会将这个封装的再重新发送一遍。这种方式在TCP协议中有在被使用
3.选择性重传
选择性重传如上图中所示,就是只将没有收到应答信息的数据包进行重传。
流量控制
我们来思考一下为什么要进行流量控制
1.首先发送方的发送速度和接受方的接受速度并不一定相等
2.当发送方的发送速度大于接受方的接受速度时,接受方的数据缓存区已经满的情况下,发送法还在向接受方发送数据,此时接受方就只能将多余的数据包进行丢弃,大量的丢包会造成对网络资源的极大浪费
3.所以控制发送方的发送速度就是进行流量控制
我们了解了流量控制的原因,那因该如何去实现流量控制呢?
如上图所示 接收端在收到数据以后,给发送方发送确认信息的同时,将自己缓存区剩余的内存容量也一并发给接受方,接受方在收到了信息后就可以根据现在缓存区剩余容量的大小来调整自己的发送速度。
上图中我们看到最后缓存已满 发送方就会停止发送,那什么时候开始重新发送数据呢
如上图所示,在发送方停止发生数据以后,会设定一个定时器,等到达固定时间以后,发送方会发送一个试探报文,接受方在收到试探报文后 会发送确认信息和自己缓存区的内存容量,在发送端收到信息后,根据接受发的缓存容量来动态调整发送的速度大小。
除此之外,也可以由接受方在发生方停止发送数据包以后,处理了缓存区的一定数量的数据包以后,主动给发送端发去通知报文,告知对方自己现在剩余的缓存容量。
拥塞控制
拥塞控制和流量控制虽然采取的动作很相似,但拥塞控制与网络的拥堵情况相关联,而流量控制与接收方的缓存状态相关联。拥塞控制是根据丢包情况调整发送窗口,调整发送数据的速率。
实现UDP可靠性传输的KCP协议有何优势
1.RTO翻倍VS RTO不翻倍
如图所示,在TCP协议中超时重传的时间是RTO×2,如果连续丢包三次 那超时重传的时间就是RTO×8这就是一个很长的等待时间了,而且因为TCP协议是传输层已经定义好的协议,所以是不能改变的。而KCP协议启动快速模式以后,超时重传时间是RTO×1.5 在这种情况下会节约很多时间
2.选择性重传 VS 全部重传
TCP协议在ARQ协议中采用的是回退n帧的模式,所以在出现丢包情况以后需要将这个包所在的所有数据包进行重新发送,而KCP协议采取的是选择性重传,只需要将丢失的数据包进行重传即可。
3.快送重传
在KCP协议中建立了快速重传机制,比如发送端发送了: 1 2 3 4 四个数据包,而接受端只收到了1 3 4 三个数据包 ,当发送方收到 ack3 ack4 但是没有收到ack2时,就清楚数据包2丢失,此时可以不用等待达到超时重传的时间,直接发重新发送数据包2
4.延迟ACKvs非延迟ACK
在TCP中采用的延迟ACK应答,此举时为了充分的应用带宽,延长了丢包的时间判断范围,而在KCP中 是否使用延迟ACK是可以自己选择的。
5.UNA+ACK VS ACK
在ARQ协议中,响应模型有两中UNA(此编号之前的所有包已经全部收到)和ACK(这个数据报已经收到)在TCP中采用的是UNA,光用UNA会导致全部重传,光用ACK则会导致开销太大,而在KCP协议中,除去单独的ACK包,每个包都含有UNA。
KCP协议详解
KCP协议报文
KCP的协议报文如上所示,其中各个部分的详细信息如下所示
1.[0,3]conv:连接号。UDP是无连接的,conv用于表示来自于哪个客户端。对连接的一种替代
2.[4]cmd:命令字。如,IKCP_CMD_ACK确认命令,
IKCP_CMD_WASK接收窗口大小询问命令,
IKCP_CMD_WINS接收窗口大小告知命令,
3.[5] frg : 分片。 用户的数据可能会被拆分成数个KCP数据包,因为每次传输的最大数量是有规定的 。
4.[6,7] wnd: 接受窗口的大小,发送方的发送窗口不能超过接收方给出的数值
5.[8,11]ts: 时间序号,计算RTT ,计算重新发包的时间点。
计算RTT的过程如上图所示。
6.[12,15] sn: 序列号
7.[16,19]una:下一个可接收的序列号。其实就是确认号,收到sn=10的包,una为11
8.[20,23]len : 数据长度
9.data: 用户数据
KCP使用方法
1 创建 KCP对象:ikcpcb *kcp = ikcp_create(conv, user);
2. 设置发送回调函数(如UDP的send函数):kcp->output = udp_output;
真正发送数据需要调用sendto
3.循环调用 update:ikcp_update(kcp, millisec); //在一个线程、定时器 5ms/10m做调度
4.输入一个应用层数据包(如UDP收到的数据包):ikcp_input(kcp,received_udp_packet,received_udp_size);
我们要使用recvfrom接收,然后扔到kcp里面做解析
5.发送数据:ikcp_send(kcp1, buffer, 8); 用户层接口
6.接收数据:hr = ikcp_recv(kcp2, buffer, 10); 用户层读取数据
如需要注意的是 在创建KCP对象时,需要在客户端和服务器端都创建一个KCP对象,且因为KCP是无连接的,所以需要再创建时指定相同的会话ID才能使两个KCP对象进行正常通信。
KCP流程
如上图所示 是KCP在运行时的流程图
首先是创建对应的KCP对象,每个KCP都是独立的,在客户端和服务端都有多个KCP对象。
接下来是进入KCP_updta 这个环节就是一个调度器环节,对传入的信息以及传出的信息进行调度
在数据发送的时候,调用ikcp_send接口,由kcp update调度的时候发送出去(通过sendto)
在数据接收的时候,首先由recvfrom读取应用层的数据,调用kcp_input接口,最后由
kcp_update调度时候交给kcp_recv 解析数据,将内容在应用层展示出来。
KCP具体发送数据的流程
如上图中所示在调用ikcp_send 接口时候,先要对用户的信息进行分片处理,然后将这个分片以后得数据包 加入到sdn_queue中区去,这一步只是加入到发送队列中,并没有加入到发送缓存中去。
接这在ikcp_update调度时候,调用ikcp_flush,将sdn_queue中对应的数据拷贝到sdn_buf 中,在sdn_buf中有三种数据包
1 已经确认的包
2.待确认的包
3.第一次发送的包
根据数据报文中的信息
上图中表示,期待下一个发包的序号为11(11号之前的包已经全部发送),序号9之前的包已经全部收到,所以可以得出9号包可能丢失,在达到超时重传的时间点后,会再次发送9号包。 cwnd,表示发送端拥塞窗口的大小,可以发送多少个UDP数据包
KCP具体接收数据的流程
如上述两张图所示 ,kcp接受数据的方式和发送数据的方式是相反的,首先调用recvfrom()从应用层读取数据,然后调用ikcp_input,,将数据解包放入rcv_buf,再从rcv_buf中拷贝到rcv_queue中。
总结
在本文中,我们简要阐述了如何实现UDP 的可靠性传输,以及解析了KCP协议是如何实现可靠性传输以及kcp协议的流程。
文章参考与<零声教育>的C/C++linux服务期高级架构系统教程