Linux C/C++ 学习日记(26):KCP协议(二):kcp源码分享

注:该文用于个人学习记录和知识交流,如有不足,欢迎指点。

这篇文章分享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要检查序号是否在未确认区间内)
相关推荐
给大佬递杯卡布奇诺3 小时前
FFmpeg 基本API avformat_open_input函数内部调用流程分析
c++·ffmpeg·音视频
刻BITTER3 小时前
用CMake 实现U8g2 的 SDL2 模拟环境
c++·stm32·单片机·嵌入式硬件·arduino
雾岛听蓝3 小时前
深入解析内存中的整数与浮点数存储
c语言·经验分享·笔记·visualstudio
三贝勒文子3 小时前
C++ 多线程实战 14|如何系统性避免死锁
c++
Yupureki4 小时前
从零开始的C++学习生活 9:stack_queue的入门使用和模板进阶
c语言·数据结构·c++·学习·visual studio
远远远远子4 小时前
C++-- 内存管理
c++·算法
一念&4 小时前
每日一个C语言知识:C 数组
c语言·开发语言·算法
小年糕是糕手4 小时前
【数据结构】单链表“0”基础知识讲解 + 实战演练
c语言·开发语言·数据结构·c++·学习·算法·链表
疯狂吧小飞牛4 小时前
Lua C API 中的 lua_rawseti 与 lua_rawgeti 介绍
c语言·开发语言·lua