背景
先介绍下写这篇博客时的背景。无意间就看到了某网站需要懂KCP、UDT的RD,随即投了份简历。因此,这篇博客算是一份对过往知识的梳理,同时也算是一次面试的准备过程。至于我为什么会接触RUDP这块,应该也算是研究生阶段的研究方向。当时刚上研究生的时候,VR、AR还是热门话题,而导师是研究ARQ、QoS出身的,便让我试着用RaptorQ去开发一套视频传输工具去加速大容量视频数据的传输。之后,就顺便学习了市面上一些主流RUDP的技术方案。
这里先罗列下以前学习过的技术方案,以后有机会的我们再慢慢一个一个来梳理;
KCP https://github.com/skywind3000/kcp
UDT http://udt.sourceforge.net/
QUIC Multipath Extension https://github.com/qdeconinck/mp-quic
KCP
相比UDT,KCP我最喜欢的一点就是简洁:一方面体现在使用的语言上;另一方面体现在代码行数上。抛去边边角角的无关紧要的函数,KCP核心代码就集中在4个函数中,分别是ikcp_send(), ikcp_flush(), ikcp_input(), ikcp_recv()四部分。接下来,我们以“一发->一收”这种场景来对代码进行梳理。
值得提的一点是,KCP源码当中的辅助函数名字真的有点“迷”,你不ctrl b跳转进去,真的一下子搞不灵清那个函数到底想干嘛;此外,KCP中代码中充斥着宏链表,有时候还是挺耽搁理解的。因此,在后面的分析梳理过程中,我们能用伪代码的尽量用伪代码吧。
在开始进入具体的实现细节之前,铺垫一下KCP相关的“宏观”知识会更有助于理解。
- 在TCP连接建立完成后发出的每个数据包中,都携带了有效的ACK序号,表示到该序号为止的所有数据包均已经成功接收;对此,KCP采取了类似的思路,在KCP中则是以“una”字段表示;
- 所不同的点在于,KCP中以数据包为基本单位进行编号;而TCP则是以字节byte为基本单位进行编号;
- UNA这种累积确认的方式,在出现“hole”这种情形时并不是十分有效;举例来说,当接收端收到1,3,4,5之后,UNA只能一直ACK 2,而毫无办法(这里其实又牵涉到了"快重传”,这个我们稍后再提);
- 对于“hole”这种情况,TCP后来引入了SACK选择确认,其中每个hole用一个[begin, end)序号对表示,序号对被存放在TCP首部选项中;TCP首部选项的最大长度仅为40字节,因此可以选择确认的"Hole" 个数有限;而在KCP中,有一种专门的数据包,其数据内容是一个数组,数组中的每个元素是要确认数据包的序号;因此,KCP中选择确认采用的单位是“接收到的数据包”,而不是TCP中缺失的“hole”;
- 既然我们刚刚在1中提到了“快重传”