注:该文用于个人学习记录和知识交流,如有不足,欢迎指点。
这篇文章分享kcp的源码,大家可以先看一下,下一篇博文我将带大家分析
1. ikcp.h
cpp
//=====================================================================
//
// KCP - 一种更优的 ARQ 协议实现
// 作者: skywind3000 (at) gmail.com, 2010-2011
//
// 核心特性:
// + 平均往返时间(RTT)比 TCP 等传统 ARQ 协议减少 30% - 40%
// + 最大往返时间比 TCP 减少三倍
// + 轻量级设计,仅单个源文件即可集成
//
// 说明:KCP 是基于 UDP 的可靠传输协议,通过重传、滑动窗口、拥塞控制等机制
// 解决 UDP 不可靠、无序的问题,同时避免 TCP 复杂的拥塞控制带来的延迟开销
//=====================================================================
#ifndef __IKCP_H__
#define __IKCP_H__
#include <stddef.h>
#include <stdlib.h>
#include <assert.h>
//=====================================================================
// 32位整数类型定义(跨平台兼容)
// 目的:统一不同编译器、不同操作系统下的 32 位整数类型标识
//=====================================================================
#ifndef __INTEGER_32_BITS__
#define __INTEGER_32_BITS__
// 根据不同平台架构定义 32 位有符号/无符号整数类型
#if defined(_WIN64) || defined(WIN64) || defined(__amd64__) || \
defined(__x86_64) || defined(__x86_64__) || defined(_M_IA64) || \
defined(_M_AMD64)
typedef unsigned int ISTDUINT32; // 64位Windows/Linux下的32位无符号整数
typedef int ISTDINT32; // 64位Windows/Linux下的32位有符号整数
#elif defined(_WIN32) || defined(WIN32) || defined(__i386__) || \
defined(__i386) || defined(_M_X86)
typedef unsigned long ISTDUINT32; // 32位Windows/Linux下的32位无符号整数
typedef long ISTDINT32; // 32位Windows/Linux下的32位有符号整数
#elif defined(__MACOS__)
typedef UInt32 ISTDUINT32; // 早期MacOS下的32位无符号整数
typedef SInt32 ISTDINT32; // 早期MacOS下的32位有符号整数
#elif defined(__APPLE__) && defined(__MACH__)
#include <sys/types.h>
typedef u_int32_t ISTDUINT32; // MacOS X下的32位无符号整数
typedef int32_t ISTDINT32; // MacOS X下的32位有符号整数
#elif defined(__BEOS__)
#include <sys/inttypes.h>
typedef u_int32_t ISTDUINT32; // BEOS系统下的32位无符号整数
typedef int32_t ISTDINT32; // BEOS系统下的32位有符号整数
#elif (defined(_MSC_VER) || defined(__BORLANDC__)) && (!defined(__MSDOS__))
typedef unsigned __int32 ISTDUINT32;// MSC/Borland编译器下的32位无符号整数
typedef __int32 ISTDINT32; // MSC/Borland编译器下的32位有符号整数
#elif defined(__GNUC__)
#include <stdint.h>
typedef uint32_t ISTDUINT32; // GCC编译器下的32位无符号整数
typedef int32_t ISTDINT32; // GCC编译器下的32位有符号整数
#else
typedef unsigned long ISTDUINT32; // 其他平台默认32位无符号整数
typedef long ISTDINT32; // 其他平台默认32位有符号整数
#endif
#endif
//=====================================================================
// 基础整数类型统一定义
// 目的:封装不同平台的整数类型差异,简化代码跨平台移植
//=====================================================================
#ifndef __IINT8_DEFINED
#define __IINT8_DEFINED
typedef char IINT8; // 8位有符号整数
#endif
#ifndef __IUINT8_DEFINED
#define __IUINT8_DEFINED
typedef unsigned char IUINT8; // 8位无符号整数
#endif
#ifndef __IUINT16_DEFINED
#define __IUINT16_DEFINED
typedef unsigned short IUINT16; // 16位无符号整数
#endif
#ifndef __IINT16_DEFINED
#define __IINT16_DEFINED
typedef short IINT16; // 16位有符号整数
#endif
#ifndef __IINT32_DEFINED
#define __IINT32_DEFINED
typedef ISTDINT32 IINT32; // 32位有符号整数(复用跨平台定义)
#endif
#ifndef __IUINT32_DEFINED
#define __IUINT32_DEFINED
typedef ISTDUINT32 IUINT32; // 32位无符号整数(复用跨平台定义)
#endif
#ifndef __IINT64_DEFINED
#define __IINT64_DEFINED
#if defined(_MSC_VER) || defined(__BORLANDC__)
typedef __int64 IINT64; // MSC/Borland编译器下的64位有符号整数
#else
typedef long long IINT64; // 其他编译器下的64位有符号整数
#endif
#endif
#ifndef __IUINT64_DEFINED
#define __IUINT64_DEFINED
#if defined(_MSC_VER) || defined(__BORLANDC__)
typedef unsigned __int64 IUINT64; // MSC/Borland编译器下的64位无符号整数
#else
typedef unsigned long long IUINT64; // 其他编译器下的64位无符号整数
#endif
#endif
//=====================================================================
// 内联函数宏定义(跨平台兼容)
// 目的:统一不同编译器的内联函数关键字,优化代码执行效率
//=====================================================================
#ifndef INLINE
#if defined(__GNUC__)
// GCC编译器内联优化:版本3.1以上支持always_inline属性强制内联
#if (__GNUC__ > 3) || ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 1))
#define INLINE __inline__ __attribute__((always_inline))
#else
#define INLINE __inline__
#endif
#elif (defined(_MSC_VER) || defined(__BORLANDC__) || defined(__WATCOMC__))
#define INLINE __inline // 微软/宝兰/Watcom编译器的内联关键字
#else
#define INLINE // 不支持内联的编译器空实现
#endif
#endif
// C语言兼容:如果未定义inline关键字,则映射为自定义的INLINE宏
#if (!defined(__cplusplus)) && (!defined(inline))
#define inline INLINE
#endif
//=====================================================================
// 双向链表队列定义(KCP核心数据结构容器)
// 用途:管理发送/接收分片队列、缓存队列等,提供高效的节点增删操作
//=====================================================================
#ifndef __IQUEUE_DEF__
#define __IQUEUE_DEF__
// 链表节点结构体:双向链表的基础节点,包含前后指针
struct IQUEUEHEAD {
struct IQUEUEHEAD *next; // 下一个节点指针
struct IQUEUEHEAD *prev; // 上一个节点指针
};
typedef struct IQUEUEHEAD iqueue_head; // 链表节点类型别名
//---------------------------------------------------------------------
// 队列初始化相关宏
//---------------------------------------------------------------------
// 初始化命名链表头:让链表头的next和prev指针指向自身(空队列状态)
#define IQUEUE_HEAD_INIT(name) { &(name), &(name) }
// 定义并初始化一个链表头
#define IQUEUE_HEAD(name) \
struct IQUEUEHEAD name = IQUEUE_HEAD_INIT(name)
// 初始化单个链表节点:让节点的next和prev指针指向自身
#define IQUEUE_INIT(ptr) ( \
(ptr)->next = (ptr), (ptr)->prev = (ptr))
// 计算结构体成员的偏移量:用于从成员指针反向获取结构体指针
#define IOFFSETOF(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
// 从成员指针获取结构体指针(容器_of实现):KCP核心技巧
// 原理:通过成员偏移量计算结构体起始地址,适用于链表节点嵌入其他结构体的场景
#define ICONTAINEROF(ptr, type, member) ( \
(type*)( ((char*)((type*)ptr)) - IOFFSETOF(type, member)) )
// 从链表节点指针获取包含该节点的结构体指针
#define IQUEUE_ENTRY(ptr, type, member) ICONTAINEROF(ptr, type, member)
//---------------------------------------------------------------------
// 队列操作相关宏(核心链表操作)
//---------------------------------------------------------------------
// 在链表头之后插入节点:头插法(用于栈式操作)
#define IQUEUE_ADD(node, head) ( \
(node)->prev = (head), (node)->next = (head)->next, \
(head)->next->prev = (node), (head)->next = (node))
// 在链表尾之前插入节点:尾插法(用于队列式操作)
#define IQUEUE_ADD_TAIL(node, head) ( \
(node)->prev = (head)->prev, (node)->next = (head), \
(head)->prev->next = (node), (head)->prev = (node))
// 删除两个节点之间的节点:内部辅助宏
#define IQUEUE_DEL_BETWEEN(p, n) ((n)->prev = (p), (p)->next = (n))
// 删除指定节点:将节点从链表中移除,重置节点的前后指针
#define IQUEUE_DEL(entry) (\
(entry)->next->prev = (entry)->prev, \
(entry)->prev->next = (entry)->next, \
(entry)->next = 0, (entry)->prev = 0)
// 删除节点并初始化:删除后让节点指向自身(可重新插入链表)
#define IQUEUE_DEL_INIT(entry) do { \
IQUEUE_DEL(entry); IQUEUE_INIT(entry); } while (0)
// 判断队列是否为空:链表头的next指针指向自身则为空
#define IQUEUE_IS_EMPTY(entry) ((entry) == (entry)->next)
// 队列操作函数别名:简化代码调用
#define iqueue_init IQUEUE_INIT
#define iqueue_entry IQUEUE_ENTRY
#define iqueue_add IQUEUE_ADD
#define iqueue_add_tail IQUEUE_ADD_TAIL
#define iqueue_del IQUEUE_DEL
#define iqueue_del_init IQUEUE_DEL_INIT
#define iqueue_is_empty IQUEUE_IS_EMPTY
// 遍历链表(通过结构体成员遍历):适用于链表节点嵌入其他结构体的场景
#define IQUEUE_FOREACH(iterator, head, TYPE, MEMBER) \
for ((iterator) = iqueue_entry((head)->next, TYPE, MEMBER); \
&((iterator)->MEMBER) != (head); \
(iterator) = iqueue_entry((iterator)->MEMBER.next, TYPE, MEMBER))
// 遍历链表别名
#define iqueue_foreach(iterator, head, TYPE, MEMBER) \
IQUEUE_FOREACH(iterator, head, TYPE, MEMBER)
// 直接遍历链表节点(不依赖外部结构体)
#define iqueue_foreach_entry(pos, head) \
for( (pos) = (head)->next; (pos) != (head) ; (pos) = (pos)->next )
// 将一个链表拼接到另一个链表的头部之后
#define __iqueue_splice(list, head) do { \
iqueue_head *first = (list)->next, *last = (list)->prev; \
iqueue_head *at = (head)->next; \
(first)->prev = (head), (head)->next = (first); \
(last)->next = (at), (at)->prev = (last); } while (0)
// 拼接非空链表
#define iqueue_splice(list, head) do { \
if (!iqueue_is_empty(list)) __iqueue_splice(list, head); } while (0)
// 拼接链表后初始化原链表
#define iqueue_splice_init(list, head) do { \
iqueue_splice(list, head); iqueue_init(list); } while (0)
// MSC编译器警告屏蔽:避免类型转换等警告干扰编译
#ifdef _MSC_VER
#pragma warning(disable:4311)
#pragma warning(disable:4312)
#pragma warning(disable:4996)
#endif
#endif
//---------------------------------------------------------------------
// 字节序与内存对齐定义(网络传输核心配置)
//---------------------------------------------------------------------
// 判断系统字节序:默认小端序,大端序平台需手动定义
#ifndef IWORDS_BIG_ENDIAN
#ifdef _BIG_ENDIAN_
#if _BIG_ENDIAN_
#define IWORDS_BIG_ENDIAN 1 // 大端序标识
#endif
#endif
#ifndef IWORDS_BIG_ENDIAN
// 以下平台默认为大端序
#if defined(__hppa__) || \
defined(__m68k__) || defined(mc68000) || defined(_M_M68K) || \
(defined(__MIPS__) && defined(__MIPSEB__)) || \
defined(__ppc__) || defined(__POWERPC__) || defined(_M_PPC) || \
defined(__sparc__) || defined(__powerpc__) || \
defined(__mc68000__) || defined(__s390x__) || defined(__s390__)
#define IWORDS_BIG_ENDIAN 1
#endif
#endif
#ifndef IWORDS_BIG_ENDIAN
#define IWORDS_BIG_ENDIAN 0 // 默认小端序
#endif
#endif
// 判断内存是否必须对齐:x86/x86_64平台支持非对齐访问,其他平台可能需要对齐
#ifndef IWORDS_MUST_ALIGN
#if defined(__i386__) || defined(__i386) || defined(_i386_)
#define IWORDS_MUST_ALIGN 0 // x86平台无需强制对齐
#elif defined(_M_IX86) || defined(_X86_) || defined(__x86_64__)
#define IWORDS_MUST_ALIGN 0 // x86_64平台无需强制对齐
#elif defined(__amd64) || defined(__amd64__)
#define IWORDS_MUST_ALIGN 0 // amd64平台无需强制对齐
#else
#define IWORDS_MUST_ALIGN 1 // 其他平台需强制内存对齐
#endif
#endif
//=====================================================================
// KCP 分片结构体(核心数据单元)
// 说明:每个KCP数据包对应一个分片,包含协议头信息和用户数据
//=====================================================================
struct IKCPSEG
{
struct IQUEUEHEAD node; // 链表节点:用于将分片加入发送/接收队列
IUINT32 conv; // 会话编号:唯一标识一个KCP会话,双方必须一致才能通信
IUINT32 cmd; // 分片类型命令:
// - IKCP_CMD_PUSH:数据分片(携带用户数据)
// - IKCP_CMD_ACK:确认分片(响应收到的数据分片)
// - IKCP_CMD_WASK:窗口查询分片(请求对方告知接收窗口大小)
// - IKCP_CMD_WINS:窗口通知分片(告知对方自己的接收窗口大小)
IUINT32 frg; // 分片序号:当用户数据超过MSS时,会被拆分为多个分片
// 取值范围为 0~n-1(n为总分片数),0 表示最后一个分片
IUINT32 wnd; // 接收窗口剩余大小:告知对方当前自己还能接收的分片数量
// 发送方的发送窗口不能超过此值,避免接收方缓存溢出
IUINT32 ts; // 发送时间戳:当前分片的发送时刻(毫秒级)
IUINT32 sn; // 分片序号:全局唯一的分片标识,按发送顺序递增
IUINT32 una; // 未确认起始序号:当前分片发送时,本地未被确认的最小序号
// 用于告知接收方发送方的接收状态
IUINT32 len; // 数据长度:当前分片携带的用户数据长度
IUINT32 resendts; // 下次重传时间戳:当超时未收到ACK时,在此时间点重传
IUINT32 rto; // 重传超时时间:当前分片的超时等待时间(动态计算)
IUINT32 fastack; // 快速重传计数器:记录该分片被跳过的ACK次数
// 达到阈值(如fastresend配置)时触发快速重传
IUINT32 xmit; // 发送次数:该分片已发送(含重传)的次数
// 用于动态调整RTO,次数越多RTO越大
char data[1]; // 数据缓冲区:柔性数组,存储用户数据(实际长度由len指定)
};
//=====================================================================
// KCP 控制块结构体(核心会话管理单元)
// 说明:每个KCP会话对应一个控制块,存储会话的所有状态信息
// 包含发送/接收窗口、拥塞控制、RTT计算、队列管理等核心状态
//=====================================================================
struct IKCPCB
{
IUINT32 conv; // 会话编号:与分片的conv对应,唯一标识会话
IUINT32 mtu; // 最大传输单元:底层UDP数据包的最大长度(默认1400字节,最小50字节)
// 决定了单个KCP分片的最大可能长度
IUINT32 mss; // 最大分片大小:单个KCP分片的最大数据长度(≤MTU - 协议头长度)
IUINT32 state; // 连接状态:0xffffffff表示断开连接,其他值为正常状态
// 发送窗口核心参数(滑动窗口机制)
IUINT32 snd_una; // 未确认起始序号:第一个未被对方确认的分片序号
IUINT32 snd_nxt; // 下一个发送序号:即将分配给新分片的序号(按顺序递增)
IUINT32 rcv_nxt; // 下一个接收序号:期望接收的下一个分片序号
// 用于判断分片是否有序,无序分片存入接收缓存
// RTT 计算相关参数
IUINT32 ts_recent; // 最近接收的分片时间戳:用于计算RTT
IUINT32 ts_lastack; // 最后一次发送ACK的时间戳:用于控制ACK发送频率
IUINT32 ssthresh; // 拥塞阈值:拥塞窗口(cwnd)增长到该值后,由指数增长转为线性增长
IINT32 rx_rttval; // RTT 偏差值:记录RTT的波动情况,用于平滑RTO计算
IINT32 rx_srtt; // 平滑RTT:加权平均后的RTT值(避免波动影响)
IINT32 rx_rto; // 重传超时时间:基于RTT计算的基础超时时间
IINT32 rx_minrto; // 最小重传超时时间:防止RTO过小导致频繁重传
// 窗口大小参数
IUINT32 snd_wnd; // 本地发送窗口大小:本地最多允许未确认的分片数量
IUINT32 rcv_wnd; // 本地接收窗口大小:本地最多能缓存的未处理分片数量
// 接收队列满(达到rcv_wnd)时会拒绝接收新分片
IUINT32 rmt_wnd; // 远端接收窗口大小:对方告知的接收窗口剩余大小
// 发送方的发送窗口不能超过此值
IUINT32 cwnd; // 拥塞窗口大小:动态调整的发送窗口限制(基于网络拥塞状态)
// 正常情况下不超过snd_wnd和rmt_wnd的最小值
IUINT32 probe; // 窗口探查标识:
// - IKCP_ASK_TELL:告知对方本地窗口大小
// - IKCP_ASK_SEND:请求对方告知其窗口大小
// 内部调度参数
IUINT32 current; // 当前时间戳:最近一次调用ikcp_update的时间
IUINT32 interval; // 刷新间隔:ikcp_update的建议调用间隔(默认100ms)
// 间隔过小会增加CPU占用,过大则会增加延迟
IUINT32 ts_flush; // 下次刷新时间戳:下次调用ikcp_update的时间点
IUINT32 xmit; // 总发送次数:所有分片的重传总次数
// 队列统计参数
IUINT32 nrcv_buf; // 接收缓存分片数:rcv_buf队列中的分片数量(未排序完成)
IUINT32 nsnd_buf; // 发送缓存分片数:snd_buf队列中的分片数量(已发送未确认)
IUINT32 nrcv_que; // 接收队列消息数:rcv_queue队列中的完整消息数量(已排序完成,可读取)
IUINT32 nsnd_que; // 发送队列消息数:snd_queue队列中的消息数量(待分片发送)
// 模式配置参数
IUINT32 nodelay; // 无延迟模式:1启用,0禁用
// 启用后rx_minrto设为0,关闭部分拥塞控制策略,降低延迟
IUINT32 updated; // 更新标识:标记是否调用过ikcp_update(避免重复初始化)
// 窗口探查参数
IUINT32 ts_probe; // 下次探查时间戳:下次发送窗口探查分片的时间点
IUINT32 probe_wait; // 探查等待时间:发送探查分片后,等待响应的时间
// 连接检测参数
IUINT32 dead_link; // 最大重传次数:单个分片重传超过此次数则判定连接中断
IUINT32 incr; // 可发送最大数据量:当前允许发送的最大数据长度(基于窗口和拥塞控制)
// 核心队列(双向链表实现)
struct IQUEUEHEAD snd_queue; // 发送消息队列:存储用户待发送的完整消息(未分片)
struct IQUEUEHEAD rcv_queue; // 接收消息队列:存储已排序完成的完整消息(可被用户读取)
struct IQUEUEHEAD snd_buf; // 发送缓存队列:存储已发送但未确认的分片
struct IQUEUEHEAD rcv_buf; // 接收缓存队列:存储已接收但未排序完成的分片
// ACK批量发送缓存
IUINT32 *acklist; // ACK列表:存储待批量发送的ACK序号和时间戳(格式:[sn1, ts1, sn2, ts2, ...])
IUINT32 ackcount; // ACK计数:当前acklist中存储的ACK数量
IUINT32 ackblock; // ACK列表容量:acklist数组的最大存储长度(不足时自动扩容)
// 用户自定义数据
void *user; // 用户数据指针:可存储用户自定义数据(如UDP连接信息)
char *buffer; // 临时缓冲区:用于分片组装、数据拷贝等临时操作
// 快速重传配置
int fastresend; // 快速重传阈值:触发快速重传所需的重复ACK次数(默认2)
int fastlimit; // 快速重传限制:每次刷新最多重传的分片数量
// 拥塞控制配置
int nocwnd; // 关闭拥塞控制:1关闭,0启用(关闭后cwnd= snd_wnd)
int stream; // 流传输模式:1启用,0禁用(启用后忽略分片的frg字段,按流处理)
// 日志配置
int logmask; // 日志掩码:控制日志输出类型(如输入数据、发送ACK等)
// 可通过IKCP_LOG_xxx宏组合配置
// 回调函数(用户实现的底层接口)
// 发送回调:KCP需要发送数据时调用,由用户实现UDP发送逻辑
int (*output)(const char *buf, int len, struct IKCPCB *kcp, void *user);
// 日志回调:KCP输出日志时调用,由用户实现日志存储/打印逻辑
void (*writelog)(const char *log, struct IKCPCB *kcp, void *user);
};
typedef struct IKCPCB ikcpcb; // KCP控制块类型别名
//=====================================================================
// 日志掩码定义(控制日志输出类型)
//=====================================================================
#define IKCP_LOG_OUTPUT 1 // 输出数据日志
#define IKCP_LOG_INPUT 2 // 输入数据日志
#define IKCP_LOG_SEND 4 // 发送数据日志
#define IKCP_LOG_RECV 8 // 接收数据日志
#define IKCP_LOG_IN_DATA 16 // 输入数据分片日志
#define IKCP_LOG_IN_ACK 32 // 输入ACK分片日志
#define IKCP_LOG_IN_PROBE 64 // 输入探查分片日志
#define IKCP_LOG_IN_WINS 128 // 输入窗口通知日志
#define IKCP_LOG_OUT_DATA 256 // 输出数据分片日志
#define IKCP_LOG_OUT_ACK 512 // 输出ACK分片日志
#define IKCP_LOG_OUT_PROBE 1024 // 输出探查分片日志
#define IKCP_LOG_OUT_WINS 2048 // 输出窗口通知日志
//=====================================================================
// 对外接口函数声明(KCP核心API)
//=====================================================================
#ifdef __cplusplus
extern "C" {
#endif
//---------------------------------------------------------------------
// 创建KCP控制块
// 参数:
// conv - 会话编号(双方必须一致)
// user - 用户数据指针(将传递给output回调)
// 返回值:创建成功返回KCP控制块指针,失败返回NULL
// 说明:创建后需通过ikcp_setoutput设置发送回调,否则无法发送数据
//---------------------------------------------------------------------
ikcpcb* ikcp_create(IUINT32 conv, void *user);
//---------------------------------------------------------------------
// 释放KCP控制块
// 参数:kcp - 待释放的KCP控制块指针
// 说明:释放所有资源(队列、缓存、ACK列表等),调用后kcp指针失效
//---------------------------------------------------------------------
void ikcp_release(ikcpcb *kcp);
//---------------------------------------------------------------------
// 设置发送回调函数
// 参数:
// kcp - KCP控制块指针
// output - 发送回调函数(用户实现UDP发送逻辑)
// 说明:回调函数的buf参数为待发送的UDP数据包,len为数据包长度
//---------------------------------------------------------------------
void ikcp_setoutput(ikcpcb *kcp, int (*output)(const char *buf, int len,
ikcpcb *kcp, void *user));
//---------------------------------------------------------------------
// 用户层接收数据
// 参数:
// kcp - KCP控制块指针
// buffer - 接收缓冲区(存储读取到的用户数据)
// len - 接收缓冲区的最大长度
// 返回值:成功返回读取到的数据长度,失败返回负数(如EAGAIN表示无数据)
// 说明:从rcv_queue队列中读取已排序完成的完整消息
//---------------------------------------------------------------------
int ikcp_recv(ikcpcb *kcp, char *buffer, int len);
//---------------------------------------------------------------------
// 用户层发送数据
// 参数:
// kcp - KCP控制块指针
// buffer - 待发送的用户数据缓冲区
// len - 待发送的数据长度
// 返回值:成功返回0,失败返回负数(如窗口满、参数错误等)
// 说明:将用户数据加入snd_queue队列,等待后续分片发送
//---------------------------------------------------------------------
int ikcp_send(ikcpcb *kcp, const char *buffer, int len);
//---------------------------------------------------------------------
// 更新KCP状态
// 参数:
// kcp - KCP控制块指针
// current - 当前时间戳(毫秒级)
// 说明:必须定期调用(建议间隔interval),处理超时重传、ACK批量发送、窗口探查等逻辑
//---------------------------------------------------------------------
void ikcp_update(ikcpcb *kcp, IUINT32 current);
//---------------------------------------------------------------------
// 获取下次更新时间
// 参数:
// kcp - KCP控制块指针
// current - 当前时间戳(毫秒级)
// 返回值:下次调用ikcp_update的时间戳(毫秒级)
// 说明:用于优化调度,避免不必要的频繁调用,提升性能(尤其适用于大量KCP会话)
//---------------------------------------------------------------------
IUINT32 ikcp_check(const ikcpcb *kcp, IUINT32 current);
//---------------------------------------------------------------------
// 处理底层接收数据
// 参数:
// kcp - KCP控制块指针
// data - 接收的UDP数据包缓冲区
// size - UDP数据包的长度
// 返回值:成功返回0,失败返回负数(如数据格式错误、会话不匹配等)
// 说明:将底层UDP数据解析为KCP分片,处理数据分片、ACK分片、窗口探查等逻辑
//---------------------------------------------------------------------
int ikcp_input(ikcpcb *kcp, const char *data, long size);
//---------------------------------------------------------------------
// 刷新待发送数据
// 参数:kcp - KCP控制块指针
// 说明:将snd_queue队列中的消息分片,加入snd_buf队列并调用output回调发送
// 可主动调用触发数据发送,也可由ikcp_update自动调用
//---------------------------------------------------------------------
void ikcp_flush(ikcpcb *kcp);
//---------------------------------------------------------------------
// 获取接收队列中下一条消息的长度
// 参数:kcp - KCP控制块指针
// 返回值:下一条消息的长度,无消息返回0
// 说明:用于用户层判断是否有足够的缓冲区接收数据
//---------------------------------------------------------------------
int ikcp_peeksize(const ikcpcb *kcp);
//---------------------------------------------------------------------
// 设置MTU大小
// 参数:
// kcp - KCP控制块指针
// mtu - 新的MTU大小(最小50字节)
// 返回值:成功返回0,失败返回负数(如MTU值非法)
// 说明:设置后会自动计算新的MSS(MSS = MTU - KCP协议头长度)
//---------------------------------------------------------------------
int ikcp_setmtu(ikcpcb *kcp, int mtu);
//---------------------------------------------------------------------
// 设置窗口大小
// 参数:
// kcp - KCP控制块指针
// sndwnd - 发送窗口大小
// rcvwnd - 接收窗口大小
// 返回值:成功返回0,失败返回负数(如窗口大小非法)
// 说明:控制发送方未确认分片的最大数量和接收方缓存的最大分片数量
//---------------------------------------------------------------------
int ikcp_wndsize(ikcpcb *kcp, int sndwnd, int rcvwnd);
//---------------------------------------------------------------------
// 获取待发送的分片数量
// 参数:kcp - KCP控制块指针
// 返回值:待发送(含未确认)的分片总数
// 说明:用于用户层监控发送队列状态
//---------------------------------------------------------------------
int ikcp_waitsnd(const ikcpcb *kcp);
//---------------------------------------------------------------------
// 配置无延迟模式
// 参数:
// kcp - KCP控制块指针
// nodelay - 无延迟模式(1启用,0禁用)
// interval - 刷新间隔(毫秒级,默认100ms)
// resend - 快速重传模式(0禁用,≥1启用,值为触发阈值)
// nc - 关闭拥塞控制(1关闭,0启用)
// 返回值:成功返回0,失败返回负数
// 示例:
// 普通模式:ikcp_nodelay(kcp, 0, 40, 0, 0);
// 极速模式:ikcp_nodelay(kcp, 1, 10, 2, 1);
//---------------------------------------------------------------------
int ikcp_nodelay(ikcpcb *kcp, int nodelay, int interval, int resend, int nc);
//---------------------------------------------------------------------
// 输出KCP日志
// 参数:
// kcp - KCP控制块指针
// mask - 日志掩码(IKCP_LOG_xxx组合)
// fmt - 日志格式化字符串
// ... - 可变参数(与fmt对应)
// 说明:通过writelog回调输出日志,仅输出mask匹配的日志类型
//---------------------------------------------------------------------
void ikcp_log(ikcpcb *kcp, int mask, const char *fmt, ...);
//---------------------------------------------------------------------
// 设置内存分配器
// 参数:
// new_malloc - 自定义malloc函数指针
// new_free - 自定义free函数指针
// 说明:用于用户层替换默认的内存分配函数(如使用内存池优化性能)
//---------------------------------------------------------------------
void ikcp_allocator(void* (*new_malloc)(size_t), void (*new_free)(void*));
//---------------------------------------------------------------------
// 从数据包中解析会话编号
// 参数:ptr - UDP数据包指针
// 返回值:解析出的conv(会话编号)
// 说明:用于用户层根据接收到的UDP数据,找到对应的KCP会话
//---------------------------------------------------------------------
IUINT32 ikcp_getconv(const void *ptr);
#ifdef __cplusplus
}
#endif
#endif
2. ikcp.c
cpp
//=====================================================================
//
// KCP - 一种高效的ARQ协议实现
// 作者:skywind3000 (at) gmail.com, 2010-2011
//
// 核心优势:
// + 平均RTT比TCP等传统ARQ协议降低30%-40%
// + 最大RTT比TCP降低约3倍
// + 轻量级实现,仅一个源文件
//
// 说明:KCP是运行在UDP之上的可靠传输协议,通过优化重传策略和窗口控制,
// 解决TCP在实时场景(如游戏、音视频)中延迟过高的问题。其核心是在可靠性
// 和低延迟之间做平衡,允许一定的包丢失以换取更快的响应速度。
//=====================================================================
#include "ikcp.h" // 包含KCP核心数据结构定义(如ikcpcb控制块、IKCPSEG分段)
#include <stddef.h> // 定义NULL、size_t等基础类型
#include <stdlib.h> // 内存分配函数(malloc/free)
#include <string.h> // 内存操作函数(memcpy等)
#include <stdarg.h> // 可变参数支持(用于日志输出)
#include <stdio.h> // 输入输出函数(vsprintf等)
//=====================================================================
// KCP基础常量(协议核心参数,直接影响传输性能和可靠性)
//=====================================================================
const IUINT32 IKCP_RTO_NDL = 30; // 无延迟模式下的最小重传超时时间(30ms)
// (无延迟模式下,为了快速响应,允许更小的RTO)
const IUINT32 IKCP_RTO_MIN = 100; // 正常模式最小重传超时时间(100ms)
// (防止过短的RTO导致不必要的重传)
const IUINT32 IKCP_RTO_DEF = 200; // 默认重传超时时间(200ms,未测量RTT时使用)
const IUINT32 IKCP_RTO_MAX = 60000; // 最大重传超时时间(60s,避免无限等待)
const IUINT32 IKCP_CMD_PUSH = 81; // 数据推送命令:携带应用层数据的数据包
const IUINT32 IKCP_CMD_ACK = 82; // 确认应答命令:告知发送方"某序号数据已收到"
const IUINT32 IKCP_CMD_WASK = 83; // 窗口探测请求:主动询问对方当前接收窗口大小
// (用于远端窗口为0时,主动探测是否可恢复传输)
const IUINT32 IKCP_CMD_WINS = 84; // 窗口大小告知:主动告知对方自己的接收窗口剩余空间
const IUINT32 IKCP_ASK_SEND = 1; // 标记位:需要发送窗口探测请求(IKCP_CMD_WASK)
const IUINT32 IKCP_ASK_TELL = 2; // 标记位:需要发送窗口大小告知(IKCP_CMD_WINS)
const IUINT32 IKCP_WND_SND = 32; // 默认发送窗口大小(最多允许32个未确认数据包)
// (窗口越大,吞吐量越高,但延迟和拥塞风险增加)
const IUINT32 IKCP_WND_RCV = 128; // 默认接收窗口大小(最多缓存128个乱序数据包)
// (需≥最大分片数,避免分片丢失后无法重组)
const IUINT32 IKCP_MTU_DEF = 1400; // 默认MTU(最大传输单元,1400字节)
// (适配互联网常见MTU=1500,预留IP/UDP头部空间)
const IUINT32 IKCP_ACK_FAST = 3; // 未使用(历史快速确认阈值,保留兼容性)
const IUINT32 IKCP_INTERVAL = 100; // 默认刷新间隔(100ms)
// (KCP核心定时器周期,决定协议处理频率)
const IUINT32 IKCP_OVERHEAD = 24; // KCP头部固定开销(24字节)
// (包含conv、cmd、sn等关键控制信息)
const IUINT32 IKCP_DEADLINK = 20; // 判定链路断开的重传次数阈值(20次)
const IUINT32 IKCP_THRESH_INIT = 2; // 慢启动初始阈值(拥塞控制参数)
const IUINT32 IKCP_THRESH_MIN = 2; // 慢启动最小阈值(拥塞控制参数)
const IUINT32 IKCP_PROBE_INIT = 7000; // 窗口探测初始间隔(7秒)
const IUINT32 IKCP_PROBE_LIMIT = 120000; // 窗口探测最大间隔(120秒)
const IUINT32 IKCP_FASTACK_LIMIT = 5; // 快速重传的最大次数限制(5次)
// (避免频繁快速重传导致网络拥塞)
//---------------------------------------------------------------------
// 编码/解码函数(小端序,保证跨平台兼容性)
//---------------------------------------------------------------------
/* 编码8位无符号整数 */
static inline char *ikcp_encode8u(char *p, unsigned char c)
{
*(unsigned char *)p++ = c; // 直接赋值(8位无需字节序转换)
return p;
}
/* 解码8位无符号整数 */
static inline const char *ikcp_decode8u(const char *p, unsigned char *c)
{
*c = *(unsigned char *)p++; // 直接读取
return p;
}
/* 编码16位无符号整数(小端序) */
static inline char *ikcp_encode16u(char *p, unsigned short w)
{
#if IWORDS_BIG_ENDIAN || IWORDS_MUST_ALIGN
// 大端系统或需要内存对齐的系统:手动拆分字节(低8位在前,高8位在后)
*(unsigned char *)(p + 0) = (w & 255); // 低8位
*(unsigned char *)(p + 1) = (w >> 8); // 高8位
#else
// 小端系统:直接内存拷贝(小端序原生存储)
memcpy(p, &w, 2);
#endif
p += 2; // 指针后移2字节
return p;
}
/* 解码16位无符号整数(小端序) */
static inline const char *ikcp_decode16u(const char *p, unsigned short *w)
{
#if IWORDS_BIG_ENDIAN || IWORDS_MUST_ALIGN
// 大端系统:手动拼接字节(先读低8位,再读高8位)
*w = *(const unsigned char *)(p + 1); // 高8位
*w = *(const unsigned char *)(p + 0) + (*w << 8); // 低8位 + 高8位左移8位
#else
// 小端系统:直接内存拷贝
memcpy(w, p, 2);
#endif
p += 2; // 指针后移2字节
return p;
}
/* 编码32位无符号整数(小端序) */
static inline char *ikcp_encode32u(char *p, IUINT32 l)
{
#if IWORDS_BIG_ENDIAN || IWORDS_MUST_ALIGN
// 大端系统:手动拆分字节(按字节0-3顺序存储,对应低到高8位)
*(unsigned char *)(p + 0) = (unsigned char)((l >> 0) & 0xff); // 低8位
*(unsigned char *)(p + 1) = (unsigned char)((l >> 8) & 0xff); // 次低8位
*(unsigned char *)(p + 2) = (unsigned char)((l >> 16) & 0xff); // 次高8位
*(unsigned char *)(p + 3) = (unsigned char)((l >> 24) & 0xff); // 高8位
#else
// 小端系统:直接内存拷贝
memcpy(p, &l, 4);
#endif
p += 4; // 指针后移4字节
return p;
}
/* 解码32位无符号整数(小端序) */
static inline const char *ikcp_decode32u(const char *p, IUINT32 *l)
{
#if IWORDS_BIG_ENDIAN || IWORDS_MUST_ALIGN
// 大端系统:手动拼接字节(按字节3-0顺序读取,对应高到低8位)
*l = *(const unsigned char *)(p + 3); // 高8位
*l = *(const unsigned char *)(p + 2) + (*l << 8); // 次高8位
*l = *(const unsigned char *)(p + 1) + (*l << 8); // 次低8位
*l = *(const unsigned char *)(p + 0) + (*l << 8); // 低8位
#else
// 小端系统:直接内存拷贝
memcpy(l, p, 4);
#endif
p += 4; // 指针后移4字节
return p;
}
// 取两个数的较小值(辅助宏)
static inline IUINT32 _imin_(IUINT32 a, IUINT32 b)
{
return a <= b ? a : b;
}
// 取两个数的较大值(辅助宏)
static inline IUINT32 _imax_(IUINT32 a, IUINT32 b)
{
return a >= b ? a : b;
}
// 将中间值限制在[lower, upper]范围内(辅助宏)
static inline IUINT32 _ibound_(IUINT32 lower, IUINT32 middle, IUINT32 upper)
{
return _imin_(_imax_(lower, middle), upper);
}
// 计算两个时间戳的差值(later - earlier)
// 说明:由于时间戳可能溢出(32位无符号),此处用IINT32强制转换实现安全减法
static inline long _itimediff(IUINT32 later, IUINT32 earlier)
{
return ((IINT32)(later - earlier));
}
//---------------------------------------------------------------------
// 分段(segment)管理(KCP数据传输的基本单位)
//---------------------------------------------------------------------
typedef struct IKCPSEG IKCPSEG; // 前向声明:KCP数据分段结构(定义在ikcp.h中)
// 内存分配/释放钩子(允许用户自定义内存管理,如使用内存池)
static void *(*ikcp_malloc_hook)(size_t) = NULL;
static void (*ikcp_free_hook)(void *) = NULL;
// 内部内存分配函数(优先使用用户自定义钩子,否则用系统malloc)
static void *ikcp_malloc(size_t size)
{
if (ikcp_malloc_hook)
return ikcp_malloc_hook(size);
return malloc(size);
}
// 内部内存释放函数(优先使用用户自定义钩子,否则用系统free)
static void ikcp_free(void *ptr)
{
if (ikcp_free_hook)
{
ikcp_free_hook(ptr);
}
else
{
free(ptr);
}
}
// 重定义内存分配器(供用户设置自定义malloc/free)
void ikcp_allocator(void *(*new_malloc)(size_t), void (*new_free)(void *))
{
ikcp_malloc_hook = new_malloc;
ikcp_free_hook = new_free;
}
// 分配一个新的KCP分段(包含数据缓冲区)
// 参数size:数据部分的长度(不包含分段头部)
static IKCPSEG *ikcp_segment_new(ikcpcb *kcp, int size)
{
// 分配"分段头部+数据缓冲区"的总内存(IKCPSEG结构后紧跟数据)
return (IKCPSEG *)ikcp_malloc(sizeof(IKCPSEG) + size);
}
// 删除一个KCP分段(释放内存)
static void ikcp_segment_delete(ikcpcb *kcp, IKCPSEG *seg)
{
ikcp_free(seg);
}
// 写日志(通过用户设置的回调函数输出)
// 参数mask:日志类型掩码(如IKCP_LOG_OUTPUT表示输出日志)
// 参数fmt:日志格式字符串
void ikcp_log(ikcpcb *kcp, int mask, const char *fmt, ...)
{
char buffer[1024]; // 临时日志缓冲区
va_list argptr;
// 检查日志掩码是否匹配且回调函数存在(避免无效日志输出)
if ((mask & kcp->logmask) == 0 || kcp->writelog == 0)
return;
// 格式化日志内容
va_start(argptr, fmt);
vsprintf(buffer, fmt, argptr);
va_end(argptr);
// 调用用户日志回调(由用户决定如何输出,如控制台、文件等)
kcp->writelog(buffer, (struct IKCPCB *)kcp, kcp->user);
}
// 检查是否可以记录指定类型的日志
static int ikcp_canlog(const ikcpcb *kcp, int mask)
{
return ((mask & kcp->logmask) != 0 && kcp->writelog != NULL) ? 1 : 0;
}
// 输出数据(通过用户设置的输出回调发送数据,通常是UDP发送)
// 参数data:待发送的数据
// 参数size:数据长度
static int ikcp_output(ikcpcb *kcp, const void *data, int size)
{
assert(kcp);
assert(kcp->output); // 必须先通过ikcp_setoutput设置输出回调
// 记录输出日志(如果允许)
if (ikcp_canlog(kcp, IKCP_LOG_OUTPUT))
{
ikcp_log(kcp, IKCP_LOG_OUTPUT, "[RO] %ld bytes", (long)size);
}
if (size == 0)
return 0; // 空数据无需发送
// 调用用户输出回调(用户需实现UDP发送逻辑)
return kcp->output((const char *)data, size, kcp, kcp->user);
}
// 输出队列内容(调试用,默认关闭)
// 功能:打印队列中的所有分段的序号和时间戳,用于调试排序或重传问题
void ikcp_qprint(const char *name, const struct IQUEUEHEAD *head)
{
#if 0 // 默认关闭,需要调试时打开
const struct IQUEUEHEAD *p;
printf("<%s>: [", name);
for (p = head->next; p != head; p = p->next) {
const IKCPSEG *seg = iqueue_entry(p, const IKCPSEG, node);
printf("(%lu %d)", (unsigned long)seg->sn, (int)(seg->ts % 10000));
if (p->next != head) printf(",");
}
printf("]\n");
#endif
}
//---------------------------------------------------------------------
// 创建KCP控制块(KCP协议的核心对象,管理一个连接的所有状态)
//---------------------------------------------------------------------
ikcpcb *ikcp_create(IUINT32 conv, void *user)
{
ikcpcb *kcp = (ikcpcb *)ikcp_malloc(sizeof(struct IKCPCB));
if (kcp == NULL)
return NULL; // 内存分配失败
// 初始化核心参数
kcp->conv = conv; // 会话ID(用于区分不同KCP连接,类似TCP的端口对)
kcp->user = user; // 用户自定义数据(透传,不参与KCP逻辑)
kcp->snd_una = 0; // 已发送但未确认的最小序号(发送窗口左边界)
kcp->snd_nxt = 0; // 下一个待发送的序号(发送窗口右边界)
kcp->rcv_nxt = 0; // 下一个期望接收的序号(接收窗口左边界)
kcp->ts_recent = 0; // 最近一次收到数据包的时间戳(用于丢包检测)
kcp->ts_lastack = 0; // 最近一次收到ACK的时间戳(用于超时判断)
kcp->ts_probe = 0; // 下一次窗口探测的时间戳
kcp->probe_wait = 0; // 窗口探测间隔(动态调整)
kcp->snd_wnd = IKCP_WND_SND; // 发送窗口大小(本地允许的未确认包最大数量)
kcp->rcv_wnd = IKCP_WND_RCV; // 接收窗口大小(本地能缓存的乱序包最大数量)
kcp->rmt_wnd = IKCP_WND_RCV; // 远端告知的接收窗口大小(远端能接收的未确认包数量)
kcp->cwnd = 0; // 拥塞窗口大小(动态调整,限制实际发送速率)
kcp->incr = 0; // 拥塞窗口增长计数器(用于拥塞避免阶段的精细控制)
kcp->probe = 0; // 窗口探测标记(组合IKCP_ASK_SEND/IKCP_ASK_TELL)
kcp->mtu = IKCP_MTU_DEF; // MTU(最大传输单元,决定单个UDP包的最大大小)
kcp->mss = kcp->mtu - IKCP_OVERHEAD; // MSS(最大分段大小 = MTU - 头部开销)
kcp->stream = 0; // 传输模式(0=消息模式,1=流模式)
// 消息模式:严格按消息边界交付;流模式:合并为字节流
// 分配发送缓冲区(3倍MTU大小,用于临时组装多个小分段为一个UDP包)
kcp->buffer = (char *)ikcp_malloc((kcp->mtu + IKCP_OVERHEAD) * 3);
if (kcp->buffer == NULL)
{ // 缓冲区分配失败,释放控制块
ikcp_free(kcp);
return NULL;
}
// 初始化四个核心队列(基于双向链表实现)
iqueue_init(&kcp->snd_queue); // 待发送队列:用户数据未进入发送窗口(等待窗口空闲)
iqueue_init(&kcp->rcv_queue); // 接收队列:已排序且连续的数据包(可交付给用户)
iqueue_init(&kcp->snd_buf); // 发送缓冲区:已进入发送窗口,等待ACK确认
iqueue_init(&kcp->rcv_buf); // 接收缓冲区:乱序接收的数据包(等待排序)
// 初始化队列计数
kcp->nrcv_buf = 0; // 接收缓冲区中的分段数量
kcp->nsnd_buf = 0; // 发送缓冲区中的分段数量
kcp->nrcv_que = 0; // 接收队列中的分段数量
kcp->nsnd_que = 0; // 待发送队列中的分段数量
// 初始化其他控制参数
kcp->state = 0; // 连接状态(0=正常,-1=链路断开)
kcp->acklist = NULL; // ACK列表:记录需要回复的序号和时间戳(批量发送ACK)
kcp->ackblock = 0; // ACK列表容量(2的幂,便于扩容)
kcp->ackcount = 0; // ACK列表中的元素数量
kcp->rx_srtt = 0; // 平滑RTT(Round-Trip Time,往返时间)
kcp->rx_rttval = 0; // RTT偏差(用于计算RTO)
kcp->rx_rto = IKCP_RTO_DEF; // 重传超时时间(Retransmission Timeout)
kcp->rx_minrto = IKCP_RTO_MIN; // 最小RTO(避免过小的RTO导致抖动)
kcp->current = 0; // 当前时间戳(由ikcp_update传入,单位毫秒)
kcp->interval = IKCP_INTERVAL; // 协议刷新间隔(驱动KCP状态更新的周期)
kcp->ts_flush = IKCP_INTERVAL; // 下一次刷新的时间戳
kcp->nodelay = 0; // 无延迟模式(0=关闭,1=开启)
// 开启后:RTO更小,重传更快,延迟降低但带宽消耗增加
kcp->updated = 0; // 是否调用过ikcp_update(初始化标记)
kcp->logmask = 0; // 日志掩码(控制哪些类型的日志被输出)
kcp->ssthresh = IKCP_THRESH_INIT; // 慢启动阈值(拥塞控制的关键参数)
kcp->fastresend = 0; // 快速重传触发阈值(被跳过的ACK数量)
// 例如=2:当一个包被2个后续ACK跳过,立即重传
kcp->fastlimit = IKCP_FASTACK_LIMIT; // 快速重传的最大次数限制
kcp->nocwnd = 0; // 是否禁用拥塞控制(0=启用,1=禁用)
// 禁用后:不考虑网络拥塞,按窗口大小全速发送
kcp->xmit = 0; // 总重传次数计数器(用于统计丢包情况)
kcp->dead_link = IKCP_DEADLINK; // 链路断开的重传次数阈值(超过则标记连接断开)
kcp->output = NULL; // 输出回调函数(KCP发送数据时调用,通常是UDP发送)
kcp->writelog = NULL; // 日志回调函数(用户自定义日志输出)
return kcp;
}
//---------------------------------------------------------------------
// 释放KCP控制块(清理所有资源)
//---------------------------------------------------------------------
void ikcp_release(ikcpcb *kcp)
{
assert(kcp); // 确保kcp不为NULL
if (kcp)
{
IKCPSEG *seg;
// 释放发送缓冲区中的所有分段
while (!iqueue_is_empty(&kcp->snd_buf))
{
seg = iqueue_entry(kcp->snd_buf.next, IKCPSEG, node); // 获取队头分段
iqueue_del(&seg->node); // 从队列中删除
ikcp_segment_delete(kcp, seg); // 释放内存
}
// 释放接收缓冲区中的所有分段
while (!iqueue_is_empty(&kcp->rcv_buf))
{
seg = iqueue_entry(kcp->rcv_buf.next, IKCPSEG, node);
iqueue_del(&seg->node);
ikcp_segment_delete(kcp, seg);
}
// 释放待发送队列中的所有分段
while (!iqueue_is_empty(&kcp->snd_queue))
{
seg = iqueue_entry(kcp->snd_queue.next, IKCPSEG, node);
iqueue_del(&seg->node);
ikcp_segment_delete(kcp, seg);
}
// 释放接收队列中的所有分段
while (!iqueue_is_empty(&kcp->rcv_queue))
{
seg = iqueue_entry(kcp->rcv_queue.next, IKCPSEG, node);
iqueue_del(&seg->node);
ikcp_segment_delete(kcp, seg);
}
// 释放发送缓冲区和ACK列表
if (kcp->buffer)
{
ikcp_free(kcp->buffer);
}
if (kcp->acklist)
{
ikcp_free(kcp->acklist);
}
// 清零所有计数和指针(避免野指针)
kcp->nrcv_buf = 0;
kcp->nsnd_buf = 0;
kcp->nrcv_que = 0;
kcp->nsnd_que = 0;
kcp->ackcount = 0;
kcp->buffer = NULL;
kcp->acklist = NULL;
// 释放控制块本身
ikcp_free(kcp);
}
}
//---------------------------------------------------------------------
// 设置输出回调函数(KCP发送数据时会调用此函数,通常绑定UDP发送)
//---------------------------------------------------------------------
void ikcp_setoutput(ikcpcb *kcp, int (*output)(const char *buf, int len,
ikcpcb *kcp, void *user))
{
kcp->output = output; // 保存用户提供的输出函数指针
}
//---------------------------------------------------------------------
// 上层接收接口:从KCP接收数据(返回数据长度,负数表示无数据)
// 参数buffer:用户缓冲区(用于存储接收的数据)
// 参数len:缓冲区大小(<0表示"窥视"模式,只获取长度不删除数据)
//---------------------------------------------------------------------
/*
核心流程:
1. 从接收队列(rcv_queue)读取完整数据(已排序且连续)
2. 将接收缓冲区(rcv_buf)中有序的数据转移到接收队列
3. 若接收窗口有空闲,标记需要告知对方(发送IKCP_CMD_WINS)
*/
int ikcp_recv(ikcpcb *kcp, char *buffer, int len)
{
struct IQUEUEHEAD *p;
int ispeek = (len < 0) ? 1 : 0; // 是否为窥视模式(1=只查看不删除数据)
int peeksize; // 当前可接收的一帧数据总长度
int recover = 0; // 标记接收窗口是否从满状态恢复为有空闲
IKCPSEG *seg;
assert(kcp);
// 接收队列为空,无数据可读
if (iqueue_is_empty(&kcp->rcv_queue))
return -1;
if (len < 0)
len = -len; // 窥视模式时,取绝对值作为缓冲区大小
// 计算当前可接收的一帧数据总长度(考虑分片)
peeksize = ikcp_peeksize(kcp);
if (peeksize < 0) // 数据不完整(分片未全部到达)
return -2;
if (peeksize > len) // 用户缓冲区不足
return -3;
// 若接收队列已满(达到rcv_wnd),标记为需要恢复窗口通知
if (kcp->nrcv_que >= kcp->rcv_wnd)
recover = 1;
// 合并分片数据(从接收队列读取完整帧)
for (len = 0, p = kcp->rcv_queue.next; p != &kcp->rcv_queue;)
{
int fragment; // 当前分片的frg标记(0表示最后一个分片)
seg = iqueue_entry(p, IKCPSEG, node);
p = p->next; // 提前获取下一个节点(避免删除当前节点后指针失效)
if (buffer)
{ // 拷贝数据到用户缓冲区
memcpy(buffer, seg->data, seg->len);
buffer += seg->len; // 移动缓冲区指针
}
len += seg->len; // 累加数据长度
fragment = seg->frg;
// 记录接收日志(如果允许)
if (ikcp_canlog(kcp, IKCP_LOG_RECV))
{
ikcp_log(kcp, IKCP_LOG_RECV, "recv sn=%lu", (unsigned long)seg->sn);
}
if (ispeek == 0)
{ // 非窥视模式,删除已读取的分段
iqueue_del(&seg->node);
ikcp_segment_delete(kcp, seg);
kcp->nrcv_que--; // 接收队列计数减1
}
if (fragment == 0) // 读到最后一个分片,结束循环
break;
}
assert(len == peeksize); // 验证数据长度是否正确(确保分片合并完整)
// 将接收缓冲区(rcv_buf)中有序的数据转移到接收队列(rcv_queue)
// 目的:让后续的ikcp_recv能读取到连续的数据
while (!iqueue_is_empty(&kcp->rcv_buf))
{
seg = iqueue_entry(kcp->rcv_buf.next, IKCPSEG, node);
// 条件1:序号匹配下一个期望接收的序号(rcv_nxt)
// 条件2:接收队列未满(避免超出接收窗口)
if (seg->sn == kcp->rcv_nxt && kcp->nrcv_que < kcp->rcv_wnd)
{
iqueue_del(&seg->node); // 从接收缓冲区删除
kcp->nrcv_buf--; // 接收缓冲区计数减1
iqueue_add_tail(&seg->node, &kcp->rcv_queue); // 加入接收队列尾部
kcp->nrcv_que++; // 接收队列计数加1
kcp->rcv_nxt++; // 更新下一个期望接收的序号
}
else
{
break; // 遇到乱序或队列已满,停止转移
}
}
// 若接收窗口从满状态变为有空闲,标记需要告知对方(发送IKCP_CMD_WINS)
// 目的:让远端知道可以继续发送数据
if (kcp->nrcv_que < kcp->rcv_wnd && recover)
{
kcp->probe |= IKCP_ASK_TELL;
}
return len; // 返回实际读取的数据长度
}
//---------------------------------------------------------------------
// 预览数据大小:获取当前可接收的一帧数据总长度(用于用户分配缓冲区)
//---------------------------------------------------------------------
int ikcp_peeksize(const ikcpcb *kcp)
{
struct IQUEUEHEAD *p;
IKCPSEG *seg;
int length = 0; // 累加数据长度
assert(kcp);
if (iqueue_is_empty(&kcp->rcv_queue))
return -1; // 接收队列为空
seg = iqueue_entry(kcp->rcv_queue.next, IKCPSEG, node);
if (seg->frg == 0)
return seg->len; // 单分片数据,直接返回长度
// 多分片数据:检查分片是否完整(接收队列中的分片数是否足够)
// frg表示当前分片在消息中的索引(从n-1递减到0),因此总分片数为frg+1
if (kcp->nrcv_que < seg->frg + 1)
return -1; // 分片不完整
// 累加所有分片的长度(直到最后一个分片,frg=0)
for (p = kcp->rcv_queue.next; p != &kcp->rcv_queue; p = p->next)
{
seg = iqueue_entry(p, IKCPSEG, node);
length += seg->len;
if (seg->frg == 0)
break; // 最后一个分片
}
return length;
}
//---------------------------------------------------------------------
// 上层发送接口:向KCP发送数据(返回0表示成功,负数表示错误)
// 参数buffer:待发送的数据
// 参数len:数据长度
//---------------------------------------------------------------------
/*
核心功能:
将用户数据按MSS分片,插入待发送队列(snd_queue),等待进入发送窗口。
分片规则:
- 数据长度 ≤ MSS:不分片(frg=0)
- 数据长度 > MSS:分片,frg从n-1递减到0(n为分片数)
例:2900字节数据,MSS=1400 → 分片为1400(frg=1)、1500(frg=0)
两种模式:
1. 流模式(stream=1):尽量填充分片到MSS大小,接收端重组为字节流
(类似TCP,无消息边界,适合大文件传输)
2. 消息模式(stream=0):严格按消息边界分片,接收端按消息完整交付
(类似UDP,保留消息边界,适合小消息传输)
*/
int ikcp_send(ikcpcb *kcp, const char *buffer, int len)
{
IKCPSEG *seg;
int count, i; // count:分片数量;i:循环计数器
assert(kcp->mss > 0); // MSS必须为正数(由mtu - overhead计算)
if (len < 0)
return -1; // 无效长度
// 流模式:尝试填充上一个未填满的分片(如果存在)
// 目的:减少小分片数量,提高传输效率
if (kcp->stream != 0)
{
if (!iqueue_is_empty(&kcp->snd_queue))
{
IKCPSEG *old = iqueue_entry(kcp->snd_queue.prev, IKCPSEG, node);
if (old->len < kcp->mss)
{ // 上一个分片未满
int capacity = kcp->mss - old->len; // 剩余容量
int extend = (len < capacity) ? len : capacity; // 本次填充长度
seg = ikcp_segment_new(kcp, old->len + extend); // 新分片
assert(seg);
if (seg == NULL)
{
return -2; // 内存分配失败
}
iqueue_add_tail(&seg->node, &kcp->snd_queue); // 加入队列尾部
memcpy(seg->data, old->data, old->len); // 拷贝旧数据
if (buffer)
{
memcpy(seg->data + old->len, buffer, extend); // 填充新数据
buffer += extend; // 移动数据指针
}
seg->len = old->len + extend;
seg->frg = 0; // 流模式分片标记为0(无需区分分片顺序)
len -= extend; // 剩余数据长度
// 删除旧分片(已被新分片替代)
iqueue_del_init(&old->node);
ikcp_segment_delete(kcp, old);
}
}
if (len <= 0)
{ // 数据已全部填充到上一个分片
return 0;
}
}
// 计算分片数量(向上取整)
if (len <= (int)kcp->mss)
count = 1;
else
count = (len + kcp->mss - 1) / kcp->mss;
/*
当 len > mss 时
要把长度为 len 的数据,按 "每个分片最多 mss 字节" 拆分,所需分片数的逻辑是:
如果 len 能被 mss 整除,分片数为 len / mss;
如果 len 不能被 mss 整除,分片数为 len / mss + 1(余下的部分需要额外一个分片)。
而 (len + mss - 1) / mss 通过数学技巧,用一次整数除法同时覆盖这两种情况:
*/
// 分片数不能超过接收窗口大小(避免对方无法缓存所有分片)
if (count >= (int)IKCP_WND_RCV)
return -2;
if (count == 0)
count = 1; // 确保至少一个分片
// 创建分片并加入待发送队列
for (i = 0; i < count; i++)
{
int size = len > (int)kcp->mss ? (int)kcp->mss : len; // 分片大小
seg = ikcp_segment_new(kcp, size); // 分配分片(包含数据缓冲区)
assert(seg);
if (seg == NULL)
{
return -2; // 内存分配失败
}
if (buffer && len > 0)
{
memcpy(seg->data, buffer, size); // 拷贝数据到分片
}
seg->len = size;
// 消息模式:frg从count-1递减到0(标识分片顺序)
// 流模式:frg=0(无需顺序标识)
seg->frg = (kcp->stream == 0) ? (count - i - 1) : 0;
iqueue_init(&seg->node); // 初始化链表节点
iqueue_add_tail(&seg->node, &kcp->snd_queue); // 加入待发送队列尾部
kcp->nsnd_que++; // 待发送队列计数+1
if (buffer)
{
buffer += size; // 移动数据指针
}
len -= size; // 剩余数据长度
}
return 0; // 成功
}
//---------------------------------------------------------------------
// 处理ACK:更新RTT和RTO(基于TCP的RTT估计算法,保证重传及时性)
// 参数rtt:本次测量的RTT(当前时间 - 数据包发送时间)
//---------------------------------------------------------------------
/*
RTT(往返时间):数据包从发送到收到ACK的时间,反映网络延迟。
RTO(重传超时时间):超过此时间未收到ACK则重传,需基于RTT动态计算。
算法逻辑(与TCP标准一致):
1. 首次测量(rx_srtt=0):
rx_srtt = rtt(平滑RTT初始化为当前RTT)
rx_rttval = rtt/2(RTT偏差初始化为RTT的一半)
2. 后续测量:
rx_srtt = (7*rx_srtt + rtt) / 8 (平滑RTT,旧值权重7/8,新值1/8)
rx_rttval = (3*rx_rttval + |rtt - rx_srtt|) / 4 (RTT偏差,旧值3/4,新值1/4)
rto = rx_srtt + 4*rx_rttval (RTO = 平滑RTT + 4倍偏差,应对网络抖动)
3. 限制RTO在[rx_minrto, IKCP_RTO_MAX]范围内(避免过小或过大)
*/
static void ikcp_update_ack(ikcpcb *kcp, IINT32 rtt)
{
IINT32 rto = 0; // 计算得到的RTO
if (kcp->rx_srtt == 0)
{ // 首次测量RTT
kcp->rx_srtt = rtt;
kcp->rx_rttval = rtt / 2;
}
else
{
long delta = rtt - kcp->rx_srtt; // 本次RTT与平滑RTT的差值
if (delta < 0)
delta = -delta; // 取绝对值(关注偏差大小,不关注方向)
// 更新RTT偏差(平滑处理,降低抖动影响)
kcp->rx_rttval = (3 * kcp->rx_rttval + delta) / 4;
// 更新平滑RTT(缓慢跟随实际RTT变化)
kcp->rx_srtt = (7 * kcp->rx_srtt + rtt) / 8;
if (kcp->rx_srtt < 1)
kcp->rx_srtt = 1; // 避免为0(防止后续计算异常)
}
// 计算RTO:平滑RTT + 4倍偏差,且不小于当前刷新间隔(避免过短)
rto = kcp->rx_srtt + _imax_(kcp->interval, 4 * kcp->rx_rttval);
// 限制RTO在合理范围
kcp->rx_rto = _ibound_(kcp->rx_minrto, rto, IKCP_RTO_MAX);
}
// 更新未确认序号(snd_una):指向发送缓冲区中最小的未确认序号
// 作用:标记发送窗口的左边界,snd_una ~ snd_nxt-1为未确认的数据包
static void ikcp_shrink_buf(ikcpcb *kcp)
{
struct IQUEUEHEAD *p = kcp->snd_buf.next;
if (p != &kcp->snd_buf)
{ // 发送缓冲区非空
IKCPSEG *seg = iqueue_entry(p, IKCPSEG, node);
kcp->snd_una = seg->sn; // 最小未确认序号为队头分段的序号
}
else
{
kcp->snd_una = kcp->snd_nxt; // 发送缓冲区为空,未确认序号等于下一个发送序号
}
}
// 处理单个ACK:从发送缓冲区删除对应序号的分段
// 参数sn:ACK确认的序号
static void ikcp_parse_ack(ikcpcb *kcp, IUINT32 sn)
{
struct IQUEUEHEAD *p, *next;
// 检查ACK序号是否在未确认区间内(snd_una ≤ sn < snd_nxt)
// 若不在区间内,说明是无效ACK(可能是延迟的旧ACK),直接忽略
if (_itimediff(sn, kcp->snd_una) < 0 || _itimediff(sn, kcp->snd_nxt) >= 0)
return;
// 遍历发送缓冲区,查找并删除对应序号的分段
// 发送缓冲区按序号递增排序,因此找到第一个大于sn的分段即可停止
for (p = kcp->snd_buf.next; p != &kcp->snd_buf; p = next)
{
IKCPSEG *seg = iqueue_entry(p, IKCPSEG, node);
next = p->next; // 提前获取下一个节点(避免删除当前节点后指针失效)
if (sn == seg->sn)
{ // 找到匹配的分段
iqueue_del(p); // 从队列中删除
ikcp_segment_delete(kcp, seg); // 释放内存
kcp->nsnd_buf--; // 发送缓冲区计数减1
break;
}
// 若当前分段序号大于sn,后续分段序号更大,无需继续查找
if (_itimediff(sn, seg->sn) < 0)
{
break;
}
}
}
// 处理UNA(批量确认):删除发送缓冲区中所有序号小于una的分段
// 参数una:远端已确认到的序号(表示una之前的所有序号均已收到)
static void ikcp_parse_una(ikcpcb *kcp, IUINT32 una)
{
struct IQUEUEHEAD *p, *next;
// 遍历发送缓冲区,删除所有序号小于una的分段
// 发送缓冲区按序号递增排序,因此找到第一个≥una的分段即可停止
for (p = kcp->snd_buf.next; p != &kcp->snd_buf; p = next)
{
IKCPSEG *seg = iqueue_entry(p, IKCPSEG, node);
next = p->next; // 提前获取下一个节点
if (_itimediff(una, seg->sn) > 0)
{ // 序号小于una(已确认)
iqueue_del(p);
ikcp_segment_delete(kcp, seg);
kcp->nsnd_buf--; // 发送缓冲区计数减1
}
else
{
break; // 后续分段序号≥una,无需继续
}
}
}
// 处理快速ACK:统计被跳过的分段(用于触发快速重传)
// 参数sn:当前ACK的序号;ts:当前ACK的时间戳
static void ikcp_parse_fastack(ikcpcb *kcp, IUINT32 sn, IUINT32 ts)
{
struct IQUEUEHEAD *p, *next;
// 检查ACK序号是否在未确认区间内(snd_una ≤ sn < snd_nxt)
if (_itimediff(sn, kcp->snd_una) < 0 || _itimediff(sn, kcp->snd_nxt) >= 0)
return;
// 遍历发送缓冲区,统计被当前ACK跳过的分段
// 例如:发送了1,2,3,4,5,收到ACK=5,说明2,3,4可能被跳过
for (p = kcp->snd_buf.next; p != &kcp->snd_buf; p = next)
{
IKCPSEG *seg = iqueue_entry(p, IKCPSEG, node);
next = p->next;
if (_itimediff(sn, seg->sn) < 0)
{ // 序号大于当前分段,停止遍历
break;
}
else if (sn != seg->sn)
{ // 序号不等于当前分段(被跳过)
#ifndef IKCP_FASTACK_CONSERVE
seg->fastack++; // 增加跳过计数(非保守模式)
#else
// 保守模式:仅当ACK时间戳晚于分段发送时间戳时计数
// 避免因旧ACK导致误判
if (_itimediff(ts, seg->ts) >= 0)
seg->fastack++;
#endif
}
}
}
//---------------------------------------------------------------------
// 添加ACK到列表:记录需要确认的序号和时间戳(用于后续批量发送ACK)
// 参数sn:需要确认的序号;ts:对应数据包的发送时间戳(用于对方计算RTT)
//---------------------------------------------------------------------
static void ikcp_ack_push(ikcpcb *kcp, IUINT32 sn, IUINT32 ts)
{
IUINT32 newsize = kcp->ackcount + 1; // 新的ACK数量
IUINT32 *ptr; // 指向新ACK的存储位置
// 如果当前容量不足,扩容(按2的幂扩容,提高内存利用率)
if (newsize > kcp->ackblock)
{
IUINT32 *acklist;
IUINT32 newblock;
// 计算新容量(大于newsize的最小2的幂)
for (newblock = 8; newblock < newsize; newblock <<= 1)
;
/*
按2的次幂扩容原因
1. 内存分配器的天然优化
操作系统和底层内存分配器(如 malloc)对 "2 的次幂大小" 的内存块支持更高效。许多内存分配算法(如「伙伴系统」)本身就是基于 "2 的次幂" 组织空闲内存块的。按 2 的次幂扩容,能让 ikcp_malloc 更高效地找到匹配的内存块,减少内存碎片,提升分配 / 释放的速度。
2. 减少扩容的总开销
每次扩容都需要 分配新内存 + 拷贝旧数据,这是有性能开销的。按 "2 的次幂" 扩容(如从 8→16→32→64...),内存容量以 **"翻倍"的速度增长,能大幅减少扩容的总次数 **。
举个例子:要存储 100 个 ACK,按 2 的次幂扩容只需从 8→16→32→64→128(共 4 次扩容);如果是 "线性扩容"(每次 + 8),则需要 12 次扩容,总开销大得多。
3. 时间与空间的权衡
有人会担心 "按 2 的次幂扩容会浪费内存"(比如需要存 9 个元素,却扩容到 16,浪费 7 个位置)。但这是 **"时间换空间" 的权衡 **------ 少量的内存浪费,能换来 "内存分配更高效、扩容总开销更低" 的优势,在 KCP 这类 ** 频繁处理 ACK(网络场景)** 的场景中,整体性能会更好。
*/
// 分配新内存(每个ACK需存储sn和ts,各4字节,共8字节)
acklist = (IUINT32 *)ikcp_malloc(newblock * sizeof(IUINT32) * 2);
if (acklist == NULL)
{
assert(acklist != NULL); // 内存分配失败,断言退出
abort();
}
// 拷贝旧数据到新内存
if (kcp->acklist != NULL)
{
IUINT32 x;
for (x = 0; x < kcp->ackcount; x++)
{
acklist[x * 2 + 0] = kcp->acklist[x * 2 + 0]; // sn
acklist[x * 2 + 1] = kcp->acklist[x * 2 + 1]; // ts
}
ikcp_free(kcp->acklist); // 释放旧内存
}
kcp->acklist = acklist; // 更新指针
kcp->ackblock = newblock; // 更新容量
}
// 添加新的ACK记录(sn和ts)
ptr = &kcp->acklist[kcp->ackcount * 2];
ptr[0] = sn;
ptr[1] = ts;
kcp->ackcount++; // ACK数量加1
}
// 从ACK列表获取指定位置的记录
// 参数p:索引;sn/ts:输出参数(存储获取的序号和时间戳)
static void ikcp_ack_get(const ikcpcb *kcp, int p, IUINT32 *sn, IUINT32 *ts)
{
if (sn)
sn[0] = kcp->acklist[p * 2 + 0];
if (ts)
ts[0] = kcp->acklist[p * 2 + 1];
}
//---------------------------------------------------------------------
// 处理接收的数据分段:加入接收缓冲区并尝试排序
// 参数newseg:收到的新分段
//---------------------------------------------------------------------
void ikcp_parse_data(ikcpcb *kcp, IKCPSEG *newseg)
{
struct IQUEUEHEAD *p, *prev;
IUINT32 sn = newseg->sn; // 分段的序号
int repeat = 0; // 是否为重复分段(接收缓冲区中已有相同序号)
// 检查序号是否在接收窗口范围内([rcv_nxt, rcv_nxt + rcv_wnd))
// 超出范围的分段直接丢弃(避免缓存过多无效数据)
if (_itimediff(sn, kcp->rcv_nxt + kcp->rcv_wnd) >= 0 ||
_itimediff(sn, kcp->rcv_nxt) < 0)
{
ikcp_segment_delete(kcp, newseg); // 超出窗口,丢弃
return;
}
// 检查是否为重复分段(接收缓冲区中已有相同序号)
// 接收缓冲区按序号递增排序,从尾部开始查找(效率更高)
for (p = kcp->rcv_buf.prev; p != &kcp->rcv_buf; p = prev)
{
IKCPSEG *seg = iqueue_entry(p, IKCPSEG, node);
prev = p->prev;
if (seg->sn == sn)
{ // 发现重复分段
repeat = 1;
break;
}
if (_itimediff(sn, seg->sn) > 0)
{ // 找到插入位置(保持序号递增)
break;
}
}
// 非重复分段,加入接收缓冲区(按序号排序)
if (repeat == 0)
{
iqueue_init(&newseg->node);
iqueue_add(&newseg->node, p); // 插入到正确位置
kcp->nrcv_buf++; // 接收缓冲区计数加1
}
else
{
ikcp_segment_delete(kcp, newseg); // 重复分段,丢弃
}
// 将接收缓冲区中有序的数据转移到接收队列(rcv_queue)
// 目的:让连续的、可交付的数据进入接收队列,供上层读取
while (!iqueue_is_empty(&kcp->rcv_buf))
{
IKCPSEG *seg = iqueue_entry(kcp->rcv_buf.next, IKCPSEG, node);
// 条件1:序号匹配下一个期望接收的序号(rcv_nxt)
// 条件2:接收队列未满(避免超出接收窗口)
if (seg->sn == kcp->rcv_nxt && kcp->nrcv_que < kcp->rcv_wnd)
{
iqueue_del(&seg->node); // 从接收缓冲区删除
kcp->nrcv_buf--; // 接收缓冲区计数减1
iqueue_add_tail(&seg->node, &kcp->rcv_queue); // 加入接收队列尾部
kcp->nrcv_que++; // 接收队列计数加1
kcp->rcv_nxt++; // 更新下一个期望接收的序号
}
else
{
break; // 遇到乱序或队列已满,停止转移
}
}
}
//---------------------------------------------------------------------
// 输入数据接口:处理接收到的KCP数据包(通常由UDP接收触发)
// 参数data:接收到的数据;size:数据长度
// 返回值:0=成功,负数=错误
//---------------------------------------------------------------------
int ikcp_input(ikcpcb *kcp, const char *data, long size)
{
IUINT32 prev_una = kcp->snd_una; // 记录当前未确认序号(用于后续拥塞控制)
IUINT32 maxack = 0, latest_ts = 0; // 最大的ACK序号和对应的时间戳
int flag = 0; // 是否收到ACK的标记
// 记录输入日志(如果允许)
if (ikcp_canlog(kcp, IKCP_LOG_INPUT))
{
ikcp_log(kcp, IKCP_LOG_INPUT, "[RI] %d bytes", (int)size);
}
// 数据合法性检查(至少包含KCP头部,24字节)
if (data == NULL || (int)size < (int)IKCP_OVERHEAD)
{
printf("%s conv:%u, data:%p, size:%ld\n", __FUNCTION__, kcp->conv, data, size);
return -1;
}
// 循环解析所有数据包(一个UDP包可能包含多个KCP分段)
while (1)
{
IUINT32 ts, sn, len, una, conv; // KCP头部字段
IUINT16 wnd;
IUINT8 cmd, frg;
IKCPSEG *seg; // 解析出的KCP分段
if (size < (int)IKCP_OVERHEAD)
break; // 数据不足,退出循环
// 解码KCP头部(24字节)
data = ikcp_decode32u(data, &conv); // 会话ID(必须匹配当前连接)
if (conv != kcp->conv)
return -1; // 会话ID不匹配,错误
data = ikcp_decode8u(data, &cmd); // 命令(PUSH/ACK/WASK/WINS)
data = ikcp_decode8u(data, &frg); // 分片标记
data = ikcp_decode16u(data, &wnd); // 远端的接收窗口大小
data = ikcp_decode32u(data, &ts); // 时间戳(发送方的发送时间)
data = ikcp_decode32u(data, &sn); // 序号
data = ikcp_decode32u(data, &una); // 已确认到的序号(UNA)
data = ikcp_decode32u(data, &len); // 数据长度
size -= IKCP_OVERHEAD; // 减去头部长度,剩余为数据部分
// 数据长度检查(实际数据需足够)
if ((long)size < (long)len || (int)len < 0)
return -2;
// 命令合法性检查(只支持四种命令)
if (cmd != IKCP_CMD_PUSH && cmd != IKCP_CMD_ACK &&
cmd != IKCP_CMD_WASK && cmd != IKCP_CMD_WINS)
return -3;
// 更新远端窗口大小(用于流量控制)
kcp->rmt_wnd = wnd;
// 处理批量确认(UNA):删除所有序号小于una的分段
ikcp_parse_una(kcp, una);
// 更新未确认序号(snd_una)
ikcp_shrink_buf(kcp);
if (cmd == IKCP_CMD_ACK)
{ // 处理ACK命令(对方确认收到数据)
// 计算RTT并更新RTO(仅当时间戳有效,即当前时间≥发送时间)
if (_itimediff(kcp->current, ts) >= 0)
{
ikcp_update_ack(kcp, _itimediff(kcp->current, ts));
}
// 处理单个ACK:删除对应序号的分段
ikcp_parse_ack(kcp, sn);
// 更新未确认序号
ikcp_shrink_buf(kcp);
// 记录最大的ACK序号(用于后续快速重传计算)
if (flag == 0)
{
flag = 1;
maxack = sn;
latest_ts = ts;
}
else
{
if (_itimediff(sn, maxack) > 0)
{ // 新ACK序号更大
#ifndef IKCP_FASTACK_CONSERVE
maxack = sn;
latest_ts = ts;
#else
// 保守模式:仅当时间戳更新时才更新最大ACK
if (_itimediff(ts, latest_ts) > 0)
{
maxack = sn;
latest_ts = ts;
}
#endif
}
}
// 记录ACK日志(如果允许)
if (ikcp_canlog(kcp, IKCP_LOG_IN_ACK))
{
ikcp_log(kcp, IKCP_LOG_IN_ACK,
"input ack: sn=%lu rtt=%ld rto=%ld", (unsigned long)sn,
(long)_itimediff(kcp->current, ts),
(long)kcp->rx_rto);
}
}
else if (cmd == IKCP_CMD_PUSH)
{ // 处理数据推送命令(收到对方发送的数据)
// 记录数据日志(如果允许)
if (ikcp_canlog(kcp, IKCP_LOG_IN_DATA))
{
ikcp_log(kcp, IKCP_LOG_IN_DATA,
"input psh: sn=%lu ts=%lu", (unsigned long)sn, (unsigned long)ts);
}
// 检查序号是否在接收窗口范围内
if (_itimediff(sn, kcp->rcv_nxt + kcp->rcv_wnd) < 0)
{
// 添加ACK记录(用于回复对方,告知已收到该序号)
ikcp_ack_push(kcp, sn, ts);
// 序号在期望接收范围内(≥rcv_nxt),处理数据
if (_itimediff(sn, kcp->rcv_nxt) >= 0)
{
seg = ikcp_segment_new(kcp, len); // 创建新分段
// 填充分段信息
seg->conv = conv;
seg->cmd = cmd;
seg->frg = frg;
seg->wnd = wnd;
seg->ts = ts;
seg->sn = sn;
seg->una = una;
seg->len = len;
if (len > 0)
{
memcpy(seg->data, data, len); // 拷贝数据部分
}
// 将分段加入接收缓冲区并尝试排序
ikcp_parse_data(kcp, seg);
}
}
}
else if (cmd == IKCP_CMD_WASK)
{ // 处理窗口探测请求(对方询问我方窗口大小)
// 标记需要发送窗口大小告知(IKCP_CMD_WINS)
kcp->probe |= IKCP_ASK_TELL;
if (ikcp_canlog(kcp, IKCP_LOG_IN_PROBE))
{
ikcp_log(kcp, IKCP_LOG_IN_PROBE, "input probe");
}
}
else if (cmd == IKCP_CMD_WINS)
{ // 处理窗口大小告知(对方主动告知其窗口大小)
// 无需额外操作,已通过kcp->rmt_wnd = wnd更新
if (ikcp_canlog(kcp, IKCP_LOG_IN_WINS))
{
ikcp_log(kcp, IKCP_LOG_IN_WINS,
"input wins: %lu", (unsigned long)(wnd));
}
}
else
{
return -3; // 未知命令,错误
}
// 移动数据指针,处理下一个分段
data += len;
size -= len;
}
// 如果收到ACK,处理快速重传统计(统计被跳过的分段)
if (flag != 0)
{
ikcp_parse_fastack(kcp, maxack, latest_ts);
}
// 如果未确认序号前进(数据被确认),更新拥塞窗口(慢启动/拥塞避免)
if (_itimediff(kcp->snd_una, prev_una) > 0)
{
if (kcp->cwnd < kcp->rmt_wnd)
{ // 拥塞窗口小于远端窗口(有增长空间)
IUINT32 mss = kcp->mss; // 最大分段大小
if (kcp->cwnd < kcp->ssthresh)
{ // 慢启动阶段(未达阈值)
kcp->cwnd++; // 拥塞窗口线性增长(+1 per ACK)
kcp->incr += mss;
}
else
{ // 拥塞避免阶段(已达阈值)
if (kcp->incr < mss)
kcp->incr = mss;
// 拥塞窗口非线性增长(近似TCP的加性增,每次增加≈1个MSS)
kcp->incr += (mss * mss) / kcp->incr + (mss / 16);
if ((kcp->cwnd + 1) * mss <= kcp->incr)
{
kcp->cwnd = (kcp->incr + mss - 1) / ((mss > 0) ? mss : 1);
}
}
// 拥塞窗口不能超过远端窗口(受对方接收能力限制)
if (kcp->cwnd > kcp->rmt_wnd)
{
kcp->cwnd = kcp->rmt_wnd;
kcp->incr = kcp->rmt_wnd * mss;
}
}
}
return 0; // 成功
}
//---------------------------------------------------------------------
// 编码分段:将KCP分段转换为字节流(用于发送)
// 参数ptr:输出缓冲区指针;seg:待编码的分段
// 返回值:编码后的缓冲区指针(后移)
//---------------------------------------------------------------------
static char *ikcp_encode_seg(char *ptr, const IKCPSEG *seg)
{
ptr = ikcp_encode32u(ptr, seg->conv); // 会话ID
ptr = ikcp_encode8u(ptr, (IUINT8)seg->cmd); // 命令
ptr = ikcp_encode8u(ptr, (IUINT8)seg->frg); // 分片标记
ptr = ikcp_encode16u(ptr, (IUINT16)seg->wnd); // 窗口大小
ptr = ikcp_encode32u(ptr, seg->ts); // 时间戳
ptr = ikcp_encode32u(ptr, seg->sn); // 序号
ptr = ikcp_encode32u(ptr, seg->una); // 已确认到的序号
ptr = ikcp_encode32u(ptr, seg->len); // 数据长度
return ptr;
}
// 计算接收窗口的空闲大小(可接收的新分段数量)
static int ikcp_wnd_unused(const ikcpcb *kcp)
{
if (kcp->nrcv_que < kcp->rcv_wnd)
{
return kcp->rcv_wnd - kcp->nrcv_que; // 空闲数量 = 窗口大小 - 已用数量
}
return 0; // 窗口已满
}
//---------------------------------------------------------------------
// 刷新数据:发送待发送的ACK、探测包和数据分段(KCP核心发送逻辑)
//---------------------------------------------------------------------
void ikcp_flush(ikcpcb *kcp)
{
IUINT32 current = kcp->current; // 当前时间戳
char *buffer = kcp->buffer; // 临时发送缓冲区(用于组装多个分段)
char *ptr = buffer; // 缓冲区指针
int count, size, i; // 循环变量和大小计算
IUINT32 resent, cwnd; // 快速重传阈值和拥塞窗口
IUINT32 rtomin; // 最小重传时间偏移
struct IQUEUEHEAD *p; // 链表遍历指针
int change = 0; // 快速重传标记(用于后续拥塞控制)
int lost = 0; // 超时重传标记(用于后续拥塞控制)
IKCPSEG seg; // 临时分段(用于组装ACK和探测包)
// 如果未调用过ikcp_update(未初始化),直接返回
if (kcp->updated == 0)
return;
// 初始化ACK分段(用于批量发送ACK)
seg.conv = kcp->conv;
seg.cmd = IKCP_CMD_ACK;
seg.frg = 0;
seg.wnd = ikcp_wnd_unused(kcp); // 携带当前接收窗口空闲大小
seg.una = kcp->rcv_nxt; // 携带已确认到的序号(UNA)
seg.len = 0; // ACK无数据部分
seg.sn = 0; // 后续从acklist填充
seg.ts = 0; // 后续从acklist填充
// 发送所有待确认的ACK(批量发送,减少小数据包数量)
count = kcp->ackcount;
for (i = 0; i < count; i++)
{
size = (int)(ptr - buffer); // 当前缓冲区已使用大小
// 如果当前缓冲区加上新ACK的头部(24字节)超过MTU,发送当前缓冲区
if (size + (int)IKCP_OVERHEAD > (int)kcp->mtu)
{
ikcp_output(kcp, buffer, size); // 发送
ptr = buffer; // 重置指针
}
// 从acklist获取ACK的序号和时间戳
ikcp_ack_get(kcp, i, &seg.sn, &seg.ts);
// 编码ACK分段头部
ptr = ikcp_encode_seg(ptr, &seg);
}
kcp->ackcount = 0; // 清空ACK列表(已发送)
// 处理窗口探测(当远端窗口为0时,主动探测是否可恢复传输)
if (kcp->rmt_wnd == 0)
{
if (kcp->probe_wait == 0)
{ // 首次探测
kcp->probe_wait = IKCP_PROBE_INIT; // 初始间隔7秒
kcp->ts_probe = kcp->current + kcp->probe_wait; // 下一次探测时间
}
else
{
// 到达探测时间,更新探测间隔并标记发送探测包
if (_itimediff(kcp->current, kcp->ts_probe) >= 0)
{
if (kcp->probe_wait < IKCP_PROBE_INIT)
kcp->probe_wait = IKCP_PROBE_INIT;
kcp->probe_wait += kcp->probe_wait / 2; // 间隔每次增加50%(指数退避)
if (kcp->probe_wait > IKCP_PROBE_LIMIT)
kcp->probe_wait = IKCP_PROBE_LIMIT; // 最大间隔120秒
kcp->ts_probe = kcp->current + kcp->probe_wait; // 更新下一次探测时间
kcp->probe |= IKCP_ASK_SEND; // 标记需要发送探测包
}
}
}
else
{
// 远端窗口正常,重置探测参数
kcp->ts_probe = 0;
kcp->probe_wait = 0;
}
// 发送窗口探测请求(IKCP_CMD_WASK)
if (kcp->probe & IKCP_ASK_SEND)
{
seg.cmd = IKCP_CMD_WASK; // 切换命令为窗口探测请求
size = (int)(ptr - buffer);
if (size + (int)IKCP_OVERHEAD > (int)kcp->mtu)
{
ikcp_output(kcp, buffer, size);
ptr = buffer;
}
ptr = ikcp_encode_seg(ptr, &seg); // 编码并加入缓冲区
}
// 发送窗口大小告知(IKCP_CMD_WINS)
if (kcp->probe & IKCP_ASK_TELL)
{
seg.cmd = IKCP_CMD_WINS; // 切换命令为窗口大小告知
size = (int)(ptr - buffer);
if (size + (int)IKCP_OVERHEAD > (int)kcp->mtu)
{
ikcp_output(kcp, buffer, size);
ptr = buffer;
}
ptr = ikcp_encode_seg(ptr, &seg); // 编码并加入缓冲区
}
kcp->probe = 0; // 清空探测标记(已处理)
// 计算实际发送窗口(取发送窗口、远端窗口、拥塞窗口的最小值)
cwnd = _imin_(kcp->snd_wnd, kcp->rmt_wnd); // 受本地发送窗口和远端接收窗口限制
if (kcp->nocwnd == 0) // 如果启用拥塞控制,再受拥塞窗口限制
cwnd = _imin_(kcp->cwnd, cwnd);
// 将待发送队列(snd_queue)中的数据转移到发送缓冲区(snd_buf)
// 条件:下一个发送序号(snd_nxt) < 未确认序号(snd_una) + 实际发送窗口(cwnd)
while (_itimediff(kcp->snd_nxt, kcp->snd_una + cwnd) < 0)
{
IKCPSEG *newseg;
if (iqueue_is_empty(&kcp->snd_queue))
break; // 待发送队列为空,退出
newseg = iqueue_entry(kcp->snd_queue.next, IKCPSEG, node); // 取队头分段
iqueue_del(&newseg->node); // 从待发送队列删除
iqueue_add_tail(&newseg->node, &kcp->snd_buf); // 加入发送缓冲区尾部
kcp->nsnd_que--; // 待发送队列计数减1
kcp->nsnd_buf++; // 发送缓冲区计数加1
// 初始化分段的控制参数(准备发送)
newseg->conv = kcp->conv;
newseg->cmd = IKCP_CMD_PUSH; // 命令为数据推送
newseg->wnd = seg.wnd; // 携带本地接收窗口空闲大小
newseg->ts = current; // 当前时间戳(用于对方计算RTT)
newseg->sn = kcp->snd_nxt++; // 分配序号(递增)
newseg->una = kcp->rcv_nxt; // 携带已确认到的序号(告知对方哪些数据已收到)
newseg->resendts = current; // 重传时间戳(初始为当前时间)
newseg->rto = kcp->rx_rto; // 重传超时时间(使用当前RTO)
newseg->fastack = 0; // 快速重传计数(初始为0)
newseg->xmit = 0; // 发送次数(初始为0)
}
// 快速重传阈值(fastresend=0表示禁用快速重传)
resent = (kcp->fastresend > 0) ? (IUINT32)kcp->fastresend : 0xffffffff;
// 最小重传时间偏移(无延迟模式为0,否则为RTO/8,避免过早重传)
rtomin = (kcp->nodelay == 0) ? (kcp->rx_rto >> 3) : 0;
// 发送发送缓冲区中的数据分段(处理新发送和重传)
for (p = kcp->snd_buf.next; p != &kcp->snd_buf; p = p->next)
{
IKCPSEG *segment = iqueue_entry(p, IKCPSEG, node);
int needsend = 0; // 是否需要发送该分段
if (segment->xmit == 0)
{ // 首次发送
needsend = 1;
segment->xmit++; // 发送次数+1
segment->rto = kcp->rx_rto; // 初始RTO
// 计算首次重传时间(当前时间+RTO+rtomin)
segment->resendts = current + segment->rto + rtomin;
}
else if (_itimediff(current, segment->resendts) >= 0)
{ // 超时重传(未收到ACK)
needsend = 1;
segment->xmit++; // 发送次数+1
kcp->xmit++; // 总重传次数+1
// 计算新的RTO(根据是否启用无延迟模式调整)
if (kcp->nodelay == 0)
{
// 正常模式:RTO += max(当前RTO, 最新RTO)(快速增长,避免频繁重传)
segment->rto += _imax_(segment->rto, (IUINT32)kcp->rx_rto);
}
else
{
// 无延迟模式:RTO += RTO/2( slower增长,更快重传)
IINT32 step = (kcp->nodelay < 2) ? ((IINT32)(segment->rto)) : kcp->rx_rto;
segment->rto += step / 2; // RTO *= 1.5
}
segment->resendts = current + segment->rto; // 更新重传时间
lost = 1; // 标记发生超时重传(用于后续拥塞控制)
}
else if (segment->fastack >= resent)
{ // 快速重传(被跳过的ACK数量达标)
// 检查快速重传次数是否在限制内(避免过度重传)
if ((int)segment->xmit <= kcp->fastlimit || kcp->fastlimit <= 0)
{
needsend = 1;
segment->xmit++; // 发送次数+1
segment->fastack = 0; // 重置快速重传计数
segment->resendts = current + segment->rto; // 更新重传时间
change++; // 标记发生快速重传(用于后续拥塞控制)
}
}
if (needsend)
{ // 需要发送该分段
int need; // 该分段所需的缓冲区大小(头部+数据)
// 更新分段的时间戳、窗口大小和已确认序号(确保对方收到最新信息)
segment->ts = current;
segment->wnd = seg.wnd;
segment->una = kcp->rcv_nxt;
size = (int)(ptr - buffer); // 当前缓冲区已使用大小
need = IKCP_OVERHEAD + segment->len; // 该分段所需大小
// 如果当前缓冲区不足以容纳,发送当前缓冲区
if (size + need > (int)kcp->mtu)
{
ikcp_output(kcp, buffer, size);
ptr = buffer; // 重置指针
}
// 编码分段头部
ptr = ikcp_encode_seg(ptr, segment);
// 拷贝数据部分(如果有)
if (segment->len > 0)
{
memcpy(ptr, segment->data, segment->len);
ptr += segment->len; // 移动指针
}
// 检查是否达到链路断开阈值(重传次数过多)
if (segment->xmit >= kcp->dead_link)
{
kcp->state = (IUINT32)-1; // 标记链路断开
}
}
}
// 发送剩余数据(缓冲区中未发送的部分)
size = (int)(ptr - buffer);
if (size > 0)
{
ikcp_output(kcp, buffer, size);
}
// 处理快速重传后的拥塞控制(发生丢包,但可能是短暂抖动)
if (change)
{
IUINT32 inflight = kcp->snd_nxt - kcp->snd_una; // 飞行中的数据包数量
// 慢启动阈值设为飞行数据的一半(收缩窗口,减少拥塞)
kcp->ssthresh = inflight / 2;
if (kcp->ssthresh < IKCP_THRESH_MIN)
kcp->ssthresh = IKCP_THRESH_MIN;
// 拥塞窗口设为阈值+快速重传阈值(允许一定的恢复空间)
kcp->cwnd = kcp->ssthresh + resent;
kcp->incr = kcp->cwnd * kcp->mss;
}
// 处理超时重传后的拥塞控制(发生明显丢包,网络可能拥塞)
if (lost)
{
// 慢启动阈值设为当前窗口的一半(大幅收缩)
kcp->ssthresh = cwnd / 2;
if (kcp->ssthresh < IKCP_THRESH_MIN)
kcp->ssthresh = IKCP_THRESH_MIN;
// 拥塞窗口重置为1(进入慢启动阶段,缓慢恢复)
kcp->cwnd = 1;
kcp->incr = kcp->mss;
}
// 确保拥塞窗口不小于1(避免无法发送数据)
if (kcp->cwnd < 1)
{
kcp->cwnd = 1;
kcp->incr = kcp->mss;
}
}
//---------------------------------------------------------------------
// 更新KCP状态(核心定时器函数,需定期调用,驱动协议运行)
// 参数current:当前时间戳(单位毫秒)
//---------------------------------------------------------------------
void ikcp_update(ikcpcb *kcp, IUINT32 current)
{
IINT32 slap; // 当前时间与下一次刷新时间的差值
kcp->current = current; // 更新当前时间戳
// 首次调用时初始化刷新时间
if (kcp->updated == 0)
{
kcp->updated = 1;
kcp->ts_flush = kcp->current;
}
// 计算时间差(当前时间 - 下一次刷新时间)
slap = _itimediff(kcp->current, kcp->ts_flush);
// 时间戳异常(超过±10秒,可能是系统时间调整),重置刷新时间
if (slap >= 10000 || slap < -10000)
{
kcp->ts_flush = kcp->current;
slap = 0;
}
// 如果到达或超过刷新时间,执行刷新并更新下一次刷新时间
if (slap >= 0)
{
kcp->ts_flush += kcp->interval; // 累加刷新间隔
// 如果下一次刷新时间已落后于当前时间(如处理耗时过长),校正为当前时间+间隔
if (_itimediff(kcp->current, kcp->ts_flush) >= 0)
{
kcp->ts_flush = kcp->current + kcp->interval;
}
ikcp_flush(kcp); // 执行刷新(发送数据、处理重传等)
}
}
//---------------------------------------------------------------------
// 计算下一次需要调用ikcp_update的时间(优化定时器效率,避免无效调用)
// 参数current:当前时间戳
// 返回值:下一次调用ikcp_update的时间戳
//---------------------------------------------------------------------
IUINT32 ikcp_check(const ikcpcb *kcp, IUINT32 current)
{
IUINT32 ts_flush = kcp->ts_flush; // 下一次刷新时间
IINT32 tm_flush = 0x7fffffff; // 下一次刷新的时间差(初始为最大int)
IINT32 tm_packet = 0x7fffffff; // 下一次数据包重传的时间差(初始为最大int)
IUINT32 minimal = 0; // 最小的时间差
struct IQUEUEHEAD *p; // 链表遍历指针
// 未初始化(未调用过ikcp_update),立即返回当前时间
if (kcp->updated == 0)
{
return current;
}
// 时间戳异常(超过±10秒),重置刷新时间
if (_itimediff(current, ts_flush) >= 10000 ||
_itimediff(current, ts_flush) < -10000)
{
ts_flush = current;
}
// 已到达刷新时间,立即返回当前时间(需要调用ikcp_update)
if (_itimediff(current, ts_flush) >= 0)
{
return current;
}
// 计算下一次刷新的时间差(ts_flush - current)
tm_flush = _itimediff(ts_flush, current);
// 计算下一次数据包重传的时间差(取所有分段中最早的重传时间)
for (p = kcp->snd_buf.next; p != &kcp->snd_buf; p = p->next)
{
const IKCPSEG *seg = iqueue_entry(p, const IKCPSEG, node);
IINT32 diff = _itimediff(seg->resendts, current);
if (diff <= 0)
{ // 已到达重传时间,立即返回当前时间
return current;
}
if (diff < tm_packet)
tm_packet = diff; // 更新最小重传时间差
}
// 取最小的时间差(刷新或重传),且不超过刷新间隔(避免等待过久)
minimal = (IUINT32)(tm_packet < tm_flush ? tm_packet : tm_flush);
if (minimal >= kcp->interval)
minimal = kcp->interval;
return current + minimal; // 下一次调用ikcp_update的时间
}
//---------------------------------------------------------------------
// 设置MTU(最大传输单元)
// 参数mtu:新的MTU值(字节)
// 返回值:0=成功,-1=无效MTU,-2=内存分配失败
//---------------------------------------------------------------------
int ikcp_setmtu(ikcpcb *kcp, int mtu)
{
char *buffer;
// MTU必须足够大(至少能容纳KCP头部24字节,且不小于50字节)
if (mtu < 50 || mtu < (int)IKCP_OVERHEAD)
return -1;
// 分配新的发送缓冲区(3倍MTU大小,用于组装多个小分段)
buffer = (char *)ikcp_malloc((mtu + IKCP_OVERHEAD) * 3);
if (buffer == NULL)
return -2; // 内存分配失败
// 更新MTU和MSS(MSS = MTU - 头部开销)
kcp->mtu = mtu;
kcp->mss = kcp->mtu - IKCP_OVERHEAD;
// 释放旧缓冲区
ikcp_free(kcp->buffer);
kcp->buffer = buffer;
return 0; // 成功
}
//---------------------------------------------------------------------
// 设置刷新间隔(毫秒)
// 参数interval:新的间隔值
// 返回值:0=成功
//---------------------------------------------------------------------
int ikcp_interval(ikcpcb *kcp, int interval)
{
// 限制间隔范围(10-5000毫秒)
if (interval > 5000)
interval = 5000;
else if (interval < 10)
interval = 10;
kcp->interval = interval;
return 0;
}
//---------------------------------------------------------------------
// 设置无延迟参数(KCP核心优化参数,影响延迟和可靠性)
// 参数nodelay:是否启用无延迟模式(0=关闭,1=开启)
// 参数interval:刷新间隔(毫秒)
// 参数resend:快速重传阈值(被跳过的ACK数量)
// 参数nc:是否禁用拥塞控制(0=启用,1=禁用)
// 返回值:0=成功
//---------------------------------------------------------------------
int ikcp_nodelay(ikcpcb *kcp, int nodelay, int interval, int resend, int nc)
{
if (nodelay >= 0)
{
kcp->nodelay = nodelay;
// 根据无延迟模式设置最小RTO(无延迟模式允许更小的RTO)
if (nodelay)
{
kcp->rx_minrto = IKCP_RTO_NDL; // 30ms
}
else
{
kcp->rx_minrto = IKCP_RTO_MIN; // 100ms
}
}
if (interval >= 0)
{ // 设置刷新间隔
if (interval > 5000)
interval = 5000;
else if (interval < 10)
interval = 10;
kcp->interval = interval;
}
if (resend >= 0)
{ // 设置快速重传阈值(0=禁用快速重传)
kcp->fastresend = resend;
}
if (nc >= 0)
{ // 设置是否禁用拥塞控制
kcp->nocwnd = nc;
}
return 0;
}
//---------------------------------------------------------------------
// 设置发送和接收窗口大小
// 参数sndwnd:发送窗口大小;rcvwnd:接收窗口大小
// 返回值:0=成功
//---------------------------------------------------------------------
int ikcp_wndsize(ikcpcb *kcp, int sndwnd, int rcvwnd)
{
if (kcp)
{
if (sndwnd > 0)
{
kcp->snd_wnd = sndwnd; // 更新发送窗口
}
if (rcvwnd > 0)
{ // 接收窗口不能小于默认值(需容纳最大分片数)
kcp->rcv_wnd = _imax_(rcvwnd, IKCP_WND_RCV);
}
}
return 0;
}
//---------------------------------------------------------------------
// 获取等待发送的数据包数量(发送缓冲区+待发送队列)
// 返回值:等待发送的数据包总数
//---------------------------------------------------------------------
int ikcp_waitsnd(const ikcpcb *kcp)
{
return kcp->nsnd_buf + kcp->nsnd_que;
}
//---------------------------------------------------------------------
// 从数据包中解析会话ID(conv)
// 参数ptr:数据包指针
// 返回值:会话ID
//---------------------------------------------------------------------
IUINT32 ikcp_getconv(const void *ptr)
{
IUINT32 conv;
ikcp_decode32u((const char *)ptr, &conv); // 解码头部第一个32位整数(conv)
return conv;
}
// 本次注释强化了以下细节:
// 每个常量的设计意图(如IKCP_RTO_NDL为何设为 30ms,IKCP_MTU_DEF为何是 1400 字节);
// 核心流程的分步解释(如ikcp_flush中数据发送的 4 个阶段:ACK 发送、窗口探测、数据转移、重传处理);
// 协议机制的背景知识(如 RTT 计算的权重来源、拥塞控制中慢启动与拥塞避免的区别);
// 数据结构交互关系(如 4 个队列snd_queue→snd_buf→rcv_buf→rcv_queue的数据流动路径);
// 边界条件处理的原因(如为何ikcp_parse_ack要检查序号是否在未确认区间内)