kcp协议简单分析

kcp项目主页c版, go版. go版编码风格略显诡异,一些简单逻辑的实现感觉有些绕,上层的sess目测不是太实用。

kcp只是一个算法上的实现,力求在保证可靠性的情况下提高传输速度,协议的关注点主要在控制数据的可靠性和提高传输速度上面,并没有规定下层传输协议,一般用udp。因为kcp采用的拥塞控制,slow start, 快速重传,选择性重传等这些算法都像是对tcp协议类似算法一个相对简单的实现,而tcp通过增大初始拥塞窗口,禁用慢启动,启动窗口缩放,sack等优化,也可以显著提升效率。两者的性能对比没有做过,有时间可以试一试。

简读源码时画的一个图以及几个主要方法的逻辑,nodelay ack,拥塞,slow start,快速、选择性重传都还体现的挺明显的。

kcp

Send逻辑

以mss为依据对用户数据分segment.

  • 消息模式,数据分片赋予独立id,依次放入snd_queue,接收方按照id解分片数据,分片大小<=mss
  • 流模式,检测上一个分片是否达到mss,如未达到则填充,利用率高一些

Flush逻辑

  1. 发送缓存的ack,处理bufferbloat引发的jitter问题,recv_nxt是接收窗口左边沿,之前的数据完备,无需发送ack
  2. rmt_wnd如果为0,则暂停数据发送,同时更新查询窗口大小等待时间
  3. 计算拥塞窗口大小cwnd,有发送窗口的时候,snd_nxt > snd_una+cwnd, snd_queue数据转移到snd_buff, snd_nxt 不断右移
  4. 遍历send_buf中的segment
    • xmit为0,第一次发送,赋值rto及resendts
    • 超过segment重发时间,却仍在send_buf中,说明长时间未收到ack,认为丢失,重发
    • 达到快速重传阈值,重新发送
  5. 如xmit大于dead_link阈值则断开连接
  6. 如发生快速重传,将慢启动阈值调整为当前发送窗口的一半,将拥塞窗口调整为ssthresh + resent,resent是触发快速重传的丢包的次数,resent的值代表的意思在被弄丢的包后面收到了resent个数的包的ack。这样调整后kcp就进入了拥塞控制状态。

Input逻辑

  1. 按kcp包格式解析数据,得到kcp包,更新rmt_wnd为远端发送窗口大小
  2. 解析segment中的una(接收窗口左侧,左侧之前的数据都已完备,无需单独发ack确认), 删除send_buf中小于una的segment
  3. 更新本地snd_una数据,如snd_buff为空,snd_una指向snd_nxt,否则指向send_buff首端
  4. segment解析
    • ack包
      • sn小于snd_una或大于等于snd_nxt,忽略该包,snd_una之前是完备的,snd_nxt之后未发送,不应收到ack
      • 由send_buf中剔除sn对应的segment
      • send_buf可能更新,更新send_una,记录最大的ack snd值,及最新的时间戳
    • push数据包
      • sn在接收窗口外,丢弃,认为是重复的数据包
      • 缓存ack到ack_list,解析数据包,数据在接收窗口内,检测是否为重复数据包,插入rcv_buf
      • 扫描rcv_buf,segment的id等于rcv_nxt,则rcv_nxt右移,同时segment移出rcv_buff移入rcv_queue,rcv_nxt的连续性保证rcv_queue的完备性
    • wask询问窗口大小
    • wins告知窗口大小
  5. 根据记录的最大ack的snd值,扫描snd_buff,小于max ack的segment的fastack ++,超过指定阈值,启动快速重传
  6. snd_una于远端una比较,snd_una>una,有未确认的segment,可能存在网络过载, 通过cwnd拥塞窗口控制
    • cwnd < ssthresh, 慢启动阶段,cwnd++,可发送最大数据量+mss
    • 拥塞控制阶段
  7. 在ack无延迟发送模式下或者远端接收窗口为0的情况下,立刻发送ack