KCP源码走读

news/2024/5/18 15:29:11 标签: linux, c++, udp, 网络协议

前言

KCP作为一个比较轻量级的UDP可靠数据传输协议,在参照TCP拥塞控制的基础上实现了ARQ的功能。走读源码了解启动的原理,对于UDP可靠传输设计具有借鉴意义。

1 整体数据流

KCP整体数据流程由四个缓冲队列来完成,数据在缓冲队列进行迁移:其中队列都是排序的,其中接收队列接收的数据必须是联系的,也就是可靠的。
在这里插入图片描述

2 接收流程

2.1用户层从KCP接收数据:

用户最终读取一帧数据。

int ikcp_recv(ikcpcb *kcp, char *buffer, int len);
{
	//1. 计算一帧数据大小:多个分片包组装,当seg->frg == 0表示包最后一个分片
	int ikcp_peeksize(const ikcpcb *kcp)
	//2. 拷贝完整一帧数据到buffer中
	//3. 检查rcv_buf是否有连续数据可以拷贝到rcv_queue队列、并更新接收序号rcv_nxt
	//4. 接收窗口从满(>=rcv_wnd),到有数据可写,需要通知发送端,利用probe |= IKCP_ASK_TELL设置标志位。
}

2.2 底层接收数据到KCP:

recvfrom从网络读取数据加入到KCP组件中。

int ikcp_input(ikcpcb *kcp, const char *data, long size){
	//1. 获取send_buf,中小于una的序列号,并将数据从发送缓冲区移除。
	static void ikcp_parse_una(ikcpcb *kcp, IUINT32 una)

	//2. 更新下一个需要对方应答的snd_una,序号
	static void ikcp_shrink_buf(ikcpcb *kcp)

	//3. 四种cmd处理
	{
		//3.1 应答包处理:IKCP_CMD_ACK
		//3.1.1 更新kcp->rx_rto:超时重传时间
		ikcp_update_ack(kcp, _itimediff(kcp->current, ts));
		//3.1.2 确认ack,移除send_buf对应的分片
		static void ikcp_parse_ack(ikcpcb *kcp, IUINT32 sn)
		//3.1.3 更新确认下一个需要应答的包snd_una
		static void ikcp_shrink_buf(ikcpcb *kcp)
		
		//3.2 数据包处理:IKCP_CMD_PUSH 
		//3.2.1 只接收接收窗口内数据:[kcp->rcv_nxt + kcp->rcv_wnd]
		//序号和时间戳加入acklist链表,这里有一个扩容的动作
		static void ikcp_ack_push(ikcpcb *kcp, IUINT32 sn, IUINT32 ts)
		//3.2.2 创建分片保存数据
		static IKCPSEG* ikcp_segment_new(ikcpcb *kcp, int size)
		//3.2.3 分片数据经过窗口和查重,按照递增顺序插入队列rcv_buf末尾
		//再次将rcv_buf中符合期望接收序列号rcv_nxt加入rcv_queue队列
		void ikcp_parse_data(ikcpcb *kcp, IKCPSEG *newseg)

		//3.3 窗口探测包处理:IKCP_CMD_WASK
		//设置标记IKCP_ASK_TELL
		kcp->probe |= IKCP_ASK_TELL;  

		//3.4 窗口通知包:IKCP_CMD_WINS
		//不做处理,因为上面解析包已经获取到远端窗口:kcp->rmt_wnd
	}
}

2.2.1 更新RTT和RTO等参数,该算法与TCP保持一致:

/*
> 1. 第一次测量,rx_srtt = rtt,rx_rttval = rtt / 2
> 2. 以后每次测量:
    rx_srtt =(1-a) * rx_srtt + a * rtt,a取值1/8
    rx_rttval= (1-b) * rx_rttval + b * |rtt - rx_srtt|,b取值1/4
    rto = rx_srtt + 4 * rx_rttval
    rx_rto = MIN(MAX(rx_minrto, rto), IKCP_RTO_MAX)
*/
static void ikcp_update_ack(ikcpcb *kcp, IINT32 rtt)

3 发送流程

3.1 用户数据加入KCP

用户数据加入到KCP队列中

int ikcp_send(ikcpcb *kcp, const char *buffer, int len)
{
	//1. 开启流传输模式:kcp->stream,需要将最后一个分片填充到:kcp->mss最大分片大小
	//2. 对buffer数据进行分片;
	//3. 将分片数据加入snd_queue队列,分片采用依次递减方式,一帧数据最后一个分片为0。
}

3.2 KCP发送数据

void ikcp_update(ikcpcb *kcp, IUINT32 current)
{
	//1. 至少10ms的间隔触发一次
	//2. 刷新
	void ikcp_flush(ikcpcb *kcp)
	{
		//1. 计算剩余的窗口
		static int ikcp_wnd_unused(const ikcpcb *kcp)
		//2. 发送ack包
		//2.1 获取确认序号和时间戳
		static void ikcp_ack_get(const ikcpcb *kcp, int p, IUINT32 *sn, IUINT32 *ts)
		//2.2 打包一个分片
		static char *ikcp_encode_seg(char *ptr, const IKCPSEG *seg)
		//2.3 拼接多个ack包,达到kcp->mtu后发送
		static int ikcp_output(ikcpcb *kcp, const void *data, int size)
		{
			//回调到用户函数
			kcp->output
		}

		//3. 窗口为0:kcp->rmt_wnd,7s探测一次,kcp->probe & IKCP_ASK_SEND,发送:IKCP_CMD_WASK包
		//打包:IKCP_CMD_WASK包,拼接多个ack包,达到kcp->mtu后发送
		ptr = ikcp_encode_seg(ptr, &seg);
		//4. 发送数据包:IKCP_CMD_PUSH
		//4.1 将符合要求的数据从snd_queue移动到snd_buf队列
		//4.2 处理重传数据
		//第一次发送:segment->xmit == 0
		//重发时间:current + segment->rto
		//快速重传利用重传次数判断:segment->xmit <= kcp->fastlimit
		//打包:IKCP_CMD_WASK包,拼接多个ack包,达到kcp->mtu后发送
		ikcp_encode_seg
		//如果发生了快速重传,拥塞窗口阈值降低为当前未确认包数量的一半或最小值 
		kcp->ssthresh = inflight / 2;
		//丢包:窗口减半
		kcp->ssthresh = cwnd / 2;
	}
}

http://www.niftyadmin.cn/n/927476.html

相关文章

协程原理与实现-高并发低内存

1 协程的运用场景 在网络高并发的场景下&#xff0c;epoll作为解决方案的首选&#xff0c;基本可以达到C1000K甚至更好的并发量&#xff0c;但是由于需要依靠线程进行异步的回调操作&#xff0c;在一定意义上资源的开销并没有降低&#xff0c;另一方面异步操作带来的问题就是整…

音视频开发-音频基础知识

音视频开发-音频基础知识1 声音基础知识1.1 声音产生1.2 声音的传播1.3 声音的三要素1.4 回声2 数字音频2.1 采样率2.2 采样位数2.3 声道2.4 码率2.5 音频帧2.6 数据存储方式3 专业术语3.1 增益3.2 信噪比3.3 PCM4 音频编码原理1 声音基础知识 1.1 声音产生 声音&#xff1a;…

音视频开发-音频数据处理流程

1 音频处理流程 音频处理流程&#xff0c;可以分为采集端和播放端两部分。这里是为了展示处理的全流程&#xff0c;对应每一个点都是一个技术点后续将展开描述。 2 音频采集流程 采集端将音频模拟信号转换为数字信号&#xff0c;进入音频处理模块&#xff0c;包括音频增益、噪…

Ubuntu16.04编译netmap-终极解决方案

Ubuntu16.04编译netmap-终极解决方案1 前言2 环境准备3 netmap下载4 编译5 网卡名称修改6 测试1 前言 最近想着自己实现用户态网络协议栈&#xff0c;需要依赖netmap获取底层网卡数据&#xff0c;一般这种三方库编译都是很easy的&#xff0c;我也是抱着这个思想结果遇到很多坑…

音视频开发-音频库使用tinyalsa使用

前言 TinyALSA 是一个在 Linux 内核中与 ALSA 接口的小型库。tinyalsa主要是为了解决alsa库过于庞大&#xff0c;同时接口繁杂&#xff0c;不利于用户的使用。tinyalsa主要是alsa内核的用户层音频接口&#xff0c;屏蔽内核的设备操作过程&#xff0c;为用户层提供对于设备的基…

UDP用户态协议栈详细实现

UDP用户态协议栈详细实现1 前言2 网络协议格式2.1 以太网协议2.2 IP协议2.3 UDP协议2.4 ARP协议2.5 ICMP协议3 UDP用户协议栈实现1 前言 首先需要回答一个问题&#xff0c;为什么要学习实现用户态协议栈&#xff0c;从技术角度分析&#xff0c;主要是由于用户态的网络协议栈更…

企业级线程池实现

异步多任务组件-线程池实现1 前言2 线程池的工作原理2.1 结构体定义2.2 线程池处理流程3 线程池实现3.1 线程池创建3.2 线程池销毁3.3 线程池添加任务3.4 线程池woker处理函数3.5 队列操作函数1 前言 线程池作为作为开发过程中的利器&#xff0c;具有的优势也非常明显&#xf…

内存管理-内存池的实现

内存池的实现1 前言2 内存池的原理2.1 内存利用链表进行管理2.2 分配固定大小2.3 按块进行内存管理3 内存池的实现3.1 内存池的创建3.2 内存池的销毁3.3 内存分配3.4 大块内存分配3.5 小块内存分配3.6 内存的对其问题1 前言 内存池出现的意义比较重大&#xff0c;对于服务器这…