KCP协议完全指南:在UDP上再造一个更快的“TCP”

一.TCP的简单回顾

TCP(传输控制协议):在任何网络条件下,优先保证数据的完整、有序、不丢失,而不是传输速度

1. 重传机制:可靠性的核心保障

这是TCP确保数据不丢失的根本。其核心逻辑是:发送的每个数据段都必须得到接收方的确认(ACK),否则认为丢失并重传

  • 超时重传 :发送方为每个发出的数据段启动一个定时器。如果在**重传超时时间(RTO)**​ 内未收到ACK,则重传该数据。RTO值根据网络往返时间动态计算,但初始值通常较大(如1秒),且超时后会以指数退避方式加倍,导致重传延迟急剧上升。

  • 快速重传 :一种优化。当接收方收到一个乱序数据段时,会立即重复发送最后一个按序数据的ACK。如果发送方连续收到3个重复ACK ,则推断该数据段已丢失(而非网络延迟),会立即重传而不等待超时,这比超时重传快得多。

对实时应用的影响:即使有快速重传,重传机制本身就会引入至少一个RTT的延迟。在丢包率高的网络中,频繁重传和保守的RTO调整会导致延迟剧烈波动,这是游戏卡顿的主要原因之一。

2. 流量控制:匹配收发双方的速度

目的是防止发送方发送过快,导致接收方缓冲区溢出。这是点对点的速度匹配。

  • 实现方式 :通过滑动窗口协议 实现。接收方在每次ACK中都会通告自己的接收窗口大小 ,即剩余缓冲区容量。发送方的发送窗口不能超过这个通告窗口。

  • 工作原理:就像一个可滑动的数据窗口,窗口内的数据可以连续发送。当窗口最前端的字节被确认后,窗口就向前"滑动",允许发送新的数据。

对实时应用的影响:流量控制本身是必要的,但若接收方应用层处理慢(如游戏逻辑卡顿),导致接收窗口变小,会反过来限制发送速率,可能加剧延迟。

3. 拥塞控制:维护网络整体的健康

目的是防止发送方发送过快,导致网络中间设备(如路由器)过载。这是发送方对网络公共资源的自律。

  • 核心算法:主要包括四个部分,其目标是探测并利用网络的可用带宽,同时在拥塞发生时快速退让。

    1. 慢启动:连接开始时或重传超时后,从一个很小的拥塞窗口开始,每收到一个ACK,窗口大小就增加(指数增长),快速探测可用带宽。

    2. 拥塞避免 :当窗口增长到慢启动阈值后,转为每RTT只增加约1个数据段(线性增长),谨慎接近网络瓶颈。

    3. 拥塞发生时的反应

      • 发生超时重传:TCP认为网络拥塞严重,将慢启动阈值设为当前窗口的一半,并将拥塞窗口重置为1,重新进入慢启动(反应非常剧烈)。

      • 触发快速重传 :TCP执行快速恢复,将窗口减半,然后进入拥塞避免阶段(反应相对温和)。

  • 核心思想AIMD(加性增,乘性减)。网络正常时缓慢增加速率(加性增),一旦发现丢包(视为拥塞信号)就大幅降低速率(乘性减)。

对实时应用的关键影响TCP将任何丢包都视为网络拥塞的信号 。但在无线网络等环境中,丢包可能只是随机错误而非拥塞。TCP此时仍会机械地执行"乘性减",导致传输速率不必要的骤降,需要很长时间才能恢复,造成吞吐量下降和延迟增加。这是TCP在移动网络或弱网环境下性能不佳的最主要原因

二.KCP的优势

KCP协议就是在保留UDP快的基础上,提供可靠的传输,应用层使用更加简单------TCP可靠简单,但是复杂无私,所以速度慢。KCP尽可能保留UDP快的特点下,保证可靠。

  • TCP是为流量设计的(每秒内可以传输多少KB的数据),讲究的是充分利用带宽。
  • KCP是为流速设计的(单个数据包从一端发送到一端需要多少时间),以10%-20%带宽浪费的代价换取了比 TCP快30%-40%的传输速度。

TCP信道是一条流速很慢,但每秒流量很大的大运河,而KCP是水流湍急的小激流。

MOBA类和"吃鸡"游戏多使用帧同步为主要同步算法,竞技性也较高,无论从流畅性,还是从公平性要求来说,对响应延迟的要求都最高,根据业内经验,当客户端与 服务器 网络延迟 超过150ms时,会开始出现卡顿,当延迟超过250ms时,会对玩家操作造成较大影响,游戏无法公平进行。类似地,"吃鸡"游戏(如《绝地求生》)玩法对玩家坐标、动作的同步要求极高,延迟稍大导致的数据不一致对体验都会造成较大影响,其实时性要求接近MOBA类游戏。而对于传统mmorpg来说,多采用状态同步算法,以属性养成和装备获取为关注点,也有一定竞技性,出于对游戏流畅性的要求,对延迟也有一定要求,同步算法的优化程度不一样,这一要求也不一样,一般情况下为保证游戏正常进行,需要响应延迟保持在300ms以下。相比之下,对于炉石传说、斗地主、梦幻西游等回合制游戏来说,同时只有一个玩家在操作双方数据,无数据竞争,且时间粒度较粗,甚至可通过特效掩盖延迟,因此对网络延迟的要求不高,即便延迟达到500ms~1000ms,游戏也能正常进行

不同传输层协议在可靠性、流量控制等方面都有差别,而这些技术细节会对延迟造成影响。

tcp追求的是完全可靠性和顺序性,丢包后会持续重传直至该包被确认,否则后续包也不会被上层接收,且重传采用指数避让策略,决定重传时间间隔的RTO(retransmission timeout)不可控制,linux内核实现中最低值为200ms,这样的机制会导致丢包率短暂升高的情况下应用层消息响应延迟急剧提高,并不适合实时性高、网络环境复杂的游戏。

基于udp定制传输层协议,引入顺序性和适当程度或者可调节程度的可靠性,修改流控算法。适当放弃重传

三.KCP与TCP的区别

1.协议层次

TCP 是 OSI 第四层(传输层) 的标准协议,直接运行在 IP 协议之上。它的实现深埋在操作系统内核中,所以说对于慢启动的初始窗口、RTO 的计算方式、ACK 的延迟策略,都是内核写死的。

TCP针对普遍的网络环境是最优解,但是针对一些比较特殊弱网环境,那么TCP却不是最优解。

KCP一个纯算法库 ------一段可以编译进你项目的 C 代码,运行在用户态 ,封装在 UDP 之上

所以说你可以轻易的根据具体状况配置合适的参数。

2.拥塞控制

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

但是RTO和RTT这一块相比TCP有以下不同

|-------------|---------------------------|-------------------------------------|
| | TCP | KCP |
| RTO 最小值 | 内核硬限制,Linux 默认约 200ms | interval 可配,典型值低至 10ms 甚至 5ms |
| 超时退避 | RTO*2 | RTO * 1.5 |
| 参数可调性 | 内核态实现 | 应用层实现可配置 |

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

KCP有正常模式和快速模式两种,通过以下策略达到提高流速的结果:

  • 普通模式/正常模式: ikcp_nodelay(kcp, 0, 40, 0, 0);
  • 极速模式/快速模式: ikcp_nodelay(kcp, 1, 10, 2, 1);

3.重传机制

超时重传

当发送端的报文没有被 una 或者 IKCP_CMD_ACK 确认收到时,定时器超时后触发超时重传:

  • 如果没有开启 nodelay,则每次超时 rto <-- 2*rto,即每次翻 2 倍
  • 如果开启 nodelay,则每次超时 rto <-- 1.5*rto,即每次翻 1.5 倍

快速重传

kcp 支持配置触发快速重传的次数 fastlimit

实际上 kcp 基本上都是在进行选择性重传,即 kcp 接收端会对每个收到的报文构造一个确认报文(IKCP_CMD_ACK)并发送出去,被确认的报文会被标记已确认。剩下未被确认收到的报文,当 una 超过 fastlimit 次没有确认该报文,那么此报文就会被执行快速重传。

但是对于等待重传的包,kcp可以进行配置主动的丢包丢弃

要实现主动丢包特性,可以做如下修改:

  • 为 kcp 对象添加一个 rcv_buf_time 的参数,限制每个在 rcv_buf 中报文的最大缓存时间
  • 为报文对象添加一个rcv_ts 参数,标识接收端收到此报文段时的当前时间戳,即接收时间戳
  • 每次 kcp 循环检查 rcv_buf 时,都可以判断当前时间戳与接收时间戳的差值是否在最大缓存时间内
  • 对于缓存超时的报文,可以向前移动 rcv_next 指针以表示跳过某个丢失的报文段;否则继续等待包到达
  • rcv_buf_time 最大缓存时间存在的意义是:sn=101 号包不会一直等待 sn=100 号包到达,等待超时后,允许丢弃前面的包,让用户层尽快读取到 sn=101 号包

4.流量控制

KCP一样为了保证接收方的能及时接收数据,发送方会缓存一个发送队列,但是对于单位来说,TCP是字节为单位,而KCP 是以包为单位的(避免了粘包问题),并且KCP可以控制窗口大小

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

一般大小是默认是32(包)

然后对于MTU(最大传输单元)默认是1400

ikcp_setmtu

5.KCP协议报文

  1. conv --- 会话编号(4 Bytes)
  • 作用:Connection ID,标识一条 KCP 连接。

  • 细节 :KCP 基于 UDP,UDP 本身无连接,conv 就是 KCP 自己实现的"连接标识"。发送端和接收端通过相同的 conv 值来识别"这是属于我们俩的对话"。

  • 类比 :TCP 用四元组(源IP、源端口、目的IP、目的端口)标识连接;KCP 用一个 32 位的 conv 就够了。

  1. cmd --- 命令类型(1 Byte)
  • 作用:标识这个报文是什么类型的命令。

  • 取值

    • IKCP_CMD_PUSH = 81(0x51):数据推送,携带应用层数据。

    • IKCP_CMD_ACK = 82(0x52):确认包,告诉对方"我收到 SN = xxx 了"。

    • IKCP_CMD_WASK = 83(0x53):窗口探测(Window Ask),主动询问对方当前接收窗口大小。

    • IKCP_CMD_WINS = 84(0x54):窗口响应(Window Answer),回复自己的接收窗口大小。

  • 细节:一个 KCP 报文可以同时是 PUSH + ACK(KCP 会把 ACK 信息附在数据报文里捎带过去,提高带宽利用率)。

  1. frg --- 分片编号(1 Byte)
  • 作用 :标识这是消息的第几个分片,用于消息重组

  • 细节

    • 当用户调用 ikcp_send(data, 3000) 时,如果数据超过 MSS(约 1376 字节),KCP 会把它切成多个 segment。

    • frg(分片总数 - 1) 递减到 0

    • frg = 0 表示这是该消息的最后一个分片

    • 接收端只有收齐从某个 frg 递减到 0 的所有分片后,才会把完整消息交给 ikcp_recv

  • 例子 :3000 字节切成 3 段,三个报文的 frg 分别是 210

  1. wnd --- 窗口大小(2 Bytes)
  • 作用接收窗口剩余大小,用于流量控制。

  • 细节

    • 发送方在发数据时,会把自己的 rcv_wnd(接收窗口剩余容量)填在这个字段里。

    • 对方收到后,就知道"你还能接收多少数据",从而控制自己的发送窗口 snd_wnd

    • 这是 KCP 滑动窗口流量控制的核心字段。

  1. ts --- 时间戳(4 Bytes)
  • 作用 :发送该报文时的本地时间戳(通常是毫秒级),用于RTT 计算

  • 细节

    • 接收方收到后,会在 ACK 报文中原封不动地把这个 ts 带回来。

    • 发送方收到 ACK 后,用 当前时间 - ts 就能算出 RTT。

    • 这是 KCP 计算 RTO(重传超时)的基础。

  1. sn --- 序列号(4 Bytes)
  • 作用 :Segment Sequence Number,报文的唯一序号

  • 细节

    • 发送方单调递增分配(snd_nxt++)。

    • 接收方用 sn 来检测重复、乱序和丢包。

    • ACK 报文中也用这个 sn 来告诉对方"我收到的是哪一号"。

  1. una --- 未确认序号(4 Bytes)
  • 作用 :Unacknowledged Sequence Number,累积确认

  • 细节

    • 表示"所有 SN < una 的报文我都已经收到了"。

    • 发送方收到 una 后,可以把 snd_buf 中所有 sn < una 的 segment 删除(释放缓冲区)。

    • 这是 KCP 可靠性传输的核心:即使某个 ACK 丢了,una 也能起到"隐式确认"的作用。

  1. len --- 数据长度(4 Bytes)
  • 作用 :本报文携带的应用层数据长度(不含 24 字节头)。

  • 细节

    • 如果 cmd = PUSHlen > 0,后面跟着 len 字节的数据。

    • 如果 cmd = ACK/WASK/WINSlen = 0,报文只有头没有数据。

    • 接收方根据 len 来解析 DATA 字段的边界。

四.实战测试

TCP与KCP都是采用事件驱动的模式来处理数据

KCP采用快速模式,并且用户态调度2ms

1.正常网络环境

bash 复制代码
== TCP test  ==
====================================
Protocol: TCP
Total Sent: 3000
Total Received: 3000
Lost: 0 (0.00%)
Avg RTT: 0.58 ms
Max RTT: 8.83 ms
Min RTT: 0.38 ms
P99 RTT: 1.45 ms
Jitter (stddev): 0.28 ms
====================================

== KCP test  ==
====================================
Protocol: KCP
Total Sent: 3000
Total Received: 3000
Lost: 0 (0.00%)
Avg RTT: 10.37 ms
Max RTT: 13.18 ms
Min RTT: 8.63 ms
P99 RTT: 11.00 ms
Jitter (stddev): 0.40 ms
====================================

正常网络下的基准测试:在理想网络环境中,TCP 的平均 RTT 为 0.58ms,KCP(interval=10ms)为 10.37ms。KCP 比 TCP 慢约 10ms,这主要是 KCP 用户态协议栈的调度开销。若将 interval 压缩至 1ms,KCP 的 RTT 可降至 2-4ms,但 CPU 占用会相应增加。在好网络下,KCP 的抖动(Jitter)与 TCP 几乎相同(0.28ms vs 0.40ms),且均无丢包,说明其性能损失在可接受范围内。

2.轻微丢包(5%loss)

bash 复制代码
== TCP test  ==
====================================
Protocol: TCP
Total Sent: 3000
Total Received: 3000
Lost: 0 (0.00%)
Avg RTT: 10.10 ms
Max RTT: 415.12 ms
Min RTT: 0.26 ms
P99 RTT: 207.74 ms
Jitter (stddev): 44.57 ms
====================================

== KCP test  ==
====================================
Protocol: KCP
Total Sent: 3000
Total Received: 3000
Lost: 0 (0.00%)
Avg RTT: 13.91 ms
Max RTT: 61.07 ms
Min RTT: 8.59 ms
P99 RTT: 41.13 ms
Jitter (stddev): 8.54 ms
====================================

5% 丢包下,TCP 的最大延迟飙升到 415ms,而 KCP 只有 61ms------两者相差近 7 倍。TCP 的 P99 延迟(207ms)是 KCP(41ms)的 5 倍。这意味着在弱网环境下,KCP 能让 99% 的请求保持在 50ms 以内响应,而 TCP 已经有 1% 的请求超过了 200ms。对于实时对战游戏来说,这就是"流畅"与"卡顿"的分界线。

3.模拟移动网络环境

bash 复制代码
sudo tc qdisc add dev eth0 root netem loss 2% delay 80ms 40ms
bash 复制代码
== TCP test  ==
====================================
Protocol: TCP
Total Sent: 3000
Total Received: 3000
Lost: 0 (0.00%)
Avg RTT: 86.74 ms
Max RTT: 615.10 ms
Min RTT: 40.58 ms
P99 RTT: 368.15 ms
Jitter (stddev): 48.17 ms
====================================

== KCP test  ==
====================================
Protocol: KCP
Total Sent: 3000
Total Received: 3000
Lost: 0 (0.00%)
Avg RTT: 116.00 ms
Max RTT: 264.96 ms
Min RTT: 53.46 ms
P99 RTT: 219.38 ms
Jitter (stddev): 31.52 ms
====================================

移动网络场景(2% 丢包 + 80ms 延迟 + 40ms 抖动) :在此场景下,TCP 的平均 RTT 为 86.74ms,KCP 为 116ms。KCP 的平均延迟比 TCP 高约 30ms,这主要来自用户态协议栈的 interval(10ms)调度开销。然而,KCP 将最大延迟从 TCP 的 615ms 压缩到 264ms ,P99 延迟从 368ms 降低到 219ms ,抖动从 48ms 降低到 31ms

这说明 KCP 的核心优势并非"绝对延迟最低",而是**"延迟更可预测"**。对于实时对战游戏而言,玩家对"偶尔卡顿 600ms"的敏感度,远高于"每局都稳定慢 30ms"。KCP 通过牺牲少量平均延迟,换取了尾部延迟的显著改善,这正是其在弱网环境下被游戏开发者青睐的原因。

五.总结

回顾全文,TCP 与 KCP 的分野,本质上是两种设计哲学的选择

TCP 诞生于互联网早期,它的设计目标是在复杂的公共网络中,最大化带宽利用率并保证绝对可靠 。为此,它把拥塞控制、流量控制、重传策略全部封装进内核,以"公平"和"稳"为第一要义。在文件传输、网页浏览、视频流媒体这些延迟不敏感、带宽敏感的场景中,TCP 至今仍是无可替代的最优解。

KCP 则走了另一条路。它不做"操作系统的一部分",而是做"你代码里的一部分"。它以 UDP 为底,在用户态重新实现了一套可靠传输算法,把控制权完全交给开发者 。通过更激进的 RTO 计算(×1.5 而非 ×2)、选择性重传、无延迟 ACK、以及可配置的窗口和时钟,KCP 在弱网、高丢包、高抖动的环境下,把"偶尔卡顿 600ms"变成了"稳定慢 30ms"。

相关推荐
上海合宙LuatOS13 小时前
LuatOS扩展库API——【libnet】TCP/UDP协议
物联网·tcp/ip·junit·udp·luatos
想唱rap13 小时前
UDP套接字编程
服务器·网络·c++·网络协议·ubuntu·udp
IpdataCloud14 小时前
游戏服务器选择,为何绕不开IP地址查询?
服务器·tcp/ip·游戏
_Evan_Yao15 小时前
对话的边界:HTTP 的克制,SSE 的流淌,WebSocket 的自由
java·后端·websocket·网络协议·http
小心我捶你啊16 小时前
VPS的主要用途,与其它方式的区别
服务器·网络协议·tcp/ip
郝学胜-神的一滴20 小时前
epoll 边缘触发 vs 水平触发:从管道到套接字的深度实战
linux·服务器·开发语言·c++·网络协议·unix
2501_9130613420 小时前
网络原理知识(6)
java·网络·网络协议·面试
被摘下的星星20 小时前
传输控制协议(TCP)
服务器·网络·tcp/ip
环流_21 小时前
网络原理-TCP协议
服务器·网络·tcp/ip