CS144 Lab 2
上一个实验:Lab 1
这篇文章会讲解 CS144 Lab 2 的思路和代码。
Lab 2
这个 Lab 的目标是写一个 TCP receiver,它支持三个功能:
- 可以接受一个
TCPSegment
,并把其中的数据传给上一个 Lab 实现的 StreamReassembler; - 支持查询
ackno
,也就是被排列好的最后一个字符的序数 +1; - 支持查询
window_size
,也就是滑动窗口里剩下的空间。
在 git merge origin/lab2-startercode
合并 Lab 2 的代码后,我们就可以开始啦。
Translating between 64-bit indexes and 32-bit seqnos
为了达成这三个目的,我们需要先弄清楚这三个序数的定义:
- Sequence Numbers, 记为 SN
- Absolute Sequence Numbers,记为 ASN
- Stream Indices,记为 SI
这三个数的定义在原文档我就不复制了,简单说一下他们的关系:
- SN = (ASN + ISN) & (0xFFFFFFFF) (取最低32位)
- SI = ASN - 1
我们还需要一个把 WrappingInt32
转换为 uint54_t
的转换函数:
uint64_t wrappingInt32_to_uint64(WrappingInt32 n) {
return static_cast<uint64_t>(n.raw_value());
}
在它的帮助下,我们可以写出 wrap
函数:
WrappingInt32 wrap(uint64_t n, WrappingInt32 isn) {
uint64_t res = n + wrappingInt32_to_uint64(isn);
res &= 0xFFFFFFFF;
return WrappingInt32{static_cast<uint32_t>(res)};
}
unwrap
函数稍微复杂一些。由于 CS144 lab 都是有测试例的,所以即使一些方法可以帮助我们完成主要目标(指实现 TCPReceiver
),但想完成 Lab 的话不能绕过这些测试例。比如这里,其实我们可以返回比 checkpoint
大的最小满足条件的数,但是由于 Lab 已经规定好了 unwrap
是寻找最接近的数,因此还是得按要求来。参考代码如下:
uint64_t unwrap(WrappingInt32 n, WrappingInt32 isn, uint64_t checkpoint) {
uint64_t res = wrappingInt32_to_uint64(n) - wrappingInt32_to_uint64(isn);
res &= 0xFFFFFFFF;
uint64_t first_part = checkpoint & 0xFFFFFFFF00000000;
res |= first_part;
uint64_t alternative = res;
if (res < checkpoint) {
alternative = res + (1ul << 32);
return alternative - checkpoint >= checkpoint - res ? res : alternative;
} else if (res > checkpoint) {
if (res < (1ul << 32))
return res;
alternative = res - (1ul << 32);
return checkpoint - alternative >= res - checkpoint ? res : alternative;
}
return res;
}
这部分可以在 build
目录下编译并测试,和之前类似:
$ CXX=g++-8 cmake ..
$ make format
$ make -j4
$ ctest -R wrap
通过的话会得到类似字样:
100% tests passed, 0 tests failed out of 4
Total Test time (real) = 0.14 sec
在这些函数的帮助下,我们可以开始实现 TCPreceiver
了。
Implementing the TCP receiver
正如同文档所说,这个 Lab 没有什么需要构造的数据结构,但是我们需要对很多数值的定义小心。
由于 ISN 指代的是 SYN 的 SN,所以除了第一个带有 SYN 的 TCPsegment
以外,在我们计算其他的 TCPSegment
的 payload 对应的 SI 时,都需要减一。另外需要注意的是如果没有收到 SYN 信号的话,我们应该抛弃所有的 TCPSegment
,因为这个时候我们的 TCPReceiver
还没准备好接受分段,这里我们维护变量 initFlag
来记载是否接收到了 SYN
。
参考代码:
void TCPReceiver::segment_received(const TCPSegment &seg) {
if (seg.header().syn) {
// initialize ISN
ISN = seg.header().seqno;
initFlag = true;
} else if (!initFlag) {
return;
}
size_t stream_idx = unwrap(seg.header().seqno, ISN, _reassembler.stream_out().bytes_written());
if (!seg.header().syn)
stream_idx -= 1;
_reassembler.push_substring(seg.payload().copy(), stream_idx, seg.header().fin);
}
optional<WrappingInt32> TCPReceiver::ackno() const {
size_t abs_idx = _reassembler.stream_out().bytes_written();
if (!initFlag)
return {};
abs_idx++;
if (_reassembler.stream_out().input_ended())
abs_idx++;
return wrap(abs_idx, ISN);
}
size_t TCPReceiver::window_size() const {
return _reassembler.stream_out().remaining_capacity();
}
完成后我们在build
目录下编译并测试,和之前类似:
$ CXX=g++-8 cmake ..
$ make format
$ make -j4
$ make check_lab2
通过的话会得到类似字样:
100% tests passed, 0 tests failed out of 26
Total Test time (real) = 0.82 sec
下一个实验:Lab 3
Let me know what you think of this article on twitter @CreamEggMeat or leave a comment below!