网络编程-UDP可靠传输(KCP)

KCP是一种快速可靠的协议,旨在通过牺牲10%-20%的带宽来换取比TCP快30%-40%的传输速度。它适用于实时应用如音视频通话、游戏和物联网。KCP提供了灵活的配置模式,如nodelay模式,以减少延迟。KCP的发送和接收流程涉及数据分片、拥塞控制和重传机制,确保数据的正确传输。与TCP不同,KCP的拥塞控制和流控更注重速度而非流量。此外,KCP的数据结构和工作原理,包括IKCPSEG和IKCPCB,使得它能够实现高效的数据管理和传输。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

引言


UDP与TCP如何选择?

选项UDPTCP
是否连接无连接面向连接
是否可靠不可靠传输,不使用流量控制和拥塞控制可靠传输,使用流量控制和拥塞控制
连接对象个数支持一对一,一对多,多对一和多对多交互通信只能是一对一通信
传输方式面向报文面向字节流
首部开销首部开销小,仅8字节首部最小20字节,最大60字节
适用场景适用于实时应用(IP电话、视频会议、直播等)游戏行业、物联网行业适用于要求可靠传输的应用,例如文件传输

udp场景总结:

  • 实时性要求,例如音视频通话、游戏
  • 节省资源的要求:嵌入式设备(电池供电),手机日志上报

如果做到可靠性传输?

  • ACK机制
  • 重传机制
  • 序号机制
  • 重排机制
  • 窗口机制

tcp不需要我们管,因为tcp自带这些机制,保证数据可靠性。

upd要实现这5种机制必须要用户层处理。

kcp简介


        KCP是⼀个快速可靠协议,能以⽐ TCP浪费10%-20%的带宽的代价,换取平均延迟降低30%-40%,且最⼤延迟降低三倍的传输效果。纯算法实现,并不负责底层协议(如UDP)的收发,需要使⽤者⾃⼰定义下层数据包的发送⽅式,以 callback的⽅式提供给 KCP。 连时钟都需要外部传递进来,内部不会有任何⼀次系统调⽤。
        整个协议只有 ikcp.h, ikcp.c两个源⽂件,可以⽅便的集成到⽤户⾃⼰的协议栈中。也许你实现了⼀个P2P,或者某个基于UDP的协议,⽽缺乏⼀套完善的ARQ可靠协议实现,那么简单的拷⻉这两个⽂件到现有项⽬中,稍微编写两⾏代码,即可使⽤。

名词说明(源码字段):

  • ⽤户数据(mss):应⽤层发送的数据,如⼀张图⽚2Kb的数据
  • MTU:最⼤传输单元。即每次发送的最⼤数据
  • RTO:Retransmission TimeOut,重传超时时间。
  • cwnd:congestion window,拥塞窗⼝,表示发送⽅可发送多少个KCP数据包。与接收⽅窗⼝有关,与⽹络状况(拥塞控制)有关,与发送窗⼝⼤⼩有关。
  • rwnd:receiver window,接收⽅窗⼝⼤⼩,表示接收⽅还可接收多少个KCP数据包
  • snd_queue:待发送KCP数据包队列
  • snd_nxt:下⼀个即将发送的kcp数据包序列号
  • snd_una:下⼀个待确认的序列号

技术特性

        TCP是为流量设计的(每秒内可以传输多少KB的数据),讲究的是充分利⽤带宽。⽽ KCP是为流速设计的(单个数据包从⼀端发送到⼀端需要多少时间),以10%-20%带宽浪费的代价换取了⽐ TCP快30%-40%的传输速度。TCP信道是⼀条流速很慢,但每秒流量很⼤的⼤运河,⽽KCP是⽔流湍急的⼩激流。KCP有正常模式和快速模式两种,通过以下策略达到提⾼流速的结果:

  • RTO翻倍vs不翻倍:

TCP超时计算是RTOx2,这样连续丢三次包就变成RTOx8了,⼗分恐怖,⽽KCP启动快速模式后不x2,只是x1.5(实验证明1.5这个值相对⽐较好),提⾼了传输速度

  • 选择性重传 vs 全部重传:

TCP丢包时会全部重传从丢的那个包开始以后的数据,KCP是选择性重传,只重传真正丢失的数据包。(TCP同样有选择重传SACK,但有区别,后续⽂章再介绍)。

  • 快速重传(跳过x个包立刻重传):

发送端发送了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是否延迟发送可以调节。kcp的ack是一个list。

  • UNA vs ACK+UNA:

ARQ模型响应有两种,UNA(此编号前所有包已收到,如TCP)和ACK(该编号包已收到),光⽤UNA将导致全部重传,光⽤ACK则丢失成本太⾼,以往协议都是⼆选其⼀,⽽ KCP协议中,除去单独的 ACK包外,所有包都有UNA信息

  • ⾮退让流控:

KCP正常模式同TCP⼀样使⽤公平退让法则,即发送窗⼝⼤⼩由:发送缓存⼤⼩、接收端剩余接收缓存⼤⼩、丢包退让及慢启动这四要素决定。但传送及时性要求很⾼的⼩数据时,可选择通过配置跳过后两步,仅⽤前两项来控制发送频率。以牺牲部分公平性及带宽利⽤率之代价,换取了开着BT都能流畅传输的效果

逻辑流程图

使用方式

  1. 创建 KCP对象: ikcpcb *kcp = ikcp_create(conv, user);
  2. 设置传输回调函数(如UDP的send函数)

    kcp->output = udp_output;

    真正发送数据需要调用sendto

  3. 循环调用 update: ikcp_update(kcp, millisec);
  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_send -> kcp_update(loop) -> sendto(kcp分片header + 用户数据)

接受流程:recvfrom -> kcp_input(放到snd_buf)-> kcp_recv(真正的用户数据)

KCP的配置模式

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

⼯作模式

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代表关闭。

example:

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

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

最⼤窗⼝

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

该调⽤将会设置协议的最⼤发送窗⼝和最⼤接收窗⼝⼤⼩,默认为32/128. 这个可以理解为 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;

KCP数据结构

数据包结构(IKCPSEG)

KCP中只有⼀种数据包格式,不管是数据还是信令,都使⽤相同的结构与头。KCP头中每⼀个字段的意义如下:

  • Conv,32bit,4Byte

为⼀个表示会话编号的整数,和TCP的 conv⼀样,通信双⽅需保证 conv相同,相互的数据包才能够被接受。conv唯⼀标识⼀个会话,但通信双⽅可以同时存在多个会话。

  • cmd,8bit,1Byte

⽤来区分分⽚的作⽤。

  • IKCP_CMD_PUSH:数据分⽚;
  • IKCP_CMD_ACK:ack分⽚;
  • IKCP_CMD_WASK:请求告知窗⼝⼤⼩;
  • IKCP_CMD_WINS:告知窗⼝⼤⼩。
  • frag,8bit,1Byte

⽤户数据可能会被分成多个KCP包发送,frag标识segment分⽚ID(在message中的索引,由⼤到⼩,0表示最后⼀个分⽚)。

  • wnd,16bit,2Byte

剩余接收窗⼝⼤⼩(接收窗⼝⼤⼩-接收队列⼤⼩),发送⽅的发送窗⼝不能超过接收⽅给出的数值。

  • ts,32bit,4Byte

message发送时刻的时间戳

  • sn,32bit,4Byte

message分⽚segment的序号,按1累次递增。

  • una,32bit,4Byte

待接收消息序号(接收滑动窗⼝左端)。对于未丢包的⽹络来说,una是下⼀个可接收的序号,如收到sn=10的包,una为11。

  • len,32bit,4Byte

数据⻓度。

除了上述的包结构的字段外,还定义了⼏个⾮常重要的变量:

  • resendts

下次超时重传的时间戳。

  • rto

该分⽚的超时重传等待时间,其计算⽅法同TCP。

  • fastack

收到ack时计算的该分⽚被跳过的累计次数,此字段⽤于快速重传,⾃定义需要⼏次确认开始快速重传。

  • xmit

发送分⽚的次数,每发送⼀次加⼀。发送的次数对RTO的计算有影响,但是⽐TCP来说,影响会⼩⼀些,计算思想类似

IKCPCB结构

IKCPCB是KCP中最重要的结构,也是在会话开始就创建的对象,代表着这次会话,所以这个结构体体现了⼀个会话所需要涉及到的所有组件。

  • conv:标识这个会话;
  • mtu:最⼤传输单元,默认数据为1400,最⼩为50;
  • mss:最⼤分⽚⼤⼩,不⼤于mtu;
  • state:连接状态(0xFFFFFFFF表示断开连接);
  • snd_una:第⼀个未确认的包;
  • snd_nxt:下⼀个待分配的包的序号;
  • rcv_nxt:待接收消息序号。为了保证包的顺序,接收⽅会维护⼀个接收窗⼝,接收窗⼝有⼀个起始序
  • rcv_nxt(待接收消息序号)以及尾序号 rcv_nxt + rcv_wnd(接收窗⼝⼤⼩);
  • ssthresh:拥塞窗⼝阈值,以包为单位(TCP以字节为单位);
  • rx_rttval:RTT的变化量,代表连接的抖动情况;
  • rx_srtt:smoothed round trip time,平滑后的RTT;
  • rx_rto:由ACK接收延迟计算出来的重传超时时间;
  • rx_minrto:最⼩重传超时时间;
  • snd_wnd:发送窗⼝⼤⼩;
  • rcv_wnd:接收窗⼝⼤⼩;
  • rmt_wnd:远端接收窗⼝⼤⼩;
  • cwnd:拥塞窗⼝⼤⼩;
  • probe:探查变量,IKCP_ASK_TELL表示告知远端窗⼝⼤⼩。IKCP_ASK_SEND表示请求远端告知窗⼝⼤⼩;
  • interval:内部flush刷新间隔,对系统循环效率有⾮常重要影响;
  • ts_flush:下次flush刷新时间戳;
  • xmit:发送segment的次数,当segment的xmit增加时,xmit增加(第⼀次或重传除外);
  • rcv_buf:接收消息的缓存;
  • nrcv_buf:接收缓存中消息数量;
  • snd_buf:发送消息的缓存;
  • nsnd_buf:发送缓存中消息数量;
  • rcv_queue:接收消息的队列
  • nrcv_que:接收队列中消息数量;
  • snd_queue:发送消息的队列;
  • nsnd_que:发送队列中消息数量;
  • nodelay:是否启动⽆延迟模式。⽆延迟模式rtomin将设置为0,拥塞控制不启动;
  • updated:是否调⽤过update函数的标识;
  • ts_probe:下次探查窗⼝的时间戳;
  • probe_wait:探查窗⼝需要等待的时间;
  • dead_link:最⼤重传次数,被认为连接中断;
  • incr:可发送的最⼤数据量;
  • acklist:待发送的ack列表;
  • ackcount:acklist中ack的数量,每个ack在acklist中存储ts,sn两个量;
  • ackblock:2的倍数,标识acklist最⼤可容纳的ack数量;
  • user:指针,可以任意放置代表⽤户的数据,也可以设置程序中需要传递的变量;
  • buffer:存储消息字节流;
  • fastresend:触发快速重传的重复ACK个数;
  • nocwnd:取消拥塞控制;
  • stream:是否采⽤流传输模式;
  • logmask:⽇志的类型,如IKCP_LOG_IN_DATA,⽅便调试;
  • output udp:发送消息的回调函数;
  • writelog:写⽇志的回调函数。

kcp发送/接收数据过程

发送

引出问题:

  • 为什么需要send_queue?
  • 为什么需要snd_buf?

send_queue用于告诉kcp要上传哪些分片,kcp_update就是从send_queue获取分片,然后执行send callback。

snd_buf主要用于暂时保存发送的数据,数据发送时从send_queue放入buf中。发送完成后获取ack时,如果需要重传,从snd_buf中找到指定的分片,进行重传。已经确认被收到的分片数据会从snd_buf删除。btw,ack确认的优先级是先确认una,再确认ack。

接收

引出问题:

  • 为什么需要rcv_buf?
  • 为什么需要rcv_queue?

rcv_buf保存的数据是从recv_from中获取的数据,这个数据是原始数据,kcp通过将接收的数据暂时保存在rcv_buf中,方便做数据过滤与清洗,以及重排序等操作。

rcv_queue主要保存已经排好序且过滤好以后的数据。是返回给用户的真实数据。

引申问题

  • sendto每次发送多长的数据?

mtu为一帧数据最大传输单元,一般为1400。但是kcp发送数据的时候是kcp分片header(24字节) + 用户数据(mss)。所以一帧可以发送的数据为 mtu - header = 1376字节

  • ikcp_send可以发送多大长度的数据?

发送窗口(cwnd) * (mtu - kcp header) = 最大可发送数据,例如128 * 1376 =  176,12

代码分析

https://download.youkuaiyun.com/download/u012173846/80494675

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值