KCP协议详解:一个快速可靠的UDP上层协议
KCP (Fast and Reliable UDP protocol) 是一个由 skywind3000 (吴云) 在 2014 年开源的快速可靠的 UDP 上层协议。它的设计目标是在网络状况不佳(高延迟、高丢包率)的环境下,提供比 TCP 更快的传输速度和更低的延迟,同时保持数据的可靠性。KCP 并不是一个完整的网络协议栈,而是一个可嵌入式的库,它运行在 UDP 协议之上,提供了 TCP 所具备的可靠性、流量控制和拥塞控制等机制,但针对延迟和重传进行了优化。
核心思想:在保障数据可靠性的前提下,通过优化重传机制、激进发送和控制重传间隔等方法,尽可能地减少传输延迟,以适应游戏、实时音视频等对延迟高度敏感的应用。
一、为什么需要 KCP?
TCP 协议是互联网上最常用的可靠传输协议,但它在一些场景下存在明显的局限性:
- 慢启动 (Slow Start):TCP 为了避免网络拥塞,在连接建立初期会限制发送速率,逐渐增加。这对于短连接或突发数据传输会增加初始延迟。
- 队头阻塞 (Head-of-Line Blocking, HOLB):TCP 的报文是严格按序到达的。如果某个数据包丢失,后续所有已到达但序号更大的数据包必须在缓冲区中等待该丢失包重传成功并按序递交,从而造成延迟。
- 重传策略:TCP 的重传通常是基于定时器或三次重复 ACK。当数据包丢失时,需要等待较长时间才能触发重传,这在延迟敏感的应用中是不可接受的。
- 固定拥塞控制:TCP 的拥塞控制算法(如 Reno、Cubic)是为了普适场景设计的,对于某些特定应用(如游戏)可能过于保守,或者响应不够迅速。
UDP 虽然提供了极低的延迟和高度的灵活性,但它是不可靠的,不保证数据传输的顺序、完整性和不重复。
KCP 的出现就是为了结合 UDP 的低延迟优势,并克服 TCP 在特定场景下的不足,在一个可靠数据传输的基础上,提供:
- 更低的首次发送延迟:通过不依赖队列长度、直接发送等手段减少延迟。
- 更快的重传响应:通过更激进的 ACK 机制和更短的重传间隔实现。
- 在丢包率高的情况下性能更好:通过选择性重传等方式,减少队头阻塞。
因此,KCP 主要应用于:
- 实时在线游戏:对延迟要求极高,偶发丢包总比卡顿或长时间延迟好。
- 实时音视频通话:需要稳定的低延迟传输。
- 边缘计算、物联网数据传输:在网络质量不稳定的环境下需要可靠高效传输。
二、KCP 与 TCP 的关键差异与优化
KCP 借鉴了 TCP 的一些机制,但对其进行了激进的优化,以达到低延迟的目标:
2.1 队头阻塞优化
- TCP:严格的字节流模型,所有数据必须完全按序到达才能递交上层应用。一个丢包会导致所有后续包的阻塞。
- KCP:基于报文模型,每个报文都有自己的序号。KCP 仍然保证数据按序递交上层应用,但其重传机制允许接收方缓存乱序到达的报文,并在丢失报文到达后快速递交。
graph TD
subgraph TCP Head-of-Line Blocking
C1(Packet 1) -- OK --> S1(Buffer)
C2(Packet 2) -- OK --> S2(Buffer)
C3(Packet 3) -- Lost -->
C4(Packet 4) -- OK --> S4(Buffer - Waiting)
C5(Packet 5) -- OK --> S5(Buffer - Waiting)
S1 --> App(递交 App)
S2 --> App
S4 -.-> App(阻塞)
S5 -.-> App(阻塞)
subgraph Retransmission
R3(Packet 3 Resend) --> S3(Buffer)
end
S3 --> App(解除阻塞)
S4 --> App
S5 --> App
end
subgraph KCP Optimized for HOLB
K_C1(Packet 1) -- OK --> K_S1(Buffer)
K_C2(Packet 2) -- OK --> K_S2(Buffer)
K_C3(Packet 3) -- Lost -->
K_C4(Packet 4) -- OK --> K_S4(Buffer - Cached)
K_C5(Packet 5) -- OK --> K_S5(Buffer - Cached)
K_S1 --> K_App(递交 App)
K_S2 --> K_App
subgraph Retransmission
K_R3(Packet 3 Resend) --> K_S3(Buffer)
end
K_S3 --> K_App(递交 App)
K_S4 --> K_App(递交 App)
K_S5 --> K_App(递交 App)
end
2.2 ACK 与重传机制
- TCP:
- 累积 ACK:一个 ACK 确认它之前的所有数据包。
- 重传定时器:基于 RTT (Round Trip Time) 动态调整重传间隔。通常较长,避免不必要的重传。
- 三次重复 ACK:快速重传机制,避免等待定时器超时。
- KCP:
- 可选 ACK:除了累积 ACK 之外,KCP 还有一个叫
UNA(Unacked Number) 的机制,类似于选择性 ACK (SACK)。ACK 包中会带上当前已收到的报文序号,即使中间有丢包。 - 激进重传 (Turbo retransmission):
- 不仅使用超时重传,还使用快速重传。当发送方收到重复的 ACK (
ack_nodata) 且达到一定阈值(可通过参数配置,默认为 2 次)时,会立即重传对应的丢失报文。 - KCP 的重传定时器远小于 TCP,可以通过参数
ikcp_nodelay(1, 10, 2, 1)(nodelay=1, interval=10ms, resend=2, nc=1)开启激进模式。interval 是内部循环的刷新间隔,resend 是快速重传阈值,nc=1 代表关闭拥塞控制。
- 不仅使用超时重传,还使用快速重传。当发送方收到重复的 ACK (
- 小延迟 ACK:KCP 收到报文后不会立即发送 ACK,而是延迟一小段时间(默认 30ms),尝试将多个 ACK 打包到一个 UDP 包中发送,以节省带宽。但在
ikcp_nodelay(1, x, x, 1)模式下,这个延迟会减少或取消。
- 可选 ACK:除了累积 ACK 之外,KCP 还有一个叫
2.3 拥塞控制与流量控制
- TCP:严格的慢启动、拥塞避免、快速重传、快速恢复机制。旨在公平地占用带宽,保护网络。
- KCP:
- 可选拥塞控制:KCP 提供了多种拥塞控制模式,可以通过参数配置。在游戏等场景中,有时会选择关闭或弱化拥塞控制 (
ikcp_nodelay的nc参数)。 - 发送窗口 (Send Window):与 TCP 类似,限制飞行中(已发送未确认)的数据量,防止发送方过载接收方。
- 接收窗口 (Receive Window):限制接收方可接收的数据量,防止接收缓冲区溢出。
- 可选拥塞控制:KCP 提供了多种拥塞控制模式,可以通过参数配置。在游戏等场景中,有时会选择关闭或弱化拥塞控制 (
2.4 其他优化
- 非延迟 ACK (NoDelay ACK):TCP 的 ACK 通常有一个延时(用于合并 ACK)。KCP 允许立即发送 ACK,降低延迟。
- 流量整形 (Flow Shaping):KCP 的内部缓冲区管理允许更细粒度的流量控制。
- 可调参数:KCP 提供了大量的参数供开发者根据应用场景和网络环境进行调优,例如窗口大小、重传间隔、重传阈值、是否开启拥塞控制等。
三、KCP 的核心数据结构与 API
KCP 作为一个库,对外提供了一组 C 语言风格的 API,其核心是 ikcp_send, ikcp_recv, ikcp_update, ikcp_input, ikcp_flush 等函数。
3.1 核心数据结构:ikcpcb
1 | struct IKCPCB { |
3.2 核心 API (Go 语言示例,其他语言类似)
KCP 库本身是 C 语言实现,但有多种语言绑定,例如 Go 语言的 github.com/xtaci/kcp。
Dial()/Listen():建立 KCP 连接。Send(data []byte):发送数据。将数据添加到 KCP 内部的发送队列。Recv() ([]byte, error):接收数据。从 KCP 内部的接收队列中获取数据,如果队列为空则阻塞。Update(currentMs uint32):KCP 的核心驱动函数。需要外部定时调用 (通常每隔 10ms - 100ms),执行 KCP 内部协议逻辑,如超时检测、重传、ACK 处理、窗口更新、发送数据包等。这是一个非阻塞函数。Input(data []byte):将底层 UDP 接收到的数据包输入到 KCP 实例中。KCP 会解析包头,根据序号和类型进行处理。SetMtu(mtu int):设置最大传输单元。SetWindow(sndwnd, rcvwnd int):设置发送窗口和接收窗口大小。SetNoDelay(nodelay, interval, resend, nc int):设置延迟模式、更新间隔、快速重传阈值和是否禁用拥塞控制。
基本使用流程:
- 初始化:创建 KCP 实例 (
ikcp_create),设置会话 ID (conv),并设置一个回调output函数,该函数负责将 KCP 封装好的 UDP 数据包真正发送出去。 - 发送:调用
ikcp_send发送数据。 - 接收:底层 UDP 收到数据后,调用
ikcp_input将数据注入 KCP 实例。随后,上层应用通过ikcp_recv从 KCP 实例中读取数据。 - 驱动:在应用层启动一个定时器,每隔一定时间调用
ikcp_update来驱动 KCP 内部状态机,确保 ACK、重传、窗口更新等机制正常工作。
四、配置调优与模式选择
KCP 的一大特点就是其高度可配置性,允许开发者根据具体应用场景进行调优。
4.1 nodelay mode
ikcp_nodelay(1, 10, 2, 1) 是 KCP 最常用的性能模式,参数含义如下:
nodelay = 1:启用NoDelay模式。0:普通模式,ikcp_update的调用间隔会随着 RTT 变化,ACK 会延迟发送,更平稳但延迟高。1:启用非延迟模式,ACK 不延迟发送,ikcp_update的调用间隔固定为interval参数。2:极速模式,比 1 更激进,ACK 优先级更高。
interval = 10:内部时钟刷新间隔,单位毫秒。建议 10-100ms,过小会消耗 CPU 资源,过大会增加延迟。resend = 2:快速重传阈值。在接收方收到 2 个重复 ACK 后,发送方会立即重传该丢包。建议 2-5。nc = 1:是否禁用拥塞控制。0:启用拥塞控制。1:禁用拥塞控制。在游戏等场景,宁可多占带宽也要保证延迟时,可以禁用。但在公网环境下禁用拥塞控制可能导致网络拥塞加剧,应谨慎使用。
4.2 窗口大小
ikcp_wndsize(sndwnd, rcvwnd):
sndwnd:发送窗口大小。决定了最大允许在途的未确认数据包数量。越大吞吐量越高,但可能浪费带宽或需要更多内存。rcvwnd:接收窗口大小。决定了接收方能缓存的最大乱序数据包数量。越大能容忍的乱序程度越高,但同样需要更多内存。
4.3 MTU (Maximum Transmission Unit)
ikcp_setmtu(mtu):
- 通常 UDP MTU 是 1400 字节,如果 KCP 封装的包超过这个值,底层 UDP 层会进行分片,这会增加丢包率。
- 根据实际网络环境调整 MTU,避免 IP 层分片,例如设置为 1300-1400 字节。
五、KCP 的优缺点
5.1 优点
- 低延迟:通过激进重传、快速 ACK、可调参数等特性,在网络不佳时比 TCP 具有更低的平均延迟。
- 高吞吐量:在丢包率较高时,由于其优化的重传机制和队头阻塞处理,可以保持更好的吞吐量。
- 高度可配置:允许开发者根据特定应用场景(如游戏)进行深度调优。
- 轻量级、嵌入式:作为一个库,可以方便地嵌入到各种应用中,且资源开销相对较小。
- 消除队头阻塞:相比 TCP,乱序数据包的处理更有效,减少了等待重传包的时间。
- 无专利:完全开源,无任何专利限制。
5.2 缺点**
- 消耗带宽:激进重传和更频繁的 ACK 可能会在网络状况良好时导致带宽的额外消耗。
- 可能会加剧网络拥塞 (如果禁用拥塞控制):如果
nc=1,KCP 会不顾网络拥塞地发送数据,这在公网环境下可能对其他流量不公平,甚至加剧拥塞。 - 不保证公平性:与 TCP 相比,KCP 不是为了实现网络上的流量公平性而设计的,可能导致“抢占”带宽。
- 额外开发成本:KCP 只是一个库,开发者需要自行实现 UDP 绑定、并发处理、底层 socket 操作、多路复用等功能。
- 需要调优经验:KCP 的性能很大程度上依赖于合理的参数配置,这需要一定的专业知识和测试。
六、KCP 的使用和项目
KCP 已经被广泛应用于各种场景,特别是在游戏领域:
- 游戏服务器框架:如
unity-kcp(Unity 游戏引擎)、skynet(Lua 语言游戏服务器)。 - VPN/隧道代理:如
kcptun,利用 KCP 优化代理传输速度。 - Go 语言库:
github.com/xtaci/kcp是一个广受欢迎的 Go 语言 KCP 实现,与 Go 的协程机制结合紧密,提供了高性能的网络编程能力。 - 多种语言绑定:C++, Java, Python, C#, Rust 等都有 KCP 的实现或绑定。
示例 (Go 语言 xtaci/kcp 库):
1 | package main |
此 Go 语言示例展示了如何使用 xtaci/kcp-go 库创建一个简单的 KCP 服务器和客户端。服务器监听 KCP 连接,接受客户端消息并回复;客户端连接服务器,发送消息并接收回复。其中包含了 SetWindowSize、SetNoDelay、SetMtu 等关键 KCP 参数的配置,演示了如何开启激进模式。
七、总结
KCP 协议通过在 UDP 基础上实现一套激进的可靠传输机制,成功在一个可靠数据传输的前提下,达到了比 TCP 更低的延迟和在网络条件不佳时更好的性能表现。它并非 TCP 的替代品,而是针对特定应用场景(如游戏、实时音视频)的高性能补充。对于延迟敏感型应用,KCP 提供了一个强大的工具,但它的使用需要开发者对网络环境和 KCP 参数有深刻理解,并进行细致的调优。在选择网络传输协议时,应根据实际业务需求权衡 KCP 的优缺点。
