0.Overview
check1.pdf
check1 要求实现的是 TCP 的流重组器。
大家都知道(不知道的现在也知道了),在网络模型中位于运输层以下的所有数据报传输都只提供“尽最大努力交付”服务(即"best-effort");这意味着一个正常的数据报在传输过程中可能丢失、可能重复发送好几次、也可能出现比特差错。而 TCP 是可靠传输协议,因此要在一个不可靠的传输服务上提供一个可靠的流交付,就必须有一个负责接收并处理所有网络流数据报的组件。
check1 的代码会基于 check0 中已经编写好的
1.Getting started
由于整个 Lab 都是复用代码的,所以需要把分支 check1-startercode merge 一下。
上面说过 Lab 是有代码复用的,因此直接使用最后一个代码分支的代码就可以一次性获得所有的实验内容。(不过在撰写时今年的 cs144 还在进行中,只能等它放完了)
2. Putting substrings in sequence
正如 Overview 中说过的,TCP 需要一个组件将不可靠的数据报交付转换为可靠的字节流传输。文档中提到了 TCP 会为它的数据报中的每个字节提供一个独一无二的序号(序号从 0 开始),这个序号标识了当前字节在一整个数据报中的位置;我们需要按照这个序号将所有字节按顺序组装在一起,并交付给
注意一下这里提到:TCP 会将数据分片为不超过 1460 字节的片段,所以你不需要考虑序号是否会超出底层数据类型能记录的上限。
public 接口中的
2.1 需求分析
文档里提到,实现的
- 接收到流的下一个字节分组,此时要尽快(或者说立刻)将字节推入
ByteStream 中; - 接收到一个缺失了部分字节的字节分组,并且字节分组的大小不超过
ByteStream 的容量,必须将字节分组存储在Reassembler 的缓冲区中; - 接收到了一个大小超过了
ByteStream 的容量的字节分组(超出了处理能力),立即丢弃,因为Reassembler 不存储任何不能立即推入ByteStream 的字节; - 当
Reassembler 获得了缺失的字节分组时,必须立刻将缓冲区中所有能够被推入的字节立即推入(当然推入过程中如果超出了容量,就需要被截断了)。
文档还很贴心地给了个流交付时的示意图。红色表示的是长度在 capacity 容许范围内的、已接收的无序字节分组,绿色表示的是重组后、已经 push 但尚未 pop 的数据,蓝色的就是已经被 pop 的数据。
很显然我们在实现时不能把所有无序的数据全部存在缓冲区(前面已经提过了)。然后建议是多结合一下需求分析,尽量完全理解这个流交付的过程的细节。
2.2 细节事项
文档的 FAQs 部分给出了一些比较细致的行为细节,这里只给出几点比较重要的:
- 对于最终的实现,只要速度超过 0.1 Gbit/s 就是可接受的,并且具体的实现完全取决于你自己的想法(远超 check0 的自由);
- 不需要考虑传给你的 data 是否存在字节差错,这不是 check1 该考虑的;
- 字节交付应该尽快;
insert() 函数要能处理一些重叠的数据分组(指存在部分字节是已经收到过的数据分组);- 在
Reassembler 的缓冲区部分中,所有的数据字节分组都应该是唯一的,(不能重复存储重叠的数据分组,而是只保留一个副本,这里很关键); - 实现时不应该涉及
reader() 的调用。
FAQs 部分还提到了调用
也不知道它这里的 85 行是怎么做到的,可能是单纯我比较菜,我写的比它说明的要多不少。
我实现的
2.3 程序实现
根据前面的分析结果,我们可以得出以下结论:
- 需要记录序号的变化,思路是使用一个
uint64_t 类型的变量记录Reassembler 正在期待的下一字节序号,只要加上推入的字节分组的长度就能不断得到下一个期待的字节序号(其实 TCP 的报文传输中就提到了期待的字节序号这个概念); - 对于超出
ByteStream 容纳能力的数据分组,使用上面提到的变量进行分类,尽快找出应当抛弃的数据分组,并且把缺失数据、未超过处理能力的分组推入缓冲区; - 有序、且唯一地存储缺失数据的字节分组,这部分比较复杂,下面详细讲讲。
从 2.1 给出的处理示意图中,我们可以知道很重要的一点:一个字节分组可以唯一的由区间段
那么我们在插入新数据分组时要做的很简单:找到所有和新数据存在交集的区间段,取并集后把原先的数据删除,把并集保持有序地放回缓冲区。
这个有序存储的缓冲区该怎么实现?首先我们的需求是要做到有序插入,并且还有频繁的遍历需求(找交集再取并,保证存储的无序字节的唯一性);尽管
3.代码测试
ehhh 其实在有序插入部分花了非常多的时间,然后不仅代码行数写了很多、性能还比较堪忧(感觉合理的速度应该和