文章目录
- 前言
- [UDP 协议](#UDP 协议)
-
- [UDP 的关键指标/特性](#UDP 的关键指标/特性)
- [UDP 的典型应用场景](#UDP 的典型应用场景)
- [KCP 协议的基础](#KCP 协议的基础)
-
- [KCP 的构造](#KCP 的构造)
- [KCP 协议特性](#KCP 协议特性)
- [KCP 的可靠传输机制------ARQ](#KCP 的可靠传输机制——ARQ)
-
- [三种 ARQ 机制对比](#三种 ARQ 机制对比)
- [KCP 的选择性重传](#KCP 的选择性重传)
- 一、基础机制:选择性重传(SR)
- [二、KCP 对 SR 的增强策略](#二、KCP 对 SR 的增强策略)
- [KCP 的激进重传策略------低延迟设计!](#KCP 的激进重传策略——低延迟设计!)
-
- [**1、 快速重传 (Fast Retransmit):跳过漫长的等待**](#1、 快速重传 (Fast Retransmit):跳过漫长的等待)
- [**2、选择性重传 (Selective Repeat / SACK-like Behavior):精准打击,避免浪费**](#2、选择性重传 (Selective Repeat / SACK-like Behavior):精准打击,避免浪费)
- [低延迟 ACK 机制能综合实现 "选择性重传" + "快速重传"](#低延迟 ACK 机制能综合实现 “选择性重传” + “快速重传”)
- [**3、激进的超时重传 (Aggressive RTO)------解决尾包延迟问题**](#3、激进的超时重传 (Aggressive RTO)——解决尾包延迟问题)
- 低延迟的本质就是用带宽换时间
- [再低延迟也总有意外------KCP 超时淘汰机制](#再低延迟也总有意外——KCP 超时淘汰机制)
-
- [KCP 滑动窗口](#KCP 滑动窗口)
- [KCP 的超时淘汰机制](#KCP 的超时淘汰机制)
- [KCP 应对网络拥塞的方案------非退让流量控制](#KCP 应对网络拥塞的方案——非退让流量控制)
- 总结
推荐一个零声教育学习教程,个人觉得老师讲得不错,分享给大家:[Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK等技术内容,点击立即学习: https://github.com/0voice 链接。
前言
我是零声学院的小学徒,我虽然不是计算机科班出身,但我是学数学的,我认为对于一个我不熟悉的东西是需要作知识汇总的,尽量让自己理解清楚事情的全貌,并且对知识作出恰当的比喻和类比,以达本意。我将仿效上一篇介绍 TCP 协议栈的文章笔法(原文链接 在此),详细的介绍在工程上(尤其是游戏工程)具有广泛应用的可靠、低延迟的通信协议------KCP.
先运行过去写过的代码(与 UDP 协议相关),执行过程使用 Wireshark 去抓包,记录一路上的 UDP 报文。从现实的 UDP 报文出发,先介绍 UDP 协议栈的简单简陋,同时又看到它的低延迟潜力,只要我们在此基础上,稍做设计,便能设计出可靠的低延迟传输协议------KCP。 而后逐步地深入介绍 KCP 协议栈的各个机制的内容,给大家一个对 KCP 的整体性的认知。事不宜迟,我们现在开始。
我过去曾写过一篇文章,它是介绍远程 DNS 域名查询服务的,原文链接 在此。在那篇文章中,传输层是采用 UDP
协议的,读者可查看那篇文章的代码,套接字使用数据报模式就是使用 UDP 的开始,
c
int sockfd = socket(AF_INET, SOCK_DGRAM, 0); // 这是一个通讯方式的参数设定
// AF_INET 表示使用 IPv4 的地址协议。
// SOCK_DGRAM 表示"数据报套接字"
// 0 指代自动选择与 type 匹配的默认协议。对于 SOCK_DGRAM,默认协议是 UDP(等价于显式指定 IPPROTO_UDP)。
// 返回的整数是一个文件描述符,唯一标识当前创建的套接字。
// 后续通过此描述符进行网络操作:bind():绑定 IP 和端口(服务端)。sendto():发送数据。recvfrom():接收数据。close():关闭套接字。
if (sockfd < 0) {
return -1; // 这表示通讯状态设置不成功
}
如果读者要问假如我想使用 TCP 协议怎么办?那也很简单,套接字使用数据流模式就 OK 了。
c
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
回到正题上,我们把使用 UDP 传输协议的 DNS 的域名查询代码运行一下,利用这个程序去查询零声教育的域名
bash
qiming@qiming:~/share/CTASK/TCP_test/DNS_test$ ./dns www.0voice.com
connect : 0
www.0voice.com has address 43.139.121.27
Time to live: 0 minutes , 0 seconds
qiming@qiming:~/share/CTASK/TCP_test/DNS_test$
零声教育的域名是 43.139.121.27
。此时,我们早已开启 Wireshark 来进行网络抓包了
于是找到了相匹配的两份 UDP 报文
我们先从 UDP 协议讲起,随后就会将 KCP 协议。
UDP 协议
我们亲眼看到真实的 UDP 报文后,我们再从理论的 UDP 报文来看
c
0 1
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 <-- 对齐 2 个字节 (16 位)
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Port |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Destination Port |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| checksum | <-- UDP 报文的头部(共 8 个字节)
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Data Payload (可变长度) | <-- 数据荷载,实际数据,对齐值为 2 个字节
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
字段名称 | 长度(字节) | 说明 |
---|---|---|
源端口号 | 2 | 发送方端口号,用于标识发送方的应用程序。 |
目的端口号 | 2 | 接收方端口号,用于标识接收方的应用程序。 |
长度 | 2 | UDP报文的总长度,包括UDP头部和数据部分,以字节为单位。最小值为8字节(仅头部)。 |
检验和 | 2 | 用于检测UDP报文在传输过程中是否出现错误。 |
数据 | 可变 | 应用层提供的数据,长度可变。 |
UDP 报文是极其简单的,没有复杂的东西。
UDP 的关键指标/特性
1、无连接:
- 核心特性。 发送数据报之前不需要像 TCP 那样进行"三次握手"建立连接。
- 意义: 通信开销极小,延迟非常低(发送第一个数据包前几乎没有准备时间)。适用于需要快速响应的场景。
2、不可靠交付:
- 核心特性。 UDP 协议本身不提供任何机制来保证数据报一定能到达目的地。
- 具体表现:
- 不确认 (No Acknowledgement): 发送方发送后不会等待接收方的确认 (ACK)。
- 不重传 (No Retransmission): 如果数据报丢失(网络拥塞、路由器丢弃、校验失败等),发送方不会自动重传。
- 不保证顺序 (No Ordering Guarantee): 后发送的数据报可能比先发送的早到达接收方。UDP 不负责重新排序。
- 不防止重复 (No Duplication Prevention): 网络问题可能导致同一个数据报被多次送达接收方(通常由底层 IP 网络引起)。
- 意义: 可靠性保证(如果需要)必须由应用程序自己实现(例如,在应用层添加序列号、超时重传、确认机制),或者应用程序能够容忍这些情况(如直播丢几帧画面)。
3、无拥塞控制:
- UDP 没有内置的机制来感知网络拥塞并调整发送速率。
- 意义: UDP 发送方会尽可能快地按照应用程序的要求发送数据,无论网络当前是否拥塞。这可能导致:
- 加剧拥塞: 在网络已经拥塞时,UDP 流量会继续冲击网络,可能使情况恶化,影响自身和其他协议(尤其是 TCP)的性能。
- 公平性问题: 对 TCP 流不公平,因为 TCP 会主动降低速率,而 UDP 不会。
- 应用程序如果需要在拥塞环境下良好运行,必须自己实现某种形式的速率控制和拥塞感知。
4、无流量控制:
- UDP 没有机制让接收方告诉发送方"慢一点,我处理不过来了"。
- 意义: 如果发送方发送速度远快于接收方的处理能力(或接收方应用程序读取数据的速度),接收方的缓冲区会溢出,导致数据报被丢弃。应用程序需要自行处理速率匹配问题。
5、面向数据报 (Message-Oriented):
- UDP 每次发送和接收的是一个完整的数据报(报文)。
- 意义:
- 保留消息边界: 应用程序发送多少次 sendto 调用,接收方就需要对应多少次 recvfrom 调用。每个 recvfrom 调用返回一个完整的数据报(或报错)。数据报之间是独立的。
- 与 TCP 的字节流模型形成鲜明对比(TCP 没有消息边界,数据是连续的字节流)。
6、头部开销小:
- 固定头部仅 8 字节,远小于 TCP 头部的 20 字节(通常)或更多(带选项)。
- 意义: 对于传输小数据包的应用(如 DNS 查询、心跳包),UDP 的协议开销比例更低,效率更高。
7、支持广播和多播(对局域网内的所有主机可见):
- UDP 数据报可以发送到:
- 单播 (Unicast): 单个目标主机(最常见)。
- 广播 (Broadcast): 同一子网内的所有主机(目的 IP 地址为特定子网的广播地址,如 192.168.1.255)。
- 多播 (Multicast): 订阅了特定多播组的一组主机(目的 IP 地址为 D 类地址 224.0.0.0 - 239.255.255.255)。
- 意义: 这是 TCP 无法做到的(TCP 严格是点对点连接)。UDP 是实现如网络发现、视频会议、实时数据分发(股票行情)等多播/广播应用的基石。路由器需要支持 IGMP 等协议来管理多播组成员关系。
UDP vs TCP 关键对比
特性 | UDP | TCP |
---|---|---|
连接 | 无连接 | 面向连接 (三次握手) |
可靠性 | 不可靠 (不确认、不重传、不排序、不保序) | 可靠 (确认、重传、排序、保序) |
交付保证 | 尽力而为交付 (Best Effort) | 保证按序、无差错、不丢失、不重复的交付 |
流量控制 | 无 | 有 (滑动窗口) |
拥塞控制 | 无 | 有 (多种复杂算法) |
数据模型 | 面向数据报 (保留消息边界) | 面向字节流 (无消息边界) |
头部大小 | 小 (8 字节) | 大 (通常 20 字节 + 选项) |
速度/延迟 | 快 (连接建立开销小,无确认重传延迟) | 相对较慢 (连接建立/断开开销,确认重传延迟) |
广播/多播 | 支持 | 不支持 (仅单播) |
适用场景 | 实时应用、简单查询、多播广播、容忍丢包 | Web 浏览、文件传输、邮件、需要可靠性的应用 |
UDP 的典型应用场景
1、对延迟敏感,能容忍少量丢失的应用:
- 实时音视频流 (VoIP, Video Conferencing, Live Streaming)
- 在线游戏 (状态更新、位置同步)
- 实时金融行情推送
2、简单查询/响应,数据量小:
- DNS (域名解析)
- DHCP (动态获取 IP 地址)
- SNMP (网络管理)
- TFTP (简单文件传输)
3、多播和广播应用:
- 服务发现 (如 mDNS/Bonjour)
- 路由协议更新 (如 RIP)
- 流媒体分发
4、在应用层实现可靠性的场景:
- 某些自定义协议或基于 UDP 构建的可靠传输协议 (如 QUIC - HTTP/3 的基础, DCCP, 部分 VPN 协议)。
UDP 是一种极其精简、快速但"不可靠"的传输层协议。它的核心价值在于低延迟 和 低开销,适用于那些对时效性要求极高、能够容忍一定程度数据丢失或由应用层自行处理可靠性的场景。理解其报文结构(特别是端口号、长度、校验和的作用)以及无连接、不可靠、无流控/拥控的特性,是正确使用 UDP 或选择 UDP 还是 TCP 的关键。虽然 UDP 本身简单,但在其之上构建可靠的应用层协议(如 QUIC)是当前网络协议发展的一个重要方向。
KCP 协议的基础
我们知道 UDP 的传输机制与 TCP 是极其不一样的,强调 低延迟 和 低开销,在游戏等领域有着广泛的应用。那么,如何在 UDP 基础之上,作些改动,让其变得更加可靠但依旧低延迟低开销呢?KCP 协议通信方案!
KCP的全称是Quick UDP Protocol,即"快速用户数据报协议"。KCP 协议是一种基于 UDP 的可靠传输协议,由国内开发者 skywind3000 设计并开源。它的核心目标是在保证一定可靠性的前提下,尽可能地降低传输延迟,特别适合对实时性要求高的网络应用。
KCP 通过 24 字节精简头部 + 双队列缓冲管理 (发送/接收队列 + 缓冲区) + 时间戳驱动更新(滑动窗口的超时淘汰机制),在 UDP 上实现了低延迟可靠传输。其核心代码约 1000 行 C 语言(见 GitHub),适合嵌入实时应用(如游戏引擎)。开发者需注意它不是 TCP 的替代品,而是为特定场景优化的传输层增强工具。
KCP 的构造
KCP 协议通过自定义报文头在 UDP 之上构建可靠传输,其设计追求简洁高效和低延迟。以下是 KCP 报文结构、核心字段和代码实现的关键解析:
c
struct kcp_segment_header {
uint32_t conv; // 会话 ID(虚拟连接标识)
uint8_t cmd; // 命令类型(见下表)
uint8_t frg; // 分片编号(0=最后一个分片,>0 表示后续还有分片)
uint16_t wnd; // 接收窗口剩余大小(接收方能接收的数据量)
uint32_t ts; // 时间戳(发送时刻,用于 RTT 计算)
uint32_t sn; // 序列号(当前包的序号)
uint32_t una; // 待确认序号(发送方尚未收到的 ACK 的起始序号)
uint32_t len; // 数据长度(不含头部的数据部分长度)
};
✅ 头部固定 24 字节,后续紧跟用户数据(若有)。
网络套接字socket 与 KCP 控制块相关联(这与 TCP 一样,TCP 协议栈维护 TCB,即 TCP 控制块),它主要是放置 KCP 协议栈的内容(里面当然是包括 "滑动窗口" 的)。
c
// KCP 会话控制块(简化版)
struct ikcpcb {
// 连接标识
uint32_t conv;
// 窗口管理
uint32_t snd_una; // 最早未确认包序号
uint32_t snd_nxt; // 下一个待发包序号
uint32_t rcv_nxt; // 下一个待接收包序号
// 队列管理
struct IQUEUEHEAD snd_queue; // 待发送队列
struct IQUEUEHEAD rcv_queue; // 乱序接收队列
struct IQUEUEHEAD snd_buf; // 已发送等待 ACK 队列
struct IQUEUEHEAD rcv_buf; // 按序接收队列
// 超时管理
uint32_t current; // 当前时间戳
uint32_t rx_rttval; // RTT 波动值
uint32_t rx_srtt; // 平滑 RTT
uint32_t rx_rto; // 当前 RTO 值
// 配置参数
uint32_t mtu; // 最大传输单元(默认 1400)
uint32_t fastresend; // 快速重传触发阈值(默认 2)
uint32_t nodelay; // 是否启用无延迟模式(0/1)
};
KCP 协议特性
以下是 KCP 协议的关键特点和原理:
- 底层使用 UDP:
- KCP 本身不处理底层网络传输,它构建在 UDP 之上。这意味着它继承了 UDP 的无连接、低开销、无拥塞控制(默认)等特性。
- 它通过自己的机制在 UDP 之上实现了可靠传输。
- 核心机制:ARQ (自动重传请求):
- 和 TCP 一样,KCP 也使用确认号、超时重传、序列号等机制来保证数据包的可靠、有序到达。
- 它实现了自己的滑动窗口来进行流量控制。
- 追求低延迟的关键设计:
- 更激进的重传策略: 这是 KCP 降低延迟的核心。
- 快速重传: 当检测到丢包(比如收到 3 个重复的 ACK)时,KCP 会立即重传丢失的包,而不像标准的 TCP RTO (重传超时) 那样需要等待一个较长的超时时间(通常至少 200ms)。KCP 的 RTO 最小值可以设置得非常低(如 10ms 甚至更低)。
- 选择性重传: KCP 可以选择性地只重传真正丢失的数据包,而不是像 TCP SACK 出现之前那样可能需要重传整个窗口的数据(尽管现代 TCP 也支持 SACK)。
- 非退让的流控: KCP 的流量控制算法(窗口大小调整)不像 TCP 那样对丢包反应"剧烈"。TCP 在发生拥塞(丢包)时会大幅减小拥塞窗口(cwnd),导致传输速率骤降。KCP 的窗口调整可以配置得更平滑,减少因单次丢包造成的传输速率波动和恢复时间,从而保持更稳定的低延迟。用户可以根据网络状况灵活配置其拥塞控制策略的激进程度。
- 更小的传输单元: KCP 鼓励更小的数据分片传输,有助于降低单个包丢失对整个传输的影响。
- 更快的 ACK: KCP 可以配置为更及时地发送 ACK 确认包,减少发送端等待确认的时间。
- 更激进的重传策略: 这是 KCP 降低延迟的核心。
性能方面的优点:
- 显著降低延迟: 在存在一定丢包和波动的网络环境下(如移动网络、跨国网络),KCP 通常能比 TCP 提供低得多的端到端延迟。
- 可配置性强: 开发者可以通过调整一系列参数(如 RTO 最小值、快速重传触发条件、窗口大小、是否启用 nodelay 等)来精细控制 KCP 的行为,在延迟、带宽利用率和抗丢包能力之间进行权衡,以适应不同的应用场景和网络条件。
- 用户态实现: 作为应用层协议,易于集成和调试,不依赖操作系统内核。
性能方面的缺点/代价:
- 更高的带宽消耗: 更激进的重传和更快的 ACK 意味着在网络状况不佳时,KCP 会比 TCP 产生更多的冗余数据包(重传包、ACK包),占用更多带宽。
- 可能加剧网络拥塞: 如果配置过于激进且网络本身已经拥塞,KCP 的大量重传可能会进一步恶化网络状况(需要合理配置流控和拥塞避免策略)。TCP 的"退让"特性在维持整个网络稳定性方面是有价值的。
- 实现复杂度: 应用开发者需要理解并合理配置 KCP 的参数才能达到最佳效果,这比直接使用 TCP 稍复杂。
- 可靠性依赖实现: 其可靠性完全依赖于自身的 ARQ 实现,不像 TCP 经过了几十年的广泛验证和优化。
主要应用场景:
- 实时网络游戏: 尤其是动作类、MOBA、FPS 等对延迟极其敏感的游戏,是 KCP 最经典的应用领域。手游中使用尤其广泛。
- 实时音视频通话: 如视频会议、直播连麦等,需要低延迟保证流畅互动。
- 弱网环境优化: 在移动网络(4G/5G)、长距离链路(跨国)、或质量不稳定的 Wi-Fi 环境下,提升应用的响应速度和流畅性。
- 需要低延迟的远程控制/操作: 如云桌面、远程机器人控制等。
- 替代 TCP 在某些特定延迟敏感场景的应用。
那么 KCP 与 TCP 有何相同点呢?
KCP 与 TCP 的基础可靠性机制相同
机制 | 作用 | KCP & TCP 实现方式 |
---|---|---|
序列号 (Sequence Number) | 标识数据包顺序 | 每个数据包携带唯一序列号,接收方按序重组 |
确认应答 (ACK) | 告知发送方数据接收状态 | 接收方发送 ACK 包(含已接收的最大连续序列号) |
超时重传 (RTO) | 应对丢包 | 未收到 ACK 的包在超时后重传 |
滑动窗口 (Sliding Window) | 流量控制与批量传输 | 限制发送方可发送的数据量,通过 ACK 推进窗口释放空间 |
✅ 两者均通过 序列号 + ACK + 重传 + 滑动窗口 实现可靠传输(本质是 ARQ 自动重传请求)
KCP 与 TCP 的滑动窗口的核心逻辑相同
概念 | 作用 | KCP & TCP 一致性 |
---|---|---|
发送窗口 (snd_una) | 跟踪最早未确认包 | 窗口左边界,收到 ACK 后向右移动 |
接收窗口 (rcv_nxt) | 期待接收的下一个包序号 | 接收方按序交付数据后向右移动 |
窗口大小 (snd_wnd/rcv_wnd) | 控制传输速率 | 限制发送速率(避免淹没接收方) |
✅ 窗口滑动逻辑完全一致:ACK 推动发送窗口,连续数据推动接收窗口。
KCP 与 TCP 的基础组件相同
组件 | 功能 | KCP & TCP |
---|---|---|
重传计时器 | 检测丢包超时 | 每个发送包独立计时,超时触发重传 |
缓冲区管理 | 缓存已发送未确认/乱序到达的数据 | 发送/接收端均维护队列 |
校验和 | 检测数据损坏 | 可选支持(KCP 依赖下层 UDP 校验,TCP 强制) |
总结:异同本质
维度 | KCP | TCP |
---|---|---|
基础结构 | ✅ 与 TCP 完全相同 | ✅ 标准滑动窗口 |
窗口单位 | ❗ 包数量(非字节) | 字节数 |
控制权 | ❗ 应用层可编程 | 内核固定控制 |
拥塞控制 | ❗ 需开发者实现 | 内置完善算法 |
ACK 机制 | ❗ 支持无延迟 ACK + 精确重传信息 | 依赖系统实现和扩展 |
KCP 的可靠传输机制------ARQ
KCP 协议的核心 ARQ(自动重传请求)策略是 选择性重传(Selective Repeat, SR),但在此基础上进行了深度优化和激进参数调整。其实,这点跟 TCP 没啥区别。
三种 ARQ 机制对比
ARQ(Automatic Repeat reQuest) 是可靠传输协议的核心机制,其三种经典实现方式------停等式(Stop-and-Wait)、回退 N 帧(Go-Back-N) 和选择性重传(Selective Repeat)------构成了现代网络协议(如 TCP、KCP)的基础。以下是它们的详细对比及与 KCP/TCP 的关联:
特性 | 停等式 (Stop-and-Wait) | 回退 N 帧 (Go-Back-N, GBN) | 选择性重传 (Selective Repeat, SR) |
---|---|---|---|
发送窗口大小 | 1 个包 | N 个包(N>1) | N 个包(N>1) |
接收窗口大小 | 1 个包 | 1 个包 | N 个包 |
重传策略 | 超时后重发当前包 | 超时后重发所有未确认包 | 仅重传丢失的包 |
乱序处理 | 丢弃乱序包 | 丢弃乱序包 | 缓存乱序包 |
传输效率 | ⭐ 最低(每包需单独确认) | ⭐⭐ 中等(批量发送但重传开销大) | ⭐⭐⭐ 最高(仅重传丢失包) |
带宽利用率 | 低(RTT 内只能发 1 个包) | 中(流水线发送,但重传冗余) | 高(精准重传 + 乱序缓存) |
典型场景 | 极弱网络(如卫星链路) | 早期 TCP(如 Tahoe) | 现代 TCP(SACK)/ KCP |
停等式 (Stop-and-Wait) 有点小捞,它太笨了。❗ 问题:每个包必须等 ACK 到达才能发下一个,效率极低。
回退 N 帧 (Go-Back-N, GBN) 处理的批量很大。❗ 问题:单包丢失导致大量冗余重传(尤其高延迟网络)。
选择性重传 (Selective Repeat, SR) 就具有相当的现代性了。✅ 优势:仅重传丢失包(SN=2),接收方缓存乱序包(3,4,5)。
KCP 的选择性重传
一、基础机制:选择性重传(SR)
特性 | KCP 的实现 |
---|---|
重传对象 | 仅重传真正丢失的包(非整个窗口) |
乱序处理 | 接收方缓存乱序到达的包,待缺失包到达后按序提交 |
确认机制 | ACK 包携带精确的丢包信息(类似 TCP SACK,但为原生支持) |
效率优势 | 单包丢失不影响后续包传输,避免回退 N 帧(GBN)的冗余重传 |
✅ 核心逻辑:"谁丢传谁" ------ 通过 ACK 包中的序列号信息精准定位丢失包。
二、KCP 对 SR 的增强策略
1.、激进快速重传
- 触发条件:收到 2 个重复 ACK(默认值,可配置)即重传(TCP 需 3 个)。
- 优势:将丢包检测时间从 RTT + 超时等待 压缩至 ≈1 RTT。
2、超时重传优化
- RTO 最小值:可设为 5-10ms(TCP 通常 ≥200ms),加速兜底重传。
- RTO 计算:启用 nodelay 模式时,RTO = RTT + 1×RTTVAR(更贴近当前延迟)。
3、ACK 无延迟反馈
- 机制:启用 ack_nodelay 时,收到数据包立即回复 ACK(TCP 常延迟 40ms 等待捎带)。
- 效果:进一步减少丢包判定时间(尤其对单向数据流)。
4、 窗口淘汰机制
- 问题场景:最早发送的包(如 SN=10)多次重传失败,阻塞窗口滑动。
- KCP 方案:
- 若后续包(如 SN=15)被确认 → 强制将窗口左边界推进到 SN=16。
- 放弃旧包的重传,避免传输停滞(牺牲少数包可靠性换流动性)。
KCP 的激进重传策略------低延迟设计!
KCP 的低延迟主要来源于它对 丢包 这一网络传输最大敌人的处理方式。TCP 为了保证网络的全局公平性和稳定性,对丢包的反应是相对"保守"和"缓慢"的。而 KCP 的设计哲学是:为了应用的实时性,可以更早、更主动、更精准地重传丢失的数据包,即使这意味着可能消耗更多的带宽。
1、 快速重传 (Fast Retransmit):跳过漫长的等待
- 原理: 当接收方发现接收到的数据包序列号不是连续的(即出现"空洞"),它会立即向发送方发送一个 ACK 包,指出它期望接收的下一个序列号(即空洞的开始位置)。如果发送方连续收到 3个(默认值,可配置) 或更多针对同一个数据包的重复 ACK (DupACK),它就立即推断该数据包很可能丢失了。
- 与 TCP 的关键区别:
- 触发阈值更低: TCP 通常也需要 3 个 DupACK 触发快速重传(NewReno 及以后算法)。看起来一样?不!关键在于时间窗口。
- RTO 最小值极低: TCP 的 RTO (Retransmission Timeout) 计算复杂,其最小值通常被限制在 200ms 左右(Linux 默认)。这意味着,即使触发了快速重传,TCP 也可能在等待 RTO 超时后才行动,或者其 RTO 本身就很大。KCP 的 RTO 最小值 (minrto) 可以由用户直接设置,典型值低至 10ms 甚至 5ms。即使没有 DupACK,KCP 的常规超时重传等待时间也远短于 TCP。
- 反应速度: 当收到足够的 DupACK 时,KCP 不等待任何定时器超时,立即重传怀疑丢失的数据包。而 TCP 在收到 DupACK 后,虽然会启动快速重传,但可能受到拥塞窗口调整、RTO 计算等因素影响,响应速度不如 KCP 直接和激进。
- 延迟收益: 在发生单包或少量丢包时,KCP 能在 几十毫秒内 完成重传(收到 DupACK -> 立即重发),而 TCP 可能因为 RTO 较大或算法保守需要等待 几百毫秒。这对于实时应用(如游戏中一个关键操作指令丢失)是质的飞跃。
2、选择性重传 (Selective Repeat / SACK-like Behavior):精准打击,避免浪费
- 原理: 当接收方检测到多个不连续的数据包丢失时,它需要告知发送方具体是哪些包丢了。KCP 在其 ACK 包中携带了接收窗口内所有缺失数据段的起始和结束序列号信息(类似于 TCP 的 SACK 选项,但实现方式不同)。
- 与 TCP 的关键区别:
- TCP 早期版本 (Reno): 在收到 DupACK 进入快速恢复阶段时,如果只丢失一个包,效果尚可。但如果一个窗口内丢失多个包,Reno 在收到部分新数据的 ACK 后就会错误地认为所有丢失包都已重传成功,退出快速恢复,导致剩余丢失包必须等待 RTO 超时(即所谓的"重传超时"问题),造成巨大的延迟(几百毫秒到几秒)。
- TCP 现代版本 (SACK): 通过 SACK 选项也能实现选择性确认,解决了 Reno 的多个丢包问题。KCP 与 TCP SACK 在"选择性"理念上相似,但 KCP 的实现通常更轻量和直接,并且其整体重传策略(结合极低的 RTO 和快速响应)更为激进。
- 延迟收益: 在发生多个数据包连续丢失的情况下,KCP 能通过接收方精确的反馈,一次性重传所有已知丢失的包,避免了像 TCP Reno 那样因多次 RTO 超时或等待导致的累积延迟。即使对比 TCP SACK,KCP 更快的触发和重传动作也能进一步压缩恢复时间。
低延迟 ACK 机制能综合实现 "选择性重传" + "快速重传"
想象一个场景:在我们中学时代,监考老师给我们点名,他们会一个一个地点,点一个学生就喊一句 "到",当点到下一位,无任何人应答的时候,老师立马就把这个学生记到迟到缺席的 "黑名单" 里,并通知班主任叫他赶快来,在此期间一直等他,直到老师不耐烦了,把它记作缺考(超时淘汰)。这其实就是一个低延迟 ACK 。
以下这个图示正是一个低延迟 ACK 的工作原理展示。
c
时间线 (向下流动) 发送方 (Sender) 接收方 (Receiver) 事件说明
------------------- --------------------------- --------------------------- --------------------------------
| 发送 Seq1 -> |
| 发送 Seq2 -> |
| 发送 Seq3 (丢失) -> (丢失) **Seq3 在网络中丢失**
| 发送 Seq4 -> |
| 发送 Seq5 -> |
| <- ACK1 (确认Seq1) **低延迟 ACK!**
| <- ACK2 (确认Seq2) **低延迟 ACK!**
| <- ACK2 (重复ACK) **收到 Seq4 (乱序),立即重复 ACK2 索要 Seq3**
| <- ACK2 (重复ACK) **收到 Seq5 (乱序),立即重复 ACK2 索要 Seq3**
| **发送方收到第2个重复ACK(2) (假设fastresend=2)**
| 重传 Seq3 -> **快速重传触发! (只重传丢失的Seq3)**
| <- ACK3 (确认Seq3) **低延迟 ACK! Seq3到达**
| **接收方已缓存 Seq4, Seq5,现在可交付 Seq3, Seq4, Seq5**
| <- ACK5 (确认Seq5) **累积确认/选择性ACK (确认Seq3,4,5)**
| 发送 Seq6 -> | 继续发送新数据
| <- ACK6 (确认Seq6) |
| ... ... ...
当 KCP 收到一个数据包时,它不会等待interval定时器到期或等待有数据要回传时捎带 ACK,而是立即生成一个 ACK 包发送回去。
更及时的 ACK 反馈:减少发送端的等待
- 原理: KCP 提供了灵活的 ACK 发送策略:
- ack_nodelay: 启用后(推荐),当 KCP 收到一个数据包时,它不会等待interval定时器到期或等待有数据要回传时捎带 ACK,而是立即生成一个 ACK 包发送回去。
- 禁用时,会尝试稍作等待(interval内)以便捎带数据或合并 ACK。
- 与 TCP 的关键区别: TCP 通常采用捎带确认策略,即接收方在发送数据时,才把 ACK 信息放在回传数据包的首部中一起发回。如果没有数据要回传,TCP 会设置一个小的延迟定时器(通常几十毫秒)等待,希望有数据可以捎带。如果没有,才单独发送一个纯 ACK 包。这个延迟是为了减少纯 ACK 包的数量。
- 延迟收益: 启用 ack_nodelay 后,发送方能更快地知道数据包是否成功到达。这带来两个好处:
- 加速快速重传触发: 丢失包的 DupACK 能更快地返回到发送方。
- 推进发送窗口: 确认包能更快地释放发送窗口的空间,允许发送方发送后续的新数据,提高吞吐量和降低整体传输完成时间。
- 代价:可能增加少量纯 ACK 包的带宽消耗。
3、激进的超时重传 (Aggressive RTO)------解决尾包延迟问题
- 原理: KCP 使用自己独立的 RTO 计算算法(基于 RTT 测量和变化)。虽然算法本身与 TCP 的 Karn/Partridge 算法类似,但其核心参数可由用户精细控制:
- minrto: 允许设置的最小 RTO 值(如 10ms)。TCP 通常有系统级下限(200ms)。
- nodelay: 启用后(通常推荐启用),会显著改变 RTO 计算方式:
- 第一次 RTO 计算:RTO = RTT + max(clock_drift, 4 * RTTVAR)
- 后续 RTO 计算:RTO = RTT + 1 * RTTVAR (非常接近最新的 RTT 测量值)
- interval: KCP 内部轮询间隔(如 10ms)。这决定了它检查超时的频率。
- 与 TCP 的关键区别:
- 用户可配置性: 开发者可以根据网络环境和应用需求,将 minrto 设置得非常低(如 10ms),而 TCP 的内核参数调整通常受限且影响全局。
- 更快的检测: 更小的 interval 和 minrto 意味着 KCP 能更快地检测到超时(可能没有触发快速重传的丢包,如尾包丢失)。
- 更激进的增长: nodelay 模式下的 RTO 计算比标准 TCP 更激进,增长幅度小,使得超时重传的等待时间更接近最新的 RTT 测量值,而不是像 TCP 那样指数级退避导致 RTO 迅速膨胀到秒级。
- 延迟收益: 对于那些无法触发快速重传(如只丢失一个包且后面没有新数据,无法产生 DupACK)的丢包场景,KCP 能在 10ms - 几十ms 级别 的超时后立即重传,而 TCP 至少需要等待 200ms 以上。这解决了"尾包延迟"问题。
协同效应:如何合力实现低延迟
- 丢包检测快: 通过 ack_nodelay 和精确的序列号/ACK机制,接收方能快速发现丢包并通知发送方 (DupACK)。
- 决策快: 收到足够 DupACK (阈值可配,默认3) 后,KCP 立即决策重传,不等待定时器。
- 重传动作快: 利用选择性重传信息,只重传丢失的包。
- 兜底快: 即使没有触发快速重传(如尾包丢失),极低的 minrto 和快速的内部轮询 (interval) 也能保证在极短时间(如 10-30ms)后通过超时重传补发。
- 恢复快: 一旦丢失的包被重传并确认,发送窗口能迅速前进(因为 ACK 回来得也快 - ack_nodelay),后续数据流能很快跟上,避免了 TCP 因 RTO 膨胀和窗口骤减导致的传输速率"雪崩"和缓慢恢复。
低延迟的本质就是用带宽换时间
KCP 激进重传策略的核心思想是:不惜以可能增加带宽消耗(发送更多重传包和更及时的纯 ACK 包)和潜在加剧网络拥塞为代价,换取传输延迟的极大降低 。 它通过一系列精心设计的、高度可配置的机制,将网络传输中最耗时的环节------丢包检测、决策、重传和恢复------的时间压缩到了极致(毫秒级),从而满足了实时应用对低延迟的苛刻要求。这种"带宽换时间"的权衡在实时游戏、音视频通信等场景下通常是值得的,因为用户体验对延迟的敏感度远高于对少量额外带宽消耗的敏感度。
再低延迟也总有意外------KCP 超时淘汰机制
我们先设想一个情景:当我们在玩英雄联盟的时候,如果我们的电脑卡顿(ping 值很高),英雄在漂移,情况持续了 3 min,网络恢复正常之后,系统并没有把我们电脑卡顿过程时候,别的英雄击杀我们的片段给出,而是快速跳过,最后只显示我们在泉水之中。这,就是 KCP 超时淘汰机制,极其具有实用性和工程性。
KCP 滑动窗口
KCP 超时淘汰机制是从属于 KCP 滑动窗口的管理机制的,其结构图示如下
c
发送方 (Sender) 接收方 (Receiver)
+---------------------------------------------------+ +---------------------------------------------------+
| **发送缓冲区 (Send Buffer)** | | **接收缓冲区 (Recv Buffer)** |
| +-----------------------------------------------+ | | +-----------------------------------------------+ |
| | 已发送 **已确认** (Delivered & Acked) | | | | 已接收 **已交付应用** (Delivered to App) | |
| | [SND.UNA 之前的数据] | | | | [RCV.NXT 之前的数据] | |
| | | | | | | |
| +-----------------------------------------------+ | | +-----------------------------------------------+ |
| | 已发送 **未确认** (In Flight / Outstanding) | | ----> | | 已接收 **待交付** (Received, Not Delivered) | |
| | [**发送窗口 (Sending Window)** 的核心区域] | | 数据包 | | [**接收窗口 (Receiving Window)** 可用空间前部]| |
| | * SND.UNA (发送未确认起点) | | ----> | | * RCV.NXT (期望接收起点) | |
| | * SND.NXT (下一个发送位置) --> | | | | | |
| | | | | | | |
| +-----------------------------------------------+ | | +-----------------------------------------------+ |
| | **允许发送但尚未发送** (Sendable) | | | | **可用接收空间** (Available Space) | |
| | [发送窗口内,SND.NXT 之后的部分] | | | | [**接收窗口 (Receiving Window)** 核心] | |
| | * 发送窗口右边界 = SND.UNA + Win | | | | * 窗口大小 = RCV.WND | |
| | Win = min(拥塞窗口 cwnd, 接收方通告窗口 rwnd) | | | | * 右边界 = RCV.NXT + RCV.WND | |
| +-----------------------------------------------+ | | +-----------------------------------------------+ |
| | **不允许发送** (Not Sendable) | | | | **不可用空间** (Not Available) | |
| | [超出当前发送窗口] | | | | [超出当前接收窗口] | |
| +-----------------------------------------------+ | | +-----------------------------------------------+ |
+---------------------------------------------------+ +---------------------------------------------------+
1、发送窗口 (SND_WND) 的约束:
- KCP 发送端维护一个发送窗口,它定义了当前允许发送的最大数据范围(从 snd_una 到 snd_una + snd_wnd)。
- snd_una 指向最早一个尚未被确认(Acknowledged)的数据包的序列号。它是窗口的左边界。
- 只有序列号落在 [snd_una, snd_una + snd_wnd) 范围内的数据包才允许被发送(包括首次发送和重传)。
2、窗口的推进 (Advancing):
- 当接收方 ACK 了 snd_una 指向的包(以及按序到达的后续包)时,snd_una 就会向右移动(增大),发送窗口也随之整体向右滑动。
- 新"暴露"出来的窗口空间允许发送新的数据包。
3、"丢包太久不重发"的本质:窗口淘汰
- 关键决策点: 为了不让整个传输流程被这些顽固的旧丢包卡死(阻塞新数据的发送),KCP 会执行一个策略:强制将 snd_una 推进到接收方已 ACK 的最小连续序列号之后的位置。
KCP 的超时淘汰机制
我接下来通过图示简单介绍一下超时淘汰机制。
图 1:初始状态(正常传输)
c
发送窗口范围: [10, 11, 12, 13, 14, 15] (snd_una=10, snd_wnd=6)
已发送未确认: █ █ █ █ █ █
10 11 12 13 14 15 <-- 序列号
状态:所有包已发送,等待 ACK。
图 2:发生丢包(包 10,11,12 丢失)------选择性重传
c
发送窗口范围: [10, 11, 12, 13, 14, 15]
已发送未确认: ✖ ✖ ✖ █ █ █
10 11 12 13 14 15
状态:
- 包 13,14,15 被接收方收到,但无法 ACK(因包 10 未到,不连续)
- 包 10,11,12 超时重传多次(🔥)仍失败。
图 3:触发淘汰机制(强制推进窗口)
c
接收方反馈:ACK 13,14,15(非连续确认)
KCP 决策:放弃包 10,11,12(重传失败)!
新窗口范围: [16, 17, 18, 19, 20, 21] (snd_una=16, snd_wnd=6)
已发送未确认: █ █ █ █ █ █
16 17 18 19 20 21
淘汰影响:
- 包 10,11,12 被移出窗口 ❌(不再重传)
- 新数据包 16-21 立即发送 ✅
图 4:新丢包处理(包 17 丢失)
c
窗口范围: [16, 17, 18, 19, 20, 21]
已发送未确认: █ ✖ █ █ █ █
16 17 18 19 20 21
状态:
- 包 17 丢失 → 触发 **快速重传**(仍在窗口内)
- 包 18,19,20,21 正常传输。
关键决策点: 为了不让整个传输流程被这些顽固的旧丢包卡死(阻塞新数据的发送),KCP 会执行一个策略:强制将 snd_una 推进到接收方已 ACK 的最小连续序列号之后的位置。在本案例中:
1、当前 snd_una = 10。
2、包 10, 11, 12 丢失且重传多次失败。
3、接收方收到了包 13, 14, 15 并 ACK 了它们(但无法 ACK 10, 11, 12)。
3、KCP 发现 13, 14, 15 被 ACK 了,但 10 这个关键点一直没 ACK。
4、KCP 判定 10, 11, 12 重传无望,决定将 snd_una 直接推进到 16(假设 15 是当前收到的最大连续包)。
后果:
5、序列号 10, 11, 12 的包被移出发送窗口 ([snd_una=16, snd_una+snd_wnd)) 的范围。
6、这些包不再有资格被重传,因为 KCP 的重传队列只维护窗口内的包。
7、发送窗口腾出了大量空间,新的数据包(序列号 >=16)可以立即被发送,传输得以继续。
与 TCP 的核心差异
机制 | KCP | TCP |
---|---|---|
窗口淘汰 | ✅ 主动放弃超时旧包,推进窗口 | ❌ 坚持重传直到成功或断开 |
阻塞避免 | ⚡ 新数据不受旧丢包影响 | 🐢 旧丢包阻塞整个窗口 |
适用场景 | 实时应用(容忍部分丢包 | ) 高可靠性传输(如文件下载) |
KCP 应对网络拥塞的方案------非退让流量控制
让我们用一个"高速公路应急车道管理"的比喻,来形象地理解 KCP 的非退让流量控制的必要性,并将其与传统 TCP 的退让式(AIMD) 控制进行对比。
场景设定:
- 道路: 一条繁忙的高速公路,有 常规车道 和 应急车道。
- 车辆: 代表需要传输的数据包。
- 交通状况: 代表网络状况。偶尔有小事故(轻微丢包),也可能有大拥堵(严重拥塞)。
- 交通管理中心 (TCP): 遵循严格的安全规则。一旦监测到任何事故(丢包),就认为可能发生大拥堵(严重拥塞),立即采取激进措施。
- 特种物流车队 (KCP): 运输的是高时效性、高优先级的物资(如救援物资、紧急医疗用品)。它们的核心任务是尽最大可能、以最快速度、稳定地将物资送达,即使路况不完美。它们拥有特殊的通行策略。
传统 TCP (退让式/AIMD,拥塞控制配合慢启动) 的比喻:
- 策略 ("宁可错杀,不可放过"):
- 只要高速摄像头(丢包检测)拍到任何一起小剐蹭事故(一个丢包),交通管理中心 (TCP) 就立刻、武断地认定整条高速即将瘫痪(严重拥塞)。
- 行动:
- 立即关闭一半常规车道! (乘性减 - 拥塞窗口减半)。所有车辆(数据包)瞬间只能使用一半的道路资源。
- 然后极其缓慢地、一条一条地重新开放车道。 (加性增 - 拥塞窗口缓慢线性增长)。需要很长时间才能恢复到原来的通行能力。
- 后果:
- 交通能力瞬间腰斩: 即使事故很小且很快处理完,车流(吞吐量)也暴跌。大量无辜车辆被堵在路上。
- 恢复极其缓慢: 重新开放车道的过程非常保守和缓慢,整体运输效率低下。
- 对高时效物资是灾难: 特种物流车队 (重要数据) 即使没有卷入事故,也被迫降速、延误,无法完成紧急任务。
- 必要性 (在公共道路上): 对于普通交通,这种策略是必要的,以防止自私的司机(应用)过度占用道路导致全局崩溃(拥塞崩溃)。安全第一,公平性优先。
KCP (非退让流控) 的比喻:
-
策略 ("精准处置,保持运力"):
- 特种车队有自己的指挥中心 (KCP协议栈)。
- 当车队收到报告,某辆特定运输车(一个数据包,如 Seq102)在某个路段发生了小事故(丢失)。
- 行动:
- 立即派遣一辆备用车 (快速重传) 走应急车道 (选择性重传) 去替换那辆故障车。目标:最快速度补上缺失的物资。
- 绝不关闭主车道! (非退让 - 发送窗口 snd_wnd 不缩减)。其他所有运输车(后续数据包)继续在主车道 (常规车道) 上按计划行驶,只要目的地仓库 (接收方) 还有空间 (rcv_wnd)。
- 可能微调车速 (调整 interval/RTO): 指挥中心可能会命令整个车队稍微降低一点车速(比如从 120km/h 降到 100km/h),给处理事故留出更多余量,避免连续出事。但这只是调速,不是封路!
- 利用一切可用空间: 一旦目的地仓库报告有更多空位(通告更大的 rcv_wnd),指挥中心立刻允许派出更多运输车(增大 snd_wnd),填满所有可用车道。
-
后果:
- 运力影响最小化: 只有出事的那一辆车需要替换,其他车辆几乎不受影响。整体运输量(吞吐量)保持高位且稳定。
- 时效性最大化: 高优先级物资总能尽快利用可用道路资源送达。平均延迟低,延迟波动小。
- 快速恢复潜力: 一旦备用车抵达(重传成功)或路况变好,车队可以瞬间提速或加车(利用更大的 snd_wnd),恢复到甚至超过之前的运力。
必要性 (对特种车队):
- 任务优先: 对于救援物资、紧急医疗用品(实时音视频、游戏指令、金融交易),送达的时效性和稳定性是最高优先级。一次大的延误或吞吐量暴跌可能导致任务失败(通话卡顿、游戏卡死、交易超时)。
- 容忍小事故: 这些小事故(少量丢包)在任务背景下是可以接受的代价(音频偶尔沙沙声、画面小马赛克),只要主体物资(大部分数据包)能持续、快速、稳定地送达。
- 专用/可控道路: 特种车队通常在有优先权、路况相对可控的路线上行驶(如专线、VPN、P2P内网),或者其流量占比不高。它们不会(或不应该)主动去挤垮公共道路(公网核心)。在这种场景下,"自私"一点,优先保障自己的关键任务,是必要且合理的。
- 避免过度反应: 关闭一半车道(窗口减半)对于处理一个小事故是灾难性的过度反应,完全违背了特种车队的核心使命。
以下是非退让流量控制的程序执行图例(大家要结合低延迟 ACK 去理解),当出现丢包的时候,稍微降低传输速度(RTO 稍微增大,但窗口没有像 TCP 那样大幅缩减),当接收方告知缓冲区充足的时候,发送方增大发送窗口的大小。
c
时间线 (向下流动) 发送方 (Sender) 接收方 (Receiver) / 网络事件
------------------ ---------------------------------------------- ------------------------------------------------
| **状态:**
| `snd_una = 100` (下一个期望ACK是100)
| `rcv_wnd (来自接收方) = 10` (最新通告)
| `snd_wnd = min(rcv_wnd, max) = 10`
| **发送窗口: [100, 110)**
| **发送: Seq100, Seq101, Seq102, Seq103, Seq104** -> **网络正常**
| **发送窗口内剩余: 5 slots (105-109)**
| **发送: Seq105, Seq106, Seq107** -> **网络正常**
| **发送窗口内剩余: 2 slots (108-109)**
| **Seq102 丢失! (网络拥塞开始)**
| **接收方收到:**
| Seq100 -> **立即发送 ACK(101)** (确认100)
| Seq101 -> **立即发送 ACK(102)** (确认101)
| Seq103 (乱序!) -> **立即发送 ACK(102)** (DUP-ACK, 索要102)
| Seq104 (乱序!) -> **立即发送 ACK(102)** (DUP-ACK)
| <- **收到 ACK(101)** (`snd_una` 推进到101)
| **发送窗口变为: [101, 111)**
| **发送: Seq108, Seq109** -> (用完当前窗口) **Seq108, Seq109 可能到达或丢失**
| <- **收到 ACK(102)** (`snd_una` 推进到102)
| **发送窗口变为: [102, 112)**
| **无新数据,或应用层暂无数据**
| <- **收到 ACK(102) (DUP-ACK 1)** **接收方处理Seq105, Seq106...**
| <- **收到 ACK(102) (DUP-ACK 2) [假设fastresend=2]** **触发快速重传条件!**
| **!! 关键点 !!**
| **检测到 Seq102 丢失 (通过 DUP-ACKs)**
| **但 `snd_wnd` 保持不变 = 10 (非退让!)**
| **动作: 立即重传 Seq102** -> **重传Seq102到达**
| **可能动作: 轻微增加 RTO 或 减小 `interval` (稍慢发送新数据)**
| **接收方收到 Seq102:**
| **立即发送 ACK(108)** (确认102-107? 或累积ACK)
| **(同时,应用层消费数据,假设释放了空间)**
| **接收方 `rcv_wnd` 增大 (e.g., =15)**
| <- **收到 ACK(108)** (`snd_una` 推进到108)
| **发送窗口变为: [108, 118)** (因为 `snd_wnd` 仍为10)
| <- **收到新通告 `rcv_wnd = 15`**
| **!! 关键点 !!**
| **更新 `snd_wnd = min(15, max) = 15`** (窗口**扩大**了!)
| **发送窗口变为: [108, 123)** (立刻获得更大发送潜力)
| **发送: Seq110, Seq111, ... (直到填满新窗口)** -> **网络拥塞可能缓解或持续...**
| **... 继续 ...**
总结
KCP 的各项机制之间是有相当大的重合,但归根结底,其底层运作机理是
- 低延迟 ACK :有快速重传、选择性重传的效果,即可靠的 UDP 传输。
- 超时重传机制:第一个计时器(在滑动窗口上的设置)是对低延迟 ACK 的补充,解决尾包延迟问题。
- 滑动窗口:有选择性重传的功效,有序的流量控制。
- 非退让流量控制:这是滑动窗口上的设置。容忍小错误、瑕疵,以换取传输流畅与稳定,用户体验更好
- 超时淘汰机制:第二个计时器(在滑动窗口上的设置),是对严重超时的包的断舍离处理。
我还会写几篇文章去介绍 KCP 协议的 C/C++ 代码实现。毕竟如果只有课本理论,那么会很单调的。话又说回来,源代码是有 1000 多行的,加上杂七杂八的应用,代码可能直逼 2000 行。故而深入解析 KCP 协议代码是一个长期的工程,我会继续跟进,但是近期不会写,我要花更多的事件去理解这些代码,而后给大家去讲述。