UDP可靠传输协议KCP的一些理解

UDP主要用在哪两个方面

  • 游戏

  • 音视频通话

为什么要使用UDP?

实时性的考虑,丢包重传,TCP协议栈重传无法控制,UDP重发可以自定义策略。

在DNS查询的时候,也使用UDP,对资源的考虑。

如何做到可靠性连接?

  • ack机制

  • 重传机制 重传策略

  • 序号机制

  • 重排机制

  • 窗口机制

TCP和UDP的选择

保证TCP的自动重传请求 ARQ协议

ARQ协议(Automatic Repeat-reQuest),即自动重传请求,是传输层的错误纠正协 议之一,它通过使用确认和超时两个机制,在不可靠的网络上实现可靠的信息 传输。 ARQ协议主要有3种模式:

  1. 即停等式(stop-and-wait)ARQ

  2. 回退n帧(go-back-n)ARQ,

  3. 选择性重传(selective repeat)ARQ

ARQ协议-停等式

停等协议的工作原理如下:

1、发送方对接收方发送数据包,然后等待接收方回复ACK并且开始计时。

2、在等待过程中,发送方停止发送新的数据包。

3、当数据包没有成功被接收方接收,接收方不会发送ACK.这样发送方在等待一 定时间后,重新发送数据包。

4、反复以上步骤直到收到从接收方发送的ACK.

缺点:较长的等待时间导致低的数据传输速度。

ARQ协议-回退n帧(go-back-n)ARQ

为了克服停等协议长时间等待ACK的缺陷,连续ARQ协议会连续发送一组数据包,然后再 等待这些数据包的ACK。

什么是滑动窗口:发送方和接收方都会维护一个数据帧的序列,这个序列被称作窗口。发送方的 窗口大小由接收方确定,目的在于控制发送速度,以免接收方的缓存不够大,而导致溢出,同时控 制流量也可以避免网络拥塞。协议中规定,对于窗口内未经确认的分组需要重传。

回退N步(Go-Back-N,GBN):回退N步协议允许发送方在等待超时的间歇,可以继续发送分 组。所有发送的分组,都带有序号。在GBN协议中,发送方需响应以下三种事件:

1、上层的调用。上层调用相应send()时,发送方首先要检查发送窗口是否已满

2、接收ACK。在该协议中,对序号为n的分组的确认采取累积确认的方式,表明接收方已 正确接收到序号n以前(包括n)的所有分组。

3、超时。若出现超时,发送方将重传所有已发出但还未被确认的分组

对于接收方来说,若一个序号为n的分组被正确接收,并且按序,则接收方会为该分组返 回一个ACK给发送方,并将该分组中的数据交付给上层。在其他情况下,接收方都会丢弃 分组。若分组n已接收并交付,那么所有序号比n小的分组也已完成了交付。因此GBN采用 累积确认是一个很自然的选择。发送方在发完一个窗口里的所有分组后,会检查最大的有 效确认,然后从最大有效确认的后一个分组开始重传。

如上图所示,序号为2的分组丢失,因此分组2及之后的分组都将被重传

总结:GBN采用的技术包括序号、累积确认、检验和以及计时/重传

ARQ协议-选择重传

虽然GBN改善了停等协议中时间等待较长的缺陷,但它依旧存在着性能问题。特别 是当窗口长度很大的时候,会使效率大大降低。而SR协议通过让发送方仅重传在接 收方丢失或损坏了的分组,从而避免了不必要的重传,提高了效率。

在SR协议下,发送方需响应以下三种事件:

1、从上层收到数据。当从上层收到数据后,发送方需检查下一个可用于该分组的 序号。若序号在窗口中则将数据发送。

2、接收ACK。若收到ACK,且该分组在窗口内,则发送方将那个被确认的分组标记 为已接收。若该分组序号等于基序号,则窗口序号向前移动到具有最小序号的未确 认分组处。若窗口移动后并且有序号落在窗口内的未发送分组,则发送这些分组。

3、超时。若出现超时,发送方将重传已发出但还未确认的分组。与GBN不同的是, SR协议中的每个分组都有独立的计时器

在SR协议下,接收方需响应以下三种事件: (假设接收窗口的基序号为4,分组长度也为4) 1、序号在[4,7]内的分组被正确接收。该情况下,收到的分组落在接收方的窗口内,一个ACK 将发送给发送方。若该分组是以前没收到的分组,则被缓存。若该分组的序号等于基序号4, 则该分组以及以前缓存的序号连续的分组都交付给上层,然后,接收窗口将向前移动。

2、序号在[0,3]内的分组被正确接收。在该情况下,必须产生一个ACK,尽管该分组是接收方 以前已确认过的分组。若接收方不确认该分组,发送方窗口将不能向前移动。

3、其他情况。忽略该分组 对于接收方来说,若一个分组正确接收而不管其是否按序,则接收方会为该分组返回一个ACK 给发送方。失序的分组将被缓存,直到所有丢失的分组都被收到,这时才可以将一批分组按 序交付给上层

RTT和RTO

  • RTO(Retransmission TimeOut)即重传超时时间。
  • RTT(Round-Trip Time): 往返时延。表示从发送端发送数据开始,到发送端收到来自 接收端的确认(接收端收到数据后便立即发送确认),总共经历的时延。
      由三部分组成:
      * 链路的传播时间(propagation delay)
      * 末端系统的处理时间、
      * 路由器缓存中的排队和处理时间(queuing delay)
      其中,前两个部分的值对于一个TCP连接相对固定,路由器缓存中的排队和处理时间会 随着整个网络拥塞程度的变化而变化。 所以RTT的变化在一定程度上反应网络的拥塞程 度
    在TCP的选项 内容可以插入时间戳。

流量控制

  • 双方在通信的时候,发送方的速率与接收方的速率是不一定相等,如果发送方的发送速率太快,会导致接收方处理不过来,这时候接收方只能把处理不过来的数据存在缓存区里(失序的数据包也会被存放在缓存区里)。
  • 如果缓存区满了发送方还在疯狂着发送数据,接收方只能把收到的数据包丢掉,大量的丢包会极大着浪费网络资源,因此,我们需要控制发送方的发送速率,让接收方与发送方处于一种动态平衡才好。
  • 对发送方发送速率的控制,称之为流量控制。

如何控制?

  • 接收方每次收到数据包,可以在发送确定报文的时候,同时告诉发送方自己的缓存区还剩
    余多少是空闲的,我们也把缓存区的剩余大小称之为接收窗口大小,用变量win来表示接收窗口的大小。

  • 发送方收到之后,便会调整自己的发送速率,也就是调整自己发送窗口的大小,当发送方收到接收窗口的大小为0时,发送方就会停止发送数据,防止出现大量丢包情况的发生。

发送方何时再继续发送数据?

当发送方停止发送数据后,该怎样才能知道自己可以继续发送数据?

  1. 当接收方处理好数据,接受窗口 win > 0 时,接收方发个通知报文去通知发送方,告诉他可以继续发送数据了。当发送方收到窗口大于0的报文时,就继续发送数据。
  2. 当发送方收到接受窗口 win = 0 时,这时发送方停止发送报文,并且同时开启一个定时器,每隔一段时间就发个测试报文去询问接收方,打听是否可以继续发送数据了,如果可以,接收方就告诉他此时接受窗口的大小;如果接受窗口大小还是为0,则发送方再次刷新启动定时器。

小结

  1. 通信的双方都拥有两个滑动窗口,一个用于接受数据,称之为接收窗口;一个用于发送数据,称之为拥塞窗口(即发送窗口)。指出接受窗口大小的通知我们称之为窗口通告。
  2. 接收窗口的大小固定吗?接受窗口的大小是根据某种算法动态调整的。
  3. 接收窗口越大越好吗?当接收窗口达到某个值的时候,再增大的话也不怎么会减少丢包率的了,而且还会更加消耗内存。所以接收窗口的大小必须根据网络环境以及发送发的的拥塞窗口来动态调整.
  4. 发送窗口和接受窗口相等吗?接收方在发送确认报文的时候,会告诉发送发自己的接收窗口大小,而发送方的发送窗口会据此来设置自己的发送窗口,但这并不意味着他们就会相等。首先接收方把确认报文发出去的那一刻,就已经在一边处理堆在自己缓存区的数据了,所以一般情况下接收窗口 >= 发送窗口。

2.5 阻塞控制

阻塞控制和流量控制虽然采取的动作很相似,但拥塞控制与网络的拥堵情况相关联,而流量控制与接收方的缓存状态相关联。

2.6 UDP并发编程

源码路径:
https://github.com/wangbojing/udp_server_concurrent

UDP如何可靠,KCP协议在哪些方面有优势

以10%-20%带宽浪费的代价换取了比 TCP快30%-40%的传输速度。
RTO翻倍vs不翻倍:
TCP超时计算是RTOx2,这样连续丢三次包就变成RTOx8了,十分恐怖,而KCP启动快速模式后不x2,只是x1.5(实验证明1.5这个值相对比较好),提高了传输速度。
以RTO=100ms为例:

选择性重传 vs 全部重传:
TCP丢包时会全部重传从丢的那个包开始以后的数据,KCP是选择性重传,只重传真正丢失的数据包。

快速重传(跳过多少个包马上重传)(如果使用了快速重传,可以不考虑RTO)):
发送端发送了1,2,3,4,5几个包,然后收到远端的ACK: 1, 3, 4, 5,当收到ACK3时,KCP
知道2被跳过1次,收到ACK4时,知道2被跳过了2次,此时可以认为2号丢失,不用等超时,直接重传2号包,大大改善了丢包时的传输速度。 fastresend =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精讲-名词说明

  • kcp官方:https://github.com/skywind3000/kcp
  • 名词说明
    用户数据:应用层发送的数据,如一张图片2Kb的数据
    MTU:最大传输单元。即每次发送的最大数据,1500 实际使用1400
    RTO:Retransmission TimeOut,重传超时时间。
    cwnd: congestion window,拥塞窗口,表示发送方可发送多少个KCP数据包。与接
    收方窗口有关,与网络状况(拥塞控制)有关,与发送窗口大小有关。
    rwnd: receiver window,接收方窗口大小,表示接收方还可接收多少个KCP数据包
    snd_queue: 待发送KCP数据包队列
    snd_nxt:下一个即将发送的kcp数据包序列号
    snd_una:下一个待确认的序列号,即是之前的包接收端都已经收到。

kcp使用方式

  1. 创建 KCP对象:ikcpcb *kcp = ikcp_create(conv, user);
  2. 设置发送回调函数(如UDP的send函数):kcp->output = udp_output;
  3. 真正发送数据需要调用sendto
  4. 循环调用 update:ikcp_update(kcp, millisec);
  5. 输入一个应用层数据包(如UDP收到的数据包):kcp_input(kcp,received_udp_packet,received_udp_size);
  6. 我们要使用recvfrom接收,然后扔到kcp里面做解析
  7. 发送数据:ikcp_send(kcp1, buffer, 8); 用户层接口
  8. 接收数据:hr = ikcp_recv(kcp2, buffer, 10)

kcp源码流程图

kcp配置模式

  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, 10, 0, 0);
    普通模式: ikcp_nodelay(kcp, 0, 10, 0, 1);关闭流控等
    极速模式: ikcp_nodelay(kcp, 2, 10, 2, 1),并且修改kcp1->rx_minrto = 10;kcp1->fastresend = 1;

  2. 最大窗口:int ikcp_wndsize(ikcpcb *kcp, int sndwnd, int rcvwnd);
    该调用将会设置协议的最大发送窗口和最大接收窗口大小,默认为32,单位为包。

  3. 最大传输单元:int ikcp_setmtu(ikcpcb *kcp, int mtu);
    kcp协议并不负责探测 MTU,默认 mtu是1400字节

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

kcp协议头

  • [0,3]conv:连接号。UDP是无连接的,conv用于表示来自于哪个客户端。对连接的一种替代
  • [4]cmd:命令字。如,IKCP_CMD_ACK确认命令,IKCP_CMD_WASK接收窗口大小询问命令,IKCP_CMD_WINS接收窗口大小告知命令,
  • [5]frg:分片,用户数据可能会被分成多个KCP包,发送出去
  • [6,7]wnd:接收窗口大小,发送方的发送窗口不能超过接收方给出的数值
  • [8,11]ts:时间序列
  • [12,15]sn:序列号
  • [16,19]una:下一个可接收的序列号。其实就是确认号,收到sn=10的包,una为11
  • [20,23]len:数据长度
  • data:用户数据,这一次发送的数据长度
cmd 作用
IKCP_CMD_PUSH 数据推送命令
IKCP_CMD_ACK 确认命令
IKCP_CMD_WASK 接收窗口大小询问命令
IKCP_CMD_WINS 接收窗口大小告知命令
IKCP_CMD_PUSH和IKCP_CMD_ACK 关联
IKCP_CMD_WASK和IKCP_CMD_WINS 关联
IKCP_CMD_ACK 是确认单独的包。

kcp发送数据过程

接收窗口

snd_wnd:固定大小,默认32
rmt_wnd:远端接收窗口大小,默认32
cwnd:滑动窗口,可变,越小一次能发送的数据越小
接收窗口的控制是:recv_queue的接收能力,比如默认接收端口为32,如果recv_queue接收了32个包后则接收窗口为0,然后用户读走了32个包,则接收窗口变为32。
IKCP_CMD_PUSH 命令

kcp确认包处理流程

kcp快速确认

ikcp_input逻辑

应答列表acklist

  • 收到包后将序号,时间戳存储到应答列表。
    在ikcp_input函数调用ikcp_ack_push存储应答包ikcp_ack_push(kcp, sn, ts); // 对该报文的确认 ACK 报文放入 ACK 列表中
  • 发送应答包
    在ikcp_flush函数发送应答包
  • 应答包解析
    在ikcp_input函数进行解析,判断IKCP_CMD_ACK

流量控制和拥塞控制

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

探测对方接收窗口

ikcp_flush 发送探测窗口IKCP_CMD_WASK

ikcp_input函数
cmd == IKCP_CMD_WASK,标记kcp->probe |= IKCP_ASK_TELL;
ikcp_flush 回应探测IKCP_CMD_WINS

推荐一个零声学院免费教程,个人觉得老师讲得不错,
分享给大家:[Linux,Nginx,ZeroMQ,MySQL,Redis,
fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,
TCP/IP,协程,DPDK等技术内容,点击立即学习:
服务器
音视频
dpdk
Linux内核

posted @ 2022-11-21 00:47  飘雨的河  阅读(1401)  评论(0编辑  收藏  举报