KCP - A Fast and Reliable ARQ Protocol
Go to file
2016-02-19 11:18:53 +08:00
donation.png new donation 2014-12-30 22:37:23 +08:00
ikcp.c commit kcp 2015-09-02 17:01:41 +08:00
ikcp.h fixed: message size of ack/una/winprobe could exceed mtu by 24 bytes 2015-02-21 02:33:46 +08:00
LICENSE Initial commit 2014-12-27 00:46:34 +08:00
README.md 更新文档 2016-02-19 11:18:53 +08:00
test.cpp new comment 2014-12-29 00:43:25 +08:00
test.h new comment 2014-12-29 00:43:25 +08:00

KCP - A Fast and Reliable ARQ Protocol

简介

KCP是一个快速可靠协议能以比 TCP浪费10%-20%的带宽的代价,换取平均延迟降低 30%-40%且最大延迟降低三倍的传输效果。纯算法实现并不负责底层协议如UDP的收发需要使用者自己定义下层数据包的发送方式以 callback的方式提供给 KCP。 连时钟都需要外部传递进来,内部不会有任何一次系统调用。

整个协议只有 ikcp.h, ikcp.c两个源文件可以方便的集成到用户自己的协议栈中。也许你实现了一个P2P或者某个基于 UDP的协议而缺乏一套完善的ARQ可靠协议实现那么简单的拷贝这两个文件到现有项目中稍微编写两行代码即可使用。

技术特性

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是选择性重传只重传真正丢失的数据包。

快速重传:

发送端发送了1,2,3,4,5几个包然后收到远端的ACK: 1, 3, 4, 5当收到ACK3时KCP知道2被跳过1次收到ACK4时知道2被跳过了2次此时可以认为2号丢失不用等超时直接重传2号包大大改善了丢包时的传输速度。

延迟ACK vs 非延迟ACK

TCP为了充分利用带宽延迟发送ACKNODELAY都没用这样超时计算会算出较大 RTT时间延长了丢包时的判断过程。KCP的ACK是否延迟发送可以调节。

UNA vs ACK+UNA

ARQ模型响应有两种UNA此编号前所有包已收到如TCP和ACK该编号包已收到光用UNA将导致全部重传光用ACK则丢失成本太高以往协议都是二选其一而 KCP协议中除去单独的 ACK包外所有包都有UNA信息。

非退让流控:

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

基本使用

  1. 创建 KCP对象

    // 初始化 kcp对象conv为一个表示会话编号的整数和tcp的 conv一样通信双
    // 方需保证 conv相同相互的数据包才能够被认可user是一个给回调函数的指针
    ikcpcb *kcp = ikcp_create(conv, user);
    
  2. 设置回调函数:

    // KCP的下层协议输出函数KCP需要发送数据时会调用它
    // buf/len 表示缓存和长度
    // user指针为 kcp对象创建时传入的值用于区别多个 KCP对象
    int udp_output(const char *buf, int len, ikcpcb *kcp, void *user)
    {
      ....
    }
    // 设置回调函数
    kcp->output = udp_output;
    
  3. 循环调用 update

    // 以一定频率调用 ikcp_update来更新 kcp状态并且传入当前时钟毫秒单位
    // 如 10ms调用一次或用 ikcp_check确定下次调用 update的时间不必每次调用
    ikcp_update(kcp, millisec);
    
  4. 输入一个下层数据包:

    // 收到一个下层数据包比如UDP包时需要调用
    ikcp_input(kcp, received_udp_packet, received_udp_size);
    

    处理了下层协议的输出/输入后 KCP协议就可以正常工作了使用 ikcp_send 来向 远端发送数据。而另一端使用 ikcp_recv(kcp, ptr, size)来接收数据。

协议配置

协议默认模式是一个标准的 ARQ需要通过配置打开各项加速开关

  1. 工作模式:

    int ikcp_nodelay(ikcpcb *kcp, int nodelay, int interval, int resend, int nc)
    

    nodelay :是否启用 nodelay模式0不启用1启用。 interval :协议内部工作的 interval单位毫秒比如 10ms或者 20ms resend 快速重传模式默认0关闭可以设置22次ACK跨越将会直接重传 nc 是否关闭流控默认是0代表不关闭1代表关闭。 普通模式:`ikcp_nodelay(kcp, 0, 40, 0, 0); 极速模式: ikcp_nodelay(kcp, 1, 10, 2, 1);

  2. 最大窗口:

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

    该调用将会设置协议的最大发送窗口和最大接收窗口大小默认为32. 这个可以理解为 TCP的 SND_BUF 和 RCV_BUF只不过单位不一样 SND/RCV_BUF 单位是字节,这个单位是包。

  3. 最大传输单元:

    纯算法协议并不负责探测 MTU默认 mtu是1400字节可以使用ikcp_setmtu来设置该值。该值将会影响数据包归并及分片时候的最大传输单元。

  4. 最小RTO

    不管是 TCP还是 KCP计算 RTO时都有最小 RTO的限制即便计算出来RTO为40ms由于默认的 RTO是100ms协议只有在100ms后才能检测到丢包快速模式下为30ms可以手动更改该值

    kcp->rx_minrto = 10;
    

更多内容

协议的使用和配置都是很简单的,大部分情况看完上面的内容基本可以使用了。如果你需要进一步进行精细的控制,比如改变 KCP的内存分配器或者你需要更有效的大规模调度 KCP链接比如 3500个以上或者如何更好的同 TCP结合那么可以继续延伸阅读

相关应用

  • dog-tunnel: GO开发的网络隧道使用 KCP极大的改进了传输速度并移植了一份 GO版本 KCP
  • lua-kcpKCP的 Lua扩展用于 Lua服务器
  • asio-kcp: 使用 KCP的完整 UDP网络库完整实现了基于 UDP的链接状态管理会话控制KCP协议调度等

性能比较

如果不丢包那么 KCP和 TCP性能差不多KCP不会有任何优势但是网络会卡造成卡的原因就是丢包和抖动有同学在内网这样好的环境下没有用任何丢包模拟直接跑跑出来的数据是差不多的但是放到公网上放到3G/4G网络情况下差距就很明显了公网在高峰期有平均接近10%的丢包wifi/3g/4g下更糟糕这正是造成各种网络卡顿的元凶。

感谢 asio-kcp 的作者 zhangyuan 对 KCP 与 enet, udt做过的一次横向评测结论如下

  • ASIO-KCP has good performace in wifi and phone network(3G, 4G).
  • Extra using 20% ~ 50% network flow for speed improvement.
  • The kcp is the first choice for realtime pvp game.
  • The lag is less than 1 second when network lag happen. 3 times better than enet when lag happen.
  • The enet is a good choice if your game allow 2 second lag.
  • UDT is a bad idea. It always sink into badly situation of more than serval seconds lag. And the recovery is not expected.
  • enet has the problem of lack of doc. And it has lots of functions that you may intrest. kcp's doc is chinese. Good thing is the function detail which is writen in code is english. And you can use asio_kcp which is a good wrap.
  • The kcp is a simple thing. You will write more code if you want more feature.
  • UDT has a perfect doc. UDT may has more bug than others as I feeling.

具体见:横向比较这里。截取一段在网络糟糕时asio-kcp/enet的延迟数据

worst network lag happen:
asio: 10:51.21
291  295   269   268   231   195   249   230   225   204

enet: 10:51.21
1563   1520    1470    1482    1438    1454    1412    1637    1588    1540

我当年主要测试了 KCP和 TCP/UDT的比较扫了一眼 libenet觉得协议实现中规中矩缺乏很多现代传输协议的技术所以并没有详细测试。而 asio-kcp的作者同时给出了KCP/enet/udt三者的详细比较为更多犹豫选择使用那一套的人提供了更多指引。

欢迎捐赠

欢迎使用支付宝对该项目进行捐赠

欢迎使用支付宝手扫描上面的二维码,对该项目进行捐赠。捐赠款项将用于改进 KCP性能以及 后续持续优化。

欢迎关注

blog: http://www.skywind.me

zhihu: https://www.zhihu.com/people/skywind3000