KCP 与 UDP 可靠传输


1. UDP 协议基础

1.1 UDP 是什么

UDP(User Datagram Protocol,用户数据报协议)是传输层最简单的协议。它只在 IP 之上加了端口号和校验和,几乎不做任何额外的事情。

复制代码
UDP 的定位:
  IP 层:负责把数据包从 A 送到 B(主机到主机)
  UDP 层:加了端口号(进程到进程),其他什么都不管
  TCP 层:加了可靠性、顺序性、流量控制、拥塞控制...(UDP 全都没有)

1.2 UDP 首部结构

复制代码
 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
┌───────────────────────────────────────────────────────────────────┐
│          Source Port (16 bit)       │    Destination Port (16 bit)│
├───────────────────────────────────────────────────────────────────┤
│          Length (16 bit)            │       Checksum (16 bit)     │
├───────────────────────────────────────────────────────────────────┤
│                        Data (变长)                                │
└───────────────────────────────────────────────────────────────────┘

只有 8 字节!对比 TCP 最小 20 字节。
字段 大小 说明
Source Port 16 bit 源端口号(可选,填0表示不需要回复)
Destination Port 16 bit 目的端口号
Length 16 bit UDP 数据报长度(首部+数据,最小8)
Checksum 16 bit 校验和(可选,但在 IPv6 中必须)

UDP 校验和 vs TCP 校验和

  • 都使用伪首部(源IP、目的IP、协议号、长度)
  • UDP 的校验和是可选的(IPv4),TCP 是必须的
  • 校验失败直接丢弃,不重传

1.3 UDP 的特点

复制代码
UDP 做了什么:
  ✓ 多路复用/分用(端口号区分不同进程)
  ✓ 数据完整性校验(可选的校验和)
  ✓ 消息边界保留(每次 sendto 对应一次 recvfrom)

UDP 没做什么:
  ✗ 不建立连接(无握手/挥手)
  ✗ 不保证可靠(丢了就丢了)
  ✗ 不保证顺序(先发的可能后到)
  ✗ 不做流量控制(发多快全凭本事)
  ✗ 不做拥塞控制(不管网络状况)
  ✗ 不做重传(丢了就丢了)

1.4 UDP 的消息边界

这是 UDP 与 TCP 最重要的行为差异之一:

复制代码
TCP(字节流):
  send("Hello") → send("World")
  可能收到:["HelloWorld"]     ← 粘包
  可能收到:["Hel"] ["loWor"] ["ld"]  ← 拆包
  → TCP 不保留消息边界

UDP(数据报):
  sendto("Hello") → sendto("World")
  recvfrom() → "Hello"    ← 一定是完整的一条消息
  recvfrom() → "World"    ← 一定是完整的一条消息
  → UDP 保留消息边界,一次 sendto = 一次 recvfrom

这意味着

  • UDP 天然适合"消息型"通信(命令、状态同步、事件通知)
  • TCP 适合"流型"通信(文件传输、视频流)
  • 如果用 UDP 传大消息,需要自己处理分片和重组

1.5 UDP 的典型应用

应用 为什么用 UDP 可靠性需求
DNS 请求/响应模式,一个包搞定 应用层重试(超时再查一次)
DHCP 客户端还没有 IP,只能广播 应用层重试
SNMP 网络管理,简单查询 低(丢了就下次再查)
NTP 时间同步,小包
视频直播 实时性 > 可靠性 低(丢了就丢了,不影响后续)
语音通话 延迟敏感,丢几帧无所谓
游戏状态同步 实时性最重要 中(旧数据过时,新数据才重要)
TFTP 简单文件传输 高(应用层自己实现 ACK)

1.6 UDP 为什么"快"

复制代码
TCP 发一个包的开销:
  1. 三次握手建立连接(1.5 RTT)
  2. 数据加上 20 字节首部
  3. 等待 ACK(或 Delayed ACK 40~200ms)
  4. 可能触发拥塞控制,降低速率
  5. 四次挥手关闭连接(至少 1 RTT + TIME_WAIT)

UDP 发一个包的开销:
  1. 直接 sendto(0 RTT)
  2. 数据加上 8 字节首部
  3. 发完就忘,不等 ACK
  4. 不管网络状况,全速发
  5. 不需要关闭连接

2. UDP 的局限性与可靠性需求

2.1 UDP 本身不保证什么

复制代码
场景:用 UDP 发送 10 个包 [1][2][3][4][5][6][7][8][9][10]

可能的结果:
  接收方收到:[1][3][5][7][8][10]   ← 丢了 2,4,6,9
  接收方收到:[10][8][7][5][3][1]   ← 全到了但顺序反了
  接收方收到:[1][1][3][3][5][5]    ← 重复了
  接收方收到:什么都没有             ← 全丢了

UDP 不关心这些,它只负责"尽力而为"地把数据报送到对端。

2.2 什么时候 UDP 本身就够了

复制代码
场景 1:一问一答(DNS)
  客户端发一个请求 → 等响应 → 超时?重发请求
  不需要复杂机制,应用层超时重试就够了

场景 2:实时音视频
  发送方以固定速率发送音频/视频帧
  丢了?跳过这帧,播放下一帧
  重传没意义------旧帧已经过时了

场景 3:游戏状态广播
  服务器每 50ms 广播一次游戏状态
  客户端收到旧的?丢弃,等新的
  客户端没收到?用预测/插值补偿

场景 4:物联网传感器上报
  传感器每 10 秒上报一次温度
  丢了?下一次 10 秒后又来了
  不需要重传

2.3 什么时候需要在 UDP 上实现可靠性

复制代码
场景 1:弱网下的可靠传输
  4G/卫星链路,丢包率 5~10%
  TCP 的拥塞控制把丢包当拥塞,速率暴跌
  → 用 UDP + 自定义可靠层,可以区分"丢包"和"拥塞"

场景 2:低延迟可靠传输
  游戏操作指令,必须可靠但不能有延迟
  TCP 的 Nagle + Delayed ACK 额外增加 40~200ms
  → 用 UDP + 自定义可靠层,去掉所有延迟优化

场景 3:自定义拥塞控制
  TCP 的拥塞控制算法在内核,应用层无法修改
  → 用 UDP + 自定义拥塞控制,完全按需定制

场景 4:多路复用(避免队头阻塞)
  TCP 的字节流特性:一个包丢了,后面全等
  → 用 UDP + 多个独立的可靠流,互不阻塞

这就是 KCP、QUIC 等协议出现的原因------UDP 提供了"原始的传输能力",上层可以在上面构建任意复杂度的可靠性机制。


3. KCP 协议深度解析

3.1 KCP 的设计目标

KCP 的作者 skywind3000 在设计文档中明确写道:

KCP 是一个快速可靠协议,能以比 TCP 浪费 10%-20% 的带宽的代价,换取平均延迟降低 30%-40%,且最大延迟降低三倍的传输效果。

复制代码
KCP 的核心目标:最小化延迟

TCP 的设计哲学:
  "在网络不拥塞的前提下,最大化吞吐量"
  → 一切机制(Nagle、Delayed ACK、拥塞控制)都为吞吐量服务
  → 牺牲了延迟

KCP 的设计哲学:
  "在可接受的带宽开销下,最小化传输延迟"
  → 关闭所有延迟优化
  → 快速重传,不等超时
  → 可选关闭拥塞控制
  → 牺牲了部分带宽

3.2 KCP 的整体架构

复制代码
┌──────────────────────────────────────────────────┐
│                   应用层                          │
│         ikcp_send() / ikcp_recv()                │
├──────────────────────────────────────────────────┤
│                   KCP 层(用户态)                 │
│  ┌─────────┐  ┌──────────┐  ┌──────────────────┐ │
│  │ 可靠传输  │  │ 流量控制  │  │ 拥塞控制(可选)   │ │
│  │ ARQ机制  │  │ 窗口管理  │  │ 慢启动/拥塞避免  │ │
│  └─────────┘  └──────────┘  └──────────────────┘ │
│         ikcp_input() / ikcp_flush()              │
├──────────────────────────────────────────────────┤
│         output 回调(用户注册的发送函数)           │
├──────────────────────────────────────────────────┤
│                   UDP 层(内核)                   │
│              sendto() / recvfrom()                │
├──────────────────────────────────────────────────┤
│                   IP 层                           │
└──────────────────────────────────────────────────┘

关键设计 :KCP 不做网络 IO,它只负责"计算应该发什么",实际的发送由用户注册的 output 回调完成。这种设计让 KCP 可以运行在任何传输之上(UDP、蓝牙、串口、共享内存...)。

3.3 KCP 报文格式详解

复制代码
KCP 报文头:24 字节

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
┌─────────────────────────────────────────────────────────────────┐
│                       Conversation ID (32 bit)                  │
├─────────────────────────────────────────────────────────────────┤
│         Command (8 bit)        │      Fragment Count (8 bit)    │
├─────────────────────────────────────────────────────────────────┤
│         Window (16 bit)        │      Timestamp (32 bit)        │
├─────────────────────────────────────────────────────────────────┤
│                   Sequence Number (32 bit)                       │
├─────────────────────────────────────────────────────────────────┤
│              Unacknowledged Number (32 bit)                      │
├─────────────────────────────────────────────────────────────────┤
│                   Data Length (32 bit)                           │
├─────────────────────────────────────────────────────────────────┤
│                   Data (变长,0~MSS 字节)                        │
└─────────────────────────────────────────────────────────────────┘

各字段详解

字段 大小 说明 对应 TCP 概念
Conversation ID 32 bit 会话标识,类似连接ID 无(TCP 用四元组)
Command 8 bit 命令类型 Flags(SYN/ACK/FIN等)
Fragment Count 8 bit 上层消息被拆成几片 无(TCP 是字节流)
Window 16 bit 本方通告窗口 Window Size
Timestamp 32 bit 发送时间戳 Timestamps Option
Sequence Number 32 bit 包序列号 Seq
Unacknowledged Number 32 bit 累积确认号 Ack
Data Length 32 bit 数据长度 无(TCP 从长度推算)

Command 类型

复制代码
IKCP_CMD_PUSH = 81   // 数据推送(携带数据)
IKCP_CMD_ACK  = 82   // 确认应答
IKCP_CMD_WASK = 83   // 窗口探测请求("你还有空间吗?")
IKCP_CMD_WINS = 84   // 窗口告知响应("我还有 X 字节空间")

3.4 KCP 的 ARQ 机制

3.4.1 发送流程
复制代码
应用层调用 ikcp_send(data, len)
    │
    ▼
┌─────────────────────────────────────────────┐
│ 1. 分片:如果 data > MSS,拆成多个 fragment  │
│    例如 data=3000字节, MSS=1400             │
│    → frag0: 1400字节 (frg=1,表示还有后续)   │
│    → frag1: 1400字节 (frg=1)               │
│    → frag2: 200字节  (frg=0,表示最后一片)   │
│    注意:frg 是倒序的,最后一个片 frg=0       │
├─────────────────────────────────────────────┤
│ 2. 入队:每个 fragment 包装成 IKCPSEG        │
│    → 挂到 snd_queue(发送队列)              │
│    → 设置 sn(序列号),sn++                 │
└─────────────────────────────────────────────┘
    │
    ▼  等待 ikcp_update() 驱动
┌─────────────────────────────────────────────┐
│ 3. 移动:snd_queue → snd_buf               │
│    条件:snd_nxt - snd_una < cwnd           │
│         && snd_nxt - snd_una < rmt_wnd      │
│    即:在途包数量 < 发送窗口 && < 远端窗口    │
├─────────────────────────────────────────────┤
│ 4. 发送:ikcp_flush() 遍历 snd_buf          │
│    → 填写报文头(sn, una, wnd, ts, ...)    │
│    → 调用 output 回调 → 实际 UDP 发送       │
│    → 设置重传定时器(resendts = now + rto)  │
└─────────────────────────────────────────────┘
3.4.2 接收流程
复制代码
网络收到 UDP 数据 → 应用调用 ikcp_input(buf, len)
    │
    ▼
┌─────────────────────────────────────────────┐
│ 1. 解析报文头:sn, una, wnd, cmd, frg, ...  │
├─────────────────────────────────────────────┤
│ 2. 处理 ACK:una 表示"una 之前的包我都收到了"│
│    → 遍历 snd_buf,释放 sn < una 的段        │
│    → 更新 snd_una                            │
│    → 计算 RTT(当前时间 - 段的发送时间戳)    │
│    → 更新 rto(SRTT + max(AckDelay, minRTO))│
├─────────────────────────────────────────────┤
│ 3. 处理数据(cmd == PUSH):                 │
│    if (sn == rcv_nxt) {                      │
│        // 按序到达,直接放入 rcv_queue        │
│        rcv_nxt++                             │
│        // 检查 rcv_buf 中是否有后续的乱序包   │
│        // 如果有,按序移到 rcv_queue          │
│    } else {                                  │
│        // 乱序到达,插入 rcv_buf(按sn排序)  │
│    }                                         │
├─────────────────────────────────────────────┤
│ 4. 更新远端窗口:rmt_wnd = 段的 wnd 字段     │
└─────────────────────────────────────────────┘
    │
    ▼
应用层调用 ikcp_recv(buf, len)
    → 从 rcv_queue 取出数据
    → 如果一个消息有多个 fragment,组装后返回
3.4.3 ACK 处理机制

KCP 的 ACK 与 TCP 有重要区别:

复制代码
TCP 的 ACK:
  - 累积确认:Ack=N 表示 N 之前的所有数据都收到了
  - 延迟确认:收到数据后等 40~200ms 才发 ACK
  - ACK 不能携带数据(纯 ACK 包)

KCP 的 ACK:
  - 也是累积确认:una=N 表示 N 之前的包都收到了
  - 但 KCP 还在每个 PUSH 包的 una 字段中携带确认信息
  - 不做延迟确认:收到数据立即回 ACK(或在下次 flush 时捎带)
  - ACK 列表:KCP 维护 acklist,记录所有需要确认的 (sn, ts) 对

KCP 的 ACK 列表工作方式:

收到数据包 sn=5, ts=1000
  → acklist.push(5, 1000)

收到数据包 sn=6, ts=1010
  → acklist.push(6, 1010)

下次 flush 时:
  → 遍历 acklist,对每个 (sn, ts) 发送一个 ACK 包
  → 或者在下一个 PUSH 包中捎带(una 字段)

为什么不用延迟确认?
  → 延迟确认增加了延迟!
  → KCP 的核心目标是最小化延迟,所以不用延迟确认

3.5 KCP 的重传机制

3.5.1 RTO 计算
c 复制代码
// KCP 的 RTT 估算(类似 TCP,但更激进)

// 每次收到 ACK,计算 RTT 样本
rtt_sample = current_time - segment.send_timestamp

// 更新 SRTT 和 RTTVAR(与 TCP 相同的指数平滑)
if (first_sample) {
    srtt = rtt_sample
    rttvar = rtt_sample / 2
} else {
    rttvar = (3/4) * rttvar + (1/4) * abs(srtt - rtt_sample)
    srtt = (7/8) * srtt + (1/8) * rtt_sample
}

// 计算 RTO(关键区别在这里)
rto = srtt + max(rttvar * 4, minRTO)   // TCP 的公式
rto = srtt + max(rttvar, minRTO)        // KCP 的公式,去掉了 ×4

// minRTO 的区别
TCP:  minRTO = 200ms(Linux 默认)
KCP:  minRTO = 可配置,nodelay 模式下 10ms

RTO 退避策略

复制代码
TCP 的 RTO 退避:
  第1次超时:RTO = 200ms
  第2次超时:RTO = 400ms   (翻倍)
  第3次超时:RTO = 800ms   (翻倍)
  第4次超时:RTO = 1600ms  (翻倍)
  → 指数退避,非常保守

KCP 的 RTO 退避(nodelay=1):
  第1次超时:RTO = 10ms
  第2次超时:RTO = 15ms   (×1.5)
  第3次超时:RTO = 22ms   (×1.5)
  第4次超时:RTO = 33ms   (×1.5)
  → 线性退避(×1.5),非常激进

KCP 的 RTO 退避(nodelay=0,普通模式):
  第1次超时:RTO = 100ms
  第2次超时:RTO = 150ms  (×1.5)
  第3次超时:RTO = 225ms  (×1.5)
  → 同样是 ×1.5 退避,但起始值更大
3.5.2 快速重传
复制代码
KCP 的快速重传原理:

发送方发送了 [sn=1] [sn=2] [sn=3] [sn=4] [sn=5]
其中 [sn=2] 丢失

接收方:
  收到 sn=1 → rcv_nxt=2 → 回复 ACK(una=2)
  收到 sn=3 → 乱序,插入 rcv_buf → 回复 ACK(una=2)
  收到 sn=4 → 乱序,插入 rcv_buf → 回复 ACK(una=2)
  收到 sn=5 → 乱序,插入 rcv_buf → 回复 ACK(una=2)

发送方:
  收到 ACK(una=2) → snd_una 前进到 2
  → 在 snd_buf 中,sn=3,4,5 的 fastack 计数器递增
    (因为 una=2 跳过了它们,说明它们"可能"丢了)

  当 sn=3 的 fastack >= resend 阈值(如2)时:
  → 立即重传 sn=3 对应的包(不等 RTO 超时)

fastack 计数器的工作方式

c 复制代码
// 伪代码:收到 ACK 时更新 fastack

void update_fastack(IKCPCB *kcp, IUINT32 una, IUINT32 sn) {
    // 遍历 snd_buf 中所有 sn >= una 的段
    for (seg in snd_buf) {
        if (seg->sn >= una && seg->sn < sn) {
            // 这个段被"跳过"了(una 没有确认它,但后续的 ACK 跳过了它)
            seg->fastack++
        }
    }
}

// flush 时检查是否需要快速重传
for (seg in snd_buf) {
    if (seg->fastack >= kcp->resend) {
        // 触发快速重传!
        retransmit(seg)
        seg->fastack = 0
    }
}

与 TCP 快速重传的对比

复制代码
TCP:
  收到 3 个重复 ACK → 重传
  "重复 ACK" = ACK 号与上一个相同
  需要至少 3 个包到达才能触发

KCP:
  fastack >= resend(默认2)→ 重传
  "fastack 递增" = 该包被后续的 ACK 跳过
  只需要 2 个后续包到达就能触发

触发速度对比(假设 RTT=100ms):
  TCP:3 个 dupACK × 100ms = 300ms 后触发快速重传
  KCP:2 个跨越 ACK × 10ms(interval) = 20ms 后触发快速重传
3.5.3 重传流程
复制代码
场景:发送 [1][2][3][4][5],[2] 丢失

时间线:
  t=0ms    发送 [1][2][3][4][5](全部进入 snd_buf)
  t=50ms   收到 ACK(una=2) → [1] 确认,[2][3][4][5] 在途
  t=60ms   收到 ACK(una=2) → [3] 的 fastack=1
  t=70ms   收到 ACK(una=2) → [3] 的 fastack=2 ≥ resend
           → 【快速重传】[2]
  t=120ms  收到 ACK(una=6) → [2][3][4][5] 全部确认

对比 TCP(RTO=200ms):
  t=0ms    发送 [1][2][3][4][5]
  t=50ms   收到 ACK=2
  t=100ms  收到 dupACK=2 (#1)
  t=150ms  收到 dupACK=2 (#2)
  t=200ms  收到 dupACK=2 (#3) → 【快速重传】[2]
  t=250ms  收到 ACK=6

KCP 在 t=70ms 就重传了,TCP 在 t=200ms 才重传
KCP 快了 130ms

3.6 KCP 的窗口管理

复制代码
KCP 的窗口结构:

发送端:
  snd_una ──────────── snd_nxt ──────────── snd_una + cwnd
     │                    │                      │
     ├── 已确认(可释放)──┤──── 在途(等待ACK)───┤──── 还能发 ────┤
     │                    │                      │
     └────────────────────┴──────────────────────┘

  snd_queue:应用层写入但还没移到 snd_buf 的数据
  snd_buf:已发送但未确认的数据(在途)

接收端:
  rcv_nxt ──────────────────── rcv_nxt + rcv_wnd
     │                              │
     ├── 期望收到的下一个 ────────────┤──── 接收窗口 ────┤

  rcv_queue:按序到达、等待应用读取的数据
  rcv_buf:乱序到达、等待前序包的数据

窗口大小的影响

复制代码
窗口太小(如 32 个包):
  ┌──────────────────────┐
  │ 发送窗口只有 32 个包   │
  │ 发完 32 个就停了       │
  │ 等 ACK 回来才能继续发  │
  │ → 吞吐量受限          │
  └──────────────────────┘
  BDP = 带宽 × RTT
  例如:10Mbps × 100ms = 125KB
  125KB / 1400B = 89 个包
  窗口 32 < 89 → 吞吐量只有 10Mbps × (32/89) = 3.6Mbps

窗口足够大(如 256 个包):
  256 > 89 → 可以填满管道,吞吐量接近 10Mbps

建议:窗口 ≥ BDP / MSS
  BDP = 带宽 × RTT
  窗口 = BDP / MSS + 余量

3.7 KCP 的拥塞控制

KCP 实现了可选的拥塞控制,类似 TCP Reno:

复制代码
KCP 拥塞控制状态机:

┌──────────┐    cwnd < ssthresh    ┌──────────┐
│  慢启动    │──────────────────────→│  拥塞避免  │
│  cwnd++   │    每 RTT 翻倍        │  cwnd++   │
│  每ACK+1  │                      │  每RTT+1  │
└──────────┘                      └──────────┘
     ↑                                  │
     │           丢包                    │
     │     ┌─────────────────────┐       │
     └─────│  ssthresh=cwnd/2    │←──────┘
           │  cwnd=ssthresh+3    │
           │  (快速恢复)          │
           └─────────────────────┘
                    │
                    │ RTO 超时
                    ▼
           ┌─────────────────────┐
           │  ssthresh=cwnd/2    │
           │  cwnd=1             │
           │  (回到慢启动)        │
           └─────────────────────┘

完全关闭拥塞控制(nc=1):

复制代码
关闭拥塞控制后:
  cwnd = 128 * 1024(一个很大的值,实际上等于无限)
  发送速率只受 rmt_wnd(远端窗口)限制

适用场景:
  - 局域网(带宽充足,不会拥塞)
  - 自己实现了拥塞控制(如基于延迟的算法)
  - 弱网环境(丢包不是拥塞,是链路质量差)

风险:
  - 如果真的拥塞了,会把网络搞瘫
  - 公网使用必须谨慎

3.8 KCP 的流量控制

复制代码
KCP 的发送条件检查(在 ikcp_flush 中):

while (snd_queue 不为空) {
    // 条件1:在途包数量 < 拥塞窗口
    if (snd_nxt - snd_una >= cwnd) break

    // 条件2:在途包数量 < 远端窗口
    if (snd_nxt - snd_una >= rmt_wnd) break

    // 条件3:远端窗口 > 0
    if (rmt_wnd == 0) break

    // 全部满足,从 snd_queue 移到 snd_buf
    move_segment(snd_queue → snd_buf)
    snd_nxt++
}

远端窗口更新

复制代码
每次收到对方的包,都从包头的 wnd 字段读取对方的窗口大小
  → 更新 rmt_wnd

如果对方窗口为 0:
  → 发送 WASK(窗口探测请求)
  → 等待对方回复 WINS(窗口告知)
  → 对方应用层读取数据后,窗口恢复,回复 WINS

3.9 KCP 分片与重组

复制代码
上层消息:3000 字节
MSS:1400 字节

分片过程(ikcp_send 内部):
  片0:data[0:1400],    frg = 2  (还有2个后续片)
  片1:data[1400:2800], frg = 1  (还有1个后续片)
  片2:data[2800:3000], frg = 0  (最后一片)

注意:frg 是倒序的!最后一片 frg=0

接收端重组:
  收到 frg=2 → 缓存,等待后续
  收到 frg=1 → 缓存,等待后续
  收到 frg=0 → 发现 frg=0,组装完整消息
  → 按 frg 从大到小排列,拼接数据
  → 放入 rcv_queue,等应用读取

为什么用倒序 frg?

复制代码
接收端收到 frg=0 时,就知道"这是一个完整消息的最后一片"
→ 可以立即开始重组
→ 如果用正序,需要先收到第一片才知道总片数

实际上 KCP 的 frg = fragment_count - 1 - current_index
例如 3 个片:frg = 2, 1, 0

4. KCP 的参数配置与模式

4.1 核心参数

c 复制代码
// 设置 nodelay 模式
int ikcp_nodelay(ikcpcb *kcp, int nodelay, int interval, int resend, int nc);

// 参数说明:
// nodelay: 0=普通模式, 1=快速模式
//   影响 RTO 计算:
//     0: rto = srtt + max(rttvar * 2, 30)
//     1: rto = srtt + max(rttvar, 10)

// interval: KCP 内部刷新时钟间隔(毫秒)
//   建议值:10~100ms
//   越小:检测超时越及时,但 CPU 开销越大
//   越大:CPU 开销小,但重传检测延迟大

// resend: 快速重传触发阈值
//   0: 关闭快速重传
//   2: 2 次 ACK 跨越就重传(推荐)
//   3: 3 次 ACK 跨越才重传(类似 TCP)

// nc: 0=启用拥塞控制, 1=关闭拥塞控制
//   0: 有拥塞控制(类似 TCP Reno)
//   1: 无拥塞控制(只受远端窗口限制)

4.2 五种典型配置模式

模式一:类 TCP(带宽优先)
c 复制代码
ikcp_nodelay(kcp, 0, 100, 0, 0);
ikcp_wndsize(kcp, 128, 128);
ikcp_setmtu(kcp, 1400);
复制代码
特点:
  - RTO 最小 100ms(接近 TCP)
  - 关闭快速重传
  - 保留拥塞控制
  - 效果:吞吐量高,延迟接近 TCP
  - 适用:文件传输、大块数据传输
模式二:普通低延迟
c 复制代码
ikcp_nodelay(kcp, 1, 20, 2, 0);
ikcp_wndsize(kcp, 128, 128);
ikcp_setmtu(kcp, 1400);
复制代码
特点:
  - RTO 最小 20ms
  - 快速重传阈值 2
  - 保留拥塞控制
  - 效果:延迟明显低于 TCP,带宽消耗适中
  - 适用:互联网游戏、远程桌面
模式三:极速低延迟
c 复制代码
ikcp_nodelay(kcp, 1, 10, 2, 1);
ikcp_wndsize(kcp, 128, 128);
ikcp_setmtu(kcp, 1400);
复制代码
特点:
  - RTO 最小 10ms
  - 快速重传阈值 2
  - 关闭拥塞控制
  - 效果:延迟最低,带宽消耗最大
  - 适用:局域网游戏、实时性要求极高的场景
  - 风险:公网使用可能造成拥塞
模式四:弱网优化
c 复制代码
ikcp_nodelay(kcp, 1, 30, 2, 0);
ikcp_wndsize(kcp, 256, 256);
ikcp_setmtu(kcp, 1200);  // MTU 稍小,减少分片丢包概率
复制代码
特点:
  - RTO 最小 30ms(弱网下太小容易误判)
  - 快速重传阈值 2
  - 保留拥塞控制
  - 大窗口(弱网下 BDP 大)
  - 小 MTU(减少 IP 分片)
  - 适用:4G/卫星/跨国弱网
模式五:音视频传输
c 复制代码
ikcp_nodelay(kcp, 1, 10, 2, 1);
ikcp_wndsize(kcp, 64, 64);
ikcp_setmtu(kcp, 1400);
// 还需要配合:限制最大重传次数,超过就放弃
复制代码
特点:
  - 极速模式
  - 小窗口(音视频数据量不大)
  - 需要额外实现:最大重传次数限制
  - 适用:直播、视频会议、语音通话

4.3 参数调优速查表

复制代码
┌─────────────────┬──────────┬──────────┬────────┬────────┐
│ 场景             │ nodelay  │ interval │ resend │ nc     │
├─────────────────┼──────────┼──────────┼────────┼────────┤
│ 文件传输         │ 0        │ 50~100   │ 0      │ 0      │
│ 网页加载         │ 0        │ 30~50    │ 0      │ 0      │
│ 互联网游戏       │ 1        │ 10~20    │ 2      │ 0      │
│ 局域网游戏       │ 1        │ 5~10     │ 2      │ 1      │
│ 远程桌面         │ 1        │ 10       │ 2      │ 0      │
│ 弱网传输         │ 1        │ 20~30    │ 2      │ 0      │
│ 音视频           │ 1        │ 10       │ 2      │ 1      │
│ IoT 传感器       │ 0        │ 100      │ 0      │ 0      │
└─────────────────┴──────────┴──────────┴────────┴────────┘

5. KCP 使用详解

5.1 核心 API

c 复制代码
// ===== 创建与销毁 =====

// 创建 KCP 对象
// conv: 会话ID,通信双方必须相同(类似 TCP 的四元组)
// user: 用户自定义指针,会传给 output 回调
ikcpcb* ikcp_create(IUINT32 conv, void *user);

// 销毁 KCP 对象
void ikcp_release(ikcpcb *kcp);

// ===== 配置 =====

// 设置输出回调(必须设置!KCP 不做网络IO)
// 函数原型:int output(const char *buf, int len, ikcpcb *kcp, void *user)
kcp->output = my_udp_output;

// 设置 nodelay 模式
int ikcp_nodelay(ikcpcb *kcp, int nodelay, int interval, int resend, int nc);

// 设置窗口大小(发送窗口, 接收窗口)
int ikcp_wndsize(ikcpcb *kcp, int sndwnd, int rcvwnd);

// 设置 MTU(最大传输单元,默认1400)
int ikcp_setmtu(ikcpcb *kcp, int mtu);

// ===== 数据收发 =====

// 发送数据(应用层 → KCP)
int ikcp_send(ikcpcb *kcp, const char *buffer, int len);

// 接收数据(KCP → 应用层)
int ikcp_recv(ikcpcb *kcp, char *buffer, int len);

// 从网络接收数据(UDP 收到的数据喂给 KCP)
int ikcp_input(ikcpcb *kcp, const char *data, long size);

// ===== 驱动 =====

// 定时更新(必须定期调用!驱动重传、拥塞控制等)
// current: 当前时间(毫秒)
void ikcp_update(ikcpcb *kcp, IUINT32 current);

// 获取下次更新时间(用于精确调度)
IUINT32 ikcp_check(const ikcpcb *kcp, IUINT32 current);

// ===== 状态查询 =====

// 获取等待发送的数据量
int ikcp_waitsnd(const ikcpcb *kcp);

// 获取接收缓冲区中的数据量
int ikcp_peeksize(const ikcpcb *kcp);

5.2 完整使用示例

c 复制代码
#include "ikcp.h"
#include <sys/socket.h>
#include <unistd.h>
#include <string.h>

// ===== 输出回调 =====
// KCP 内部要发包时调用这个函数
int udp_output(const char *buf, int len, ikcpcb *kcp, void *user) {
    int sock = *(int*)user;
    struct sockaddr_in *addr = (struct sockaddr_in*)(kcp + 1); // 简化示例
    return sendto(sock, buf, len, 0, (struct sockaddr*)addr, sizeof(*addr));
}

// ===== 客户端示例 =====
void client_example() {
    int sock = socket(AF_INET, SOCK_DGRAM, 0);
    struct sockaddr_in server_addr = {
        .sin_family = AF_INET,
        .sin_port = htons(8888),
        .sin_addr.s_addr = inet_addr("127.0.0.1")
    };

    // 1. 创建 KCP
    ikcpcb *kcp = ikcp_create(0x1, &sock);  // conv=0x1

    // 2. 设置 output 回调
    kcp->output = udp_output;

    // 3. 配置模式(快速模式)
    ikcp_nodelay(kcp, 1, 10, 2, 1);
    ikcp_wndsize(kcp, 128, 128);
    ikcp_setmtu(kcp, 1400);

    // 4. 事件循环
    char buf[4096];
    while (1) {
        // 从 UDP 读取数据,喂给 KCP
        int n = recvfrom(sock, buf, sizeof(buf), MSG_DONTWAIT, NULL, NULL);
        if (n > 0) {
            ikcp_input(kcp, buf, n);
        }

        // 更新 KCP(驱动重传等)
        ikcp_update(kcp, get_current_ms());

        // 从 KCP 读取已确认的数据
        while ((n = ikcp_recv(kcp, buf, sizeof(buf))) > 0) {
            printf("收到 %d 字节: %.*s\n", n, n, buf);
        }

        // 发送数据
        if (has_data_to_send()) {
            const char *msg = "Hello KCP!";
            ikcp_send(kcp, msg, strlen(msg));
        }

        // 精确调度:获取下次需要 update 的时间
        IUINT32 next = ikcp_check(kcp, get_current_ms());
        IUINT32 now = get_current_ms();
        if (next > now) {
            usleep((next - now) * 1000);  // 等待到下次 update 时间
        }
    }

    ikcp_release(kcp);
    close(sock);
}

5.3 数据流向图

复制代码
发送方向:
  应用层
    │ ikcp_send(data)
    ▼
  KCP 内部
    ├── 分片(如果 data > MSS)
    ├── 每个片段包装成 IKCPSEG
    ├── 放入 snd_queue
    │
    ├── ikcp_update() 触发
    │   ├── 从 snd_queue 移到 snd_buf(受窗口限制)
    │   └── 检查超时/快速重传
    │
    └── ikcp_flush()
        ├── 遍历 snd_buf,填写报文头
        ├── 调用 output 回调
        └── output → UDP sendto()
    │
    ▼
  UDP socket → 网络


接收方向:
  网络 → UDP socket
    │ recvfrom()
    ▼
  应用层
    │ ikcp_input(buf, len)
    ▼
  KCP 内部
    ├── 解析报文头
    ├── 处理 ACK(更新 una, rto, cwnd)
    ├── 处理数据(cmd=PUSH)
    │   ├── 按序(sn==rcv_nxt)→ 放入 rcv_queue
    │   └── 乱序 → 放入 rcv_buf(等前序到达)
    │
    └── ikcp_recv() 被应用调用
        ├── 从 rcv_queue 取出数据
        └── 如果有分片,重组后返回
    │
    ▼
  应用层处理数据

5.4 ikcp_update 的调度

c 复制代码
// 方法1:固定间隔调用(简单但不精确)
while (running) {
    ikcp_update(kcp, get_current_ms());
    usleep(10 * 1000);  // 10ms
}

// 方法2:精确调度(推荐)
while (running) {
    // 先处理网络数据
    int n = recvfrom(sock, buf, sizeof(buf), MSG_DONTWAIT, ...);
    if (n > 0) ikcp_input(kcp, buf, n);

    // 更新 KCP
    ikcp_update(kcp, get_current_ms());

    // 获取下次需要 update 的时间
    IUINT32 next_update = ikcp_check(kcp, get_current_ms());
    IUINT32 now = get_current_ms();

    // 等待到下次 update 时间(或有网络数据到来)
    if (next_update > now) {
        // 用 select/poll/epoll 同时等待网络数据和定时器
        struct pollfd pfd = { .fd = sock, .events = POLLIN };
        poll(&pfd, 1, next_update - now);  // 超时 = next_update - now
    }
}

// 方法3:配合 epoll(高性能服务器)
// 将 KCP 的定时器注册到 epoll 的 timerfd
int timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK);
// 每次 update 后设置 timerfd 为 next_update 时间
// epoll_wait 同时监听 sock 和 timerfd

6. KCP 与 TCP 的深度对比

6.1 协议行为对比

复制代码
场景:发送 100 个包,每个 1400 字节,其中第 50 个包丢失
RTT = 100ms

TCP(CUBIC,Linux 默认):
  t=0      发送 [1-10](拥塞窗口初始 10)
  t=50     收到 ACK 1-10,窗口增长
  ...      继续发送,窗口指数增长
  t=490    发送 [50],丢包
  t=540    收到 ACK 49,等待 [50] 的 ACK
  t=590    收到 dupACK #1
  t=640    收到 dupACK #2
  t=690    收到 dupACK #3 → 快速重传 [50]
  t=690    ssthresh = cwnd/2, cwnd = ssthresh + 3
  t=740    收到 ACK 51-100,恢复传输
  总延迟增加:约 200ms(从 540 到 690)
  窗口变化:从 ~50 降到 ~25,缓慢恢复

KCP(快速模式,nodelay=1, interval=10, resend=2):
  t=0      发送 [1-100](窗口足够大)
  t=50     收到 ACK(una=1),[1] 确认
  t=55     收到 ACK(una=1),[2] 的 fastack++
  ...      持续发送,持续收到 ACK
  t=540    [50] 丢失
  t=550    收到 ACK(una=50),[51] 的 fastack=1
  t=560    收到 ACK(una=50),[51] 的 fastack=2 → 快速重传 [50]
  t=610    收到 ACK(una=51),[50] 确认
  总延迟增加:约 20ms(从 550 到 560)
  窗口变化:不变化(nc=1 时)

对比:
  TCP 增加延迟:200ms
  KCP 增加延迟:20ms
  KCP 比 TCP 快 10 倍

6.2 Nagle 与 Delayed ACK 的影响

复制代码
场景:每 10ms 发送一个 100 字节的小包

TCP(默认开启 Nagle + Delayed ACK):
  t=0ms    send(100B) → 缓存,不发(Nagle:等 ACK 或攒够 MSS)
  t=10ms   send(100B) → 缓存,不发
  t=20ms   send(100B) → 缓存,不发
  ...      持续缓存
  t=50ms   第一个包的 ACK 还没来(Delayed ACK 等 40ms)
  t=60ms   收到 ACK → 立即发送缓存的数据(Nagle:有 ACK 了)
  → 小包被合并,延迟 60ms+

  或者更糟糕的情况:
  t=0ms    send(100B) → 缓存
  t=40ms   接收方 Delayed ACK 超时,回 ACK
  t=50ms   收到 ACK → 发送缓存数据
  → 延迟 50ms

KCP(默认关闭合并):
  t=0ms    send(100B) → 立即发送
  t=10ms   send(100B) → 立即发送
  t=20ms   send(100B) → 立即发送
  → 延迟 ≈ 0ms

6.3 拥塞控制行为对比

复制代码
场景:1% 随机丢包率的网络,不是拥塞

TCP(CUBIC):
  检测到丢包 → 认为是拥塞 → cwnd 减半
  恢复期间发送速率降低
  持续丢包 → cwnd 持续在低水平
  实际吞吐量:理论值的 30~50%

KCP(nc=1,关闭拥塞控制):
  检测到丢包 → 只重传丢失的包 → cwnd 不变
  发送速率不受丢包影响
  实际吞吐量:接近理论值
  代价:重传包占带宽的 1~2%

KCP(nc=0,保留拥塞控制):
  行为类似 TCP Reno,但恢复更快
  因为 RTO 更小,快速重传阈值更低
  实际吞吐量:理论值的 50~70%(比 TCP 稍好)

6.4 资源消耗对比

复制代码
内存消耗:
  TCP:内核为每个连接维护 ~4KB 的 tcp_sock 结构
  KCP:用户态,ikcpcb 结构 ~2KB + 发送/接收缓冲区

CPU 消耗:
  TCP:内核实现,高度优化
  KCP:用户态,需要定期调用 ikcp_update()
  → KCP 的 CPU 消耗略高,但通常可以忽略

系统调用:
  TCP:send()/recv() 直接用内核协议栈
  KCP:需要 sendto()/recvfrom() + ikcp_input()/ikcp_send()/ikcp_recv()
  → KCP 多了一层用户态处理,但延迟更低

7. KCP 的高级话题

7.1 与加密层配合

KCP 本身不加密,公网使用必须加加密层:

复制代码
方案1:KCP + DTLS
  ┌──────────┐
  │   应用    │
  ├──────────┤
  │   KCP    │  ← 可靠传输
  ├──────────┤
  │  DTLS    │  ← 加密 + 完整性
  ├──────────┤
  │   UDP    │
  └──────────┘

方案2:KCP + 自定义加密
  ┌──────────┐
  │   应用    │
  ├──────────┤
  │ AES-GCM  │  ← 自己加解密
  ├──────────┤
  │   KCP    │
  ├──────────┤
  │   UDP    │
  └──────────┘

方案3:KCP over WireGuard/Noise
  ┌──────────┐
  │   应用    │
  ├──────────┤
  │   KCP    │
  ├──────────┤
  │WireGuard │  ← 隧道加密
  ├──────────┤
  │   UDP    │
  └──────────┘

7.2 多路复用

KCP 没有内置多路复用,需要自己实现:

c 复制代码
// 方案1:多个 KCP 实例(简单但资源消耗大)
struct connection {
    ikcpcb *kcp;
    int id;
};
// 每个逻辑流一个 KCP 实例
// 优点:流之间完全独立,互不影响
// 缺点:每个实例都有自己的缓冲区和状态

// 方案2:单个 KCP 实例 + 应用层多路复用
// 在 KCP 数据中加一个 stream_id 字段
// 优点:只维护一个 KCP 实例
// 缺点:流之间有队头阻塞(KCP 层面)

// 推荐:对延迟敏感的场景用方案1
//       对资源敏感的场景用方案2

7.3 连接管理

KCP 没有连接建立/关闭的概念(没有握手/挥手),需要自己实现:

复制代码
方案1:无连接管理
  直接用 conv 区分会话
  优点:最简单
  缺点:无法检测对端是否在线

方案2:应用层心跳
  定期发送心跳包(通过 KCP 发送)
  超时无心跳 → 认为连接断开
  优点:简单有效
  缺点:心跳包也走 KCP 可靠传输,开销大

方案3:应用层握手
  用 KCP 发送握手消息,确认双方在线
  优点:连接状态明确
  缺点:增加一次 RTT

方案4:UDP 直接发心跳(不走 KCP)
  心跳包直接用 UDP 发送,不经过 KCP
  优点:开销最小
  缺点:心跳不可靠(丢了就丢了,靠频率弥补)

7.4 NAT 穿透

KCP 没有内置 NAT 穿透,但因为基于 UDP,穿透比 TCP 容易:

复制代码
UDP NAT 穿透(UDP Hole Punching):

  客户端 A (192.168.1.10:5000)
      │
      ├── NAT A (203.0.113.1:40001)
      │
      ▼
  中继服务器 (8.8.8.8:8888)
      │
      ▲
      │
      ├── NAT B (198.51.100.1:40002)
      │
  客户端 B (10.0.0.20:6000)

步骤:
  1. A 和 B 都向服务器注册(服务器记录各自的外部地址)
  2. 服务器告诉 A:"B 的外部地址是 198.51.100.1:40002"
  3. 服务器告诉 B:"A 的外部地址是 203.0.113.1:40001"
  4. A 向 B 的外部地址发 UDP 包(在 NAT A 上打洞)
  5. B 向 A 的外部地址发 UDP 包(在 NAT B 上打洞)
  6. 双方的 NAT 都有了对方的映射,后续可以直接通信

限制:
  - 对称 NAT(Symmetric NAT)穿透失败率高
  - 需要一个公网中继服务器作为信令

7.5 KCP 与 FEC(前向纠错)

FEC(Forward Error Correction)可以在不重传的情况下恢复丢失的数据:

复制代码
原理:
  发送方:原始数据 + 冗余校验数据
  接收方:用校验数据恢复丢失的原始数据

常用方案:Reed-Solomon 编码

例子(4+2,即 4 个原始包 + 2 个校验包):
  发送:[D1] [D2] [D3] [D4] [F1] [F2]
  [D2] 丢失,但收到 [D1] [D3] [D4] [F1] [F2]
  → 用 5 个包(需要至少 4 个)恢复 [D2]
  → 不需要重传!

KCP + FEC 的优势:
  - 重传需要一个 RTT,FEC 不需要
  - 在高丢包率下,FEC + KCP 比纯 KCP 延迟更低
  - 代价:带宽增加(2/6 = 33% 冗余)

典型实现:
  - kcp-fec(GitHub 开源项目)
  - 自己实现:发送端用 RS 编码,接收端用 RS 解码

8. 其他基于 UDP 的可靠传输方案

8.1 QUIC(HTTP/3 的基础)

复制代码
QUIC = UDP + 可靠性 + 加密 + 多路复用 + 连接迁移

由 Google 开发,IETF 标准化(RFC 9000/9001/9002)
Chrome/Edge/Firefox 默认启用
HTTP/3 就是基于 QUIC

QUIC vs KCP:
┌─────────────────┬──────────────┬──────────────┐
│ 特性             │ QUIC         │ KCP          │
├─────────────────┼──────────────┼──────────────┤
│ 代码量           │ 几万行        │ ~2000 行     │
│ 加密             │ 内置 TLS 1.3 │ 无           │
│ 多路复用         │ 有           │ 无           │
│ 连接迁移         │ 有           │ 无           │
│ 0-RTT           │ 有           │ 无           │
│ 拥塞控制         │ BBR/CUBIC    │ 类 Reno/可关 │
│ 流量控制         │ 连接级+流级  │ 连接级       │
│ 标准化           │ IETF RFC     │ 社区开源     │
│ 适用             │ Web 通用     │ 游戏/嵌入式  │
└─────────────────┴──────────────┴──────────────┘

8.2 UDT(UDP-based Data Transfer)

复制代码
专为高速网络设计(10Gbps+)

特点:
  - 基于速率的拥塞控制(不是基于窗口)
  - 适合大数据传输(科学计算、文件传输)
  - 支持 IPv6

UDT vs KCP:
  UDT:追求高吞吐量(10Gbps+)
  KCP:追求低延迟(10ms 级别)

8.3 ENet

复制代码
轻量级游戏网络库

特点:
  - 内置可靠/不可靠通道
  - 每个通道可以配置不同的可靠性
  - 常用于游戏引擎(Godot)

ENet vs KCP:
  ENet:更简单的 API,适合快速开发
  KCP:更底层的控制,适合精细调优

8.4 RUDP(Reliable UDP)

复制代码
RFC 3208 定义的可靠 UDP

特点:
  - 类似 TCP 的可靠性,但基于 UDP
  - 没有流行起来(被 QUIC 取代了)

8.5 选择指南

复制代码
┌─────────────────────────┬──────────────┐
│ 场景                     │ 推荐方案      │
├─────────────────────────┼──────────────┤
│ Web 应用                 │ QUIC (HTTP/3)│
│ 游戏服务器(自研)        │ KCP / ENet   │
│ 远程桌面                 │ KCP          │
│ 弱网/物联网              │ KCP          │
│ 大数据传输(10Gbps+)    │ UDT          │
│ 浏览器到服务器           │ QUIC         │
│ 嵌入式设备               │ KCP          │
│ 多人在线游戏             │ ENet / KCP   │
│ 直播/视频会议            │ KCP + FEC    │
└─────────────────────────┴──────────────┘

9. 实践中的常见问题

9.1 KCP 延迟没降下来

复制代码
排查步骤:

1. 检查 ikcp_update() 调用频率
   → 如果每 100ms 才调用一次,那 RTO 最小也就 100ms
   → 建议 10ms 或更小

2. 检查 output 回调是否阻塞
   → output 里做了耗时操作(如 DNS 解析、同步写日志)
   → output 应该只做 sendto()

3. 检查 MTU 设置
   → MTU 太大导致 IP 分片
   → 一个分片丢了,整个 IP 包废了
   → 建议 MTU = 1400(留 40 字节给 IP+UDP 首部)

4. 检查网络状况
   → 用 ping 看实际 RTT
   → 如果 RTT 本身就是 100ms,KCP 也降不到 10ms
   → KCP 降低的是"额外延迟",不是物理延迟

9.2 KCP 带宽消耗太大

复制代码
排查步骤:

1. 检查是否关闭了拥塞控制(nc=1)
   → 公网使用建议开启拥塞控制(nc=0)

2. 检查快速重传阈值
   → resend=1 太激进,可能导致大量不必要的重传
   → 建议 resend=2

3. 检查是否有大量重传
   → 如果重传率 > 5%,说明网络丢包严重
   → 加 FEC 可以减少重传

4. 检查窗口大小
   → 窗口太大,发送速率可能超过网络承载能力
   → 合理设置窗口 = BDP / MSS

9.3 KCP 丢包恢复慢

复制代码
排查步骤:

1. 检查 resend 参数
   → resend=0 表示关闭快速重传,只能等 RTO 超时
   → 建议 resend=2

2. 检查 RTO 设置
   → nodelay=0 时 RTO 最小 100ms
   → 建议 nodelay=1,RTO 最小 10ms

3. 检查 interval
   → interval=100ms 意味着最多 100ms 才检测到超时
   → 建议 interval=10ms

4. 检查是否启用了 FEC
   → FEC 可以在不重传的情况下恢复丢包
   → 对高丢包率网络特别有效

9.4 KCP 内存泄漏

复制代码
常见原因:

1. snd_buf 中的段没有被释放
   → ACK 处理逻辑有 bug,某些段永远收不到 ACK
   → 设置最大重传次数,超过就放弃

2. rcv_buf 中的乱序段堆积
   → 前序包丢了,后续包一直在等
   → 设置乱序超时,超时后丢弃

3. pbuf/内存池耗尽
   → 发送速率 > 接收速率,缓冲区堆积
   → 合理设置窗口大小

10. 总结

10.1 UDP 的定位

复制代码
UDP = 最小开销的传输层
  - 只加了端口号和校验和(8 字节首部)
  - 不保证可靠性、顺序性、流量控制
  - 适合:实时应用、简单查询、广播/多播
  - 不适合:需要可靠传输的场景

10.2 KCP 的定位

复制代码
KCP = 在 UDP 上构建的低延迟可靠传输
  - 用户态实现,完全可控
  - 核心优化:关闭 Nagle、快速重传、可选关闭拥塞控制
  - 代价:带宽消耗比 TCP 多 10~20%
  - 收益:延迟降低 30~40%,最大延迟降低 3 倍
  - 适合:游戏、远程桌面、弱网、嵌入式

10.3 选择原则

复制代码
1. 不需要可靠性 → UDP(视频、语音、游戏状态广播)
2. 需要可靠性 + 低延迟 → KCP(游戏、远程桌面)
3. 需要可靠性 + 高吞吐 → TCP(文件传输、Web)
4. 需要可靠性 + 低延迟 + Web → QUIC(HTTP/3)
5. 需要可靠性 + 高吞吐(10Gbps+)→ UDT

核心认知:
  - TCP 是"最大化吞吐量"的设计
  - KCP 是"最小化延迟"的设计
  - UDP 是"什么都不管"的设计
  - QUIC 是"互联网下一代标准"的设计
  - 没有银弹,根据场景选择最合适的方案

10.4 KCP 的价值

复制代码
KCP 的真正价值不在于"比 TCP 快",而在于:

1. 用户态实现:可以深度定制所有参数
2. 跨平台一致:Linux/Windows/macOS/嵌入式行为完全相同
3. 极简代码:~2000 行 C 代码,容易理解和移植
4. 灵活组合:可以和任何加密层、FEC、多路复用组合

KCP 不是要取代 TCP,而是提供了一个在特定场景下比 TCP 更好的选择。
相关推荐
一个向上的运维者1 小时前
Docker 自定义网络中容器无法通过宿主机 IP 访问服务的完整排障记录
网络·tcp/ip·docker
WIZnet1 小时前
W55RP20-EVB-MKR MicroPython 实战(14):MQTT 协议与 OneNET 平台对接
单片机·网络协议·wiznet
Kiling_07041 小时前
Java IO流:字节流实战与性能优化
java·开发语言·php
三佛科技-187366133971 小时前
AIP8P005B(SOP14)中微爱芯8位MCU用辉芒微FT60E112A SOP14替代
单片机·嵌入式硬件
utf8mb4安全女神1 小时前
子网划分【概念+实操+理解】
运维·服务器·网络
GlobalSign数字证书1 小时前
中小企业的 SSL/TLS 证书管理,有更轻量的方案
数据库·网络协议·ssl
比昨天多敲两行1 小时前
Linux信号
linux·运维·服务器
西城微科方案开发1 小时前
LED汽车打气泵PCBA方案
单片机·嵌入式硬件
码语智行1 小时前
拦截器、接口限流、过滤器、防重发/幂等性功能说明
开发语言·网络·python