在实时音视频、游戏同步、物联网等场景中,TCP 的延迟往往成为瓶颈,而裸 UDP 又缺乏可靠性保证。KCP 协议应运而生------它能在保持 UDP 低延迟优势的同时,提供可靠有序的数据传输。本文介绍一个我开源的 Rust KCP 封装库 kcp-io,帮你用写 TCP 的方式享受 UDP 的速度。
此项目是对官方最新版C的封装,所以天然适配,也适配其他语言库,如 java-kcp、kcp-netty、kcp2k 等
什么是 kcp-io?
kcp-io 是一个 Rust 网络库,对 skywind3000/kcp 的 C 实现进行了完整封装,并提供了基于 Tokio 的异步集成。你可以像使用 TcpStream / TcpListener 一样使用 KCP,同时享受比 TCP 低 30%~40% 延迟的传输体验。
核心特点:
| 特性 | 说明 |
|---|---|
| 开箱即用 | 像 TCP 一样的 KcpStream / KcpListener API,无需了解底层 KCP 细节 |
| 异步原生 | 完整的 Tokio 集成,支持 AsyncRead / AsyncWrite trait |
| 读写分离 | into_split() 支持在不同 task 中并发读写 |
| 自适应接收 | recv_kcp() 自动分配缓冲区,无需猜数据大小 |
| 分层架构 | 通过 feature flags 按需引入:FFI 绑定 → 安全封装 → 异步集成 |
| 跨平台 | 支持 Windows / Linux / macOS,已处理 Windows 特有的 UDP 陷阱 |
KCP vs TCP:为什么需要 KCP?
KCP 是一个运行在 UDP 之上的 ARQ(自动重传请求) 协议。与 TCP 相比:
| 对比维度 | TCP | KCP |
|---|---|---|
| 传输层 | 内核态 TCP 栈 | 用户态 UDP + KCP 引擎 |
| 延迟 | 受 Nagle、延迟 ACK 等影响 | 可配置为极低延迟模式 |
| 丢包恢复 | 依赖 RTO 超时,恢复慢 | 快速重传 + 选择性重传 |
| 拥塞控制 | 强制拥塞窗口 | 可选,可关闭以牺牲公平性换取速度 |
| 带宽开销 | 较低 | 多消耗 10%~20%(用带宽换延迟) |
适用场景:实时对战游戏、音视频通话、远程桌面、IoT 设备通信、对延迟敏感的 RPC 调用。
5 分钟快速上手
1. 添加依赖
toml
[dependencies]
kcp-io = "0.0.4"
tokio = { version = "1", features = ["full"] }
2. 服务端:接受连接 & 回显
rust
use kcp_io::{KcpListener, KcpSessionConfig};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// 绑定地址,使用快速模式预设
let mut listener = KcpListener::bind("0.0.0.0:9090", KcpSessionConfig::fast()).await?;
println!("KCP 服务器已启动,监听端口 9090");
loop {
// accept() 等待新连接 ------ 和 TcpListener 用法一样
let (mut stream, addr) = listener.accept().await?;
println!("[{}] 新连接,conv={}", addr, stream.conv());
tokio::spawn(async move {
loop {
match stream.recv_kcp().await {
Ok(data) => {
println!("[{}] 收到 {} 字节", addr, data.len());
// 原样回显
stream.send_kcp(&data).await.ok();
}
Err(e) => {
println!("[{}] 连接断开:{}", addr, e);
break;
}
}
}
});
}
}
3. 客户端:连接 & 收发
rust
use kcp_io::{KcpStream, KcpSessionConfig};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// 连接服务端 ------ 和 TcpStream::connect() 用法一样
let mut stream = KcpStream::connect("127.0.0.1:9090", KcpSessionConfig::fast()).await?;
println!("已连接!conv={}", stream.conv());
// 发送数据
stream.send_kcp(b"Hello, KCP!").await?;
println!("已发送:Hello, KCP!");
// 接收回显 ------ 自动分配缓冲区,无需指定大小
let data = stream.recv_kcp().await?;
println!("收到回显:{}", String::from_utf8_lossy(&data));
Ok(())
}
运行效果:
# 终端1
KCP 服务器已启动,监听端口 9090
[127.0.0.1:54321] 新连接,conv=305419896
[127.0.0.1:54321] 收到 11 字节
# 终端2
已连接!conv=305419896
已发送:Hello, KCP!
收到回显:Hello, KCP!
进阶用法
读写分离:并发读写
在很多业务场景中你需要同时读和写------比如一个 task 负责接收心跳或推送,另一个 task 负责发送请求。kcp-io 提供了 into_split() 将流拆分为独立的读半和写半,和 Tokio 的 TcpStream::into_split() 用法一致:
rust
use kcp_io::{KcpStream, KcpSessionConfig};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let stream = KcpStream::connect("127.0.0.1:9090", KcpSessionConfig::fast()).await?;
// 拆分为读半和写半
let (mut read_half, mut write_half) = stream.into_split();
// 读取任务:持续接收数据
let reader = tokio::spawn(async move {
loop {
match read_half.recv_kcp().await {
Ok(data) => println!("收到:{}", String::from_utf8_lossy(&data)),
Err(_) => break,
}
}
});
// 写入任务:发送多条消息
write_half.send_kcp(b"消息1").await?;
write_half.send_kcp(b"消息2").await?;
write_half.send_kcp(b"消息3").await?;
// 关闭写端(读端也会随之关闭)
write_half.close().await;
reader.await?;
Ok(())
}
兼容 Tokio 生态:AsyncRead / AsyncWrite
KcpStream 实现了 Tokio 的 AsyncRead 和 AsyncWrite trait,可以无缝对接 tokio-util 的 codec、序列化框架等基于 Tokio I/O 的生态:
rust
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use kcp_io::{KcpStream, KcpSessionConfig};
async fn example() -> std::io::Result<()> {
let mut stream = KcpStream::connect("127.0.0.1:9090", KcpSessionConfig::fast())
.await
.map_err(|e| std::io::Error::other(e))?;
// 标准 Tokio I/O 方式写入
stream.write_all(b"hello via AsyncWrite").await?;
// 标准 Tokio I/O 方式读取
let mut buf = [0u8; 1024];
let n = stream.read(&mut buf).await?;
println!("收到 {} 字节", n);
Ok(())
}
自定义 KCP 参数
kcp-io 提供了三种预设配置,也支持完全自定义:
rust
use kcp_io::{KcpSessionConfig, KcpConfig};
use std::time::Duration;
// 预设1:默认模式 ------ 保守方案,带宽占用最低
let _config = KcpSessionConfig::default();
// 预设2:普通模式 ------ 延迟与带宽均衡
let _config = KcpSessionConfig::normal();
// 预设3:快速模式 ------ 最低延迟,推荐大多数场景使用
let _config = KcpSessionConfig::fast();
// 完全自定义
let config = KcpSessionConfig {
kcp_config: KcpConfig {
nodelay: true, // 开启无延迟模式
interval: 20, // 内部更新间隔 20ms
resend: 2, // 快速重传触发次数
nc: true, // 关闭拥塞控制
mtu: 1200, // 适合受限网络的较小 MTU
snd_wnd: 256, // 发送窗口
rcv_wnd: 256, // 接收窗口
stream_mode: false, // 消息模式(保留消息边界)
},
flush_interval: Duration::from_millis(20),
timeout: Some(Duration::from_secs(30)), // 30秒超时
flush_write: true, // 写入后立即刷新
recv_buf_size: 65536, // UDP 接收缓冲区大小
};
仅使用 Core 层(不依赖 Tokio)
如果你有自己的异步运行时或者想手动管理 KCP 状态机,可以只引入 kcp-core feature:
toml
[dependencies]
kcp-io = { version = "0.0.4", default-features = false, features = ["kcp-core"] }
rust
use kcp_io::core::{Kcp, KcpConfig};
use std::io;
fn main() {
// 创建 KCP 实例,conv=1,提供输出回调
let mut kcp = Kcp::new(1, |data: &[u8]| -> io::Result<usize> {
// 这里通过你自己的 UDP socket 发送数据
println!("KCP 输出 {} 字节", data.len());
Ok(data.len())
}).unwrap();
// 应用快速模式配置
kcp.apply_config(&KcpConfig::fast()).unwrap();
// 发送数据
kcp.send(b"Hello from core layer!").unwrap();
// 驱动 KCP 状态机(需要定期调用)
kcp.update(0);
kcp.flush().unwrap();
}
预设配置对比
| 预设 | nodelay | interval | 快速重传 | 拥塞控制 | 发送窗口 | 适用场景 |
|---|---|---|---|---|---|---|
default() |
关闭 | 100ms | 关闭 | 开启 | 32 | 带宽受限、对延迟不敏感 |
normal() |
开启 | 40ms | 2次 | 开启 | 64 | 一般实时应用 |
fast() |
开启 | 10ms | 2次 | 关闭 | 128 | 游戏、音视频、低延迟 RPC |
可靠性验证
项目内置了丢包模拟测试,通过一个 UDP 有损代理(Lossy Proxy)验证 KCP 在不同丢包率下的消息可靠性:
| 测试场景 | 丢包率 | 消息数 | 结果 |
|---|---|---|---|
| 轻度丢包 | 10% | 20 条 | 全部正确送达 |
| 中度丢包 | 30% | 20 条 | 全部正确送达 |
| 重度丢包 | 50% | 10 条 | 全部正确送达 |
即使在 50% 丢包率 这种极端条件下,所有消息依然能正确、有序地送达------这就是 KCP ARQ 重传机制的可靠性保证。
写在最后
开发 kcp-io 的初衷是在自己的项目中需要一个低延迟的可靠 UDP 传输方案,市面上的 Rust KCP 库要么年久失修,要么 API 不够友好,于是决定自己封装一个。目前这个库已经在我的几个项目中稳定运行,拿出来开源希望对有同样需求的同学有所帮助。
如果你在使用中遇到任何问题,欢迎到 GitHub 提 Issue 交流: