利用lab0和lab1实现的ByteStream类和StreamReassembler类实现一个TCPReceiverTCPReceiverTCPReceiver,即TCPTCPTCP的接收功能。
该部分主要实现StreamReassembler中64位的序号(从零开始)与TCPTCPTCP头部中32位的序号(从某个随机初始序号开始)的相互转换。
将序号视为一个32位无符号整数(使用自然溢出方法对2的32次方取模)
一个字节流的初始序号对应着SYN标记,末尾序号对应着FIN标记,除去SYN与FIN,中间是真正的字节内容(也就是说SYN与FIN都占有着一个序号,即使报文中的数据部分没有数据)
比如文档中给出的例子(初始序号isn为2^32-2):

其中,
seqno就是TCP报文中的序号字段(32位无符号数)absolute seqno是原字节流加上SYN与FIN之后的序号(从0开始递增)stream index是原字节流的序号(不包括SYN和FIN,从0开始递增)详细的字段描述如下图所示

要求实现以下两个函数:
wrap函数
该函数将absolute seqno转换为seqno,很简单,直接将64位无符号整数截取到32位无符号整数再加上初始序号(isn)即可
unwrap
将给定的seqno转换为与某个给定值checkpoint(是一个absolute seqno)距离最近的一个absolute seqno,这个也不难(但是我很傻搞了一上午)
我的思路如下(可能不太严谨)
令给定的seqno的值为nnn,checkpoint为ckckck,则checkpoint转换为相应的seqno为ck′=(ck%232+isn)%232ck'=(ck \% 2^{32} + isn) \% 2^{32}ck′=(ck%232+isn)%232
假设nnn转换成absolute seqno之后的值为n′n'n′,文档要求(n′n'n′与ckckck之间的间隔)与(nnn与ck′ck'ck′之间的间隔)一致。
那么首先算出nnn与ck′ck'ck′之间的间隔diff=(n−ck′+232)%232diff=(n - ck' + 2^{32})\% 2^{32}diff=(n−ck′+232)%232,diffdiffdiff也会等于(n′−ck+232)%232(n' - ck + 2^{32}) \% 2^{32}(n′−ck+232)%232
即 diff+ckdiff + ckdiff+ck 与 n′n'n′ 在2322^{32}232下同余
就是说 n’ = diff+ck+k∗232diff + ck + k * 2^{32}diff+ck+k∗232(kkk为任意整数)
由于需要求出与ckckck距离最近的一个n′n'n′,于是要么是ck+diffck + diffck+diff要么是ck+diff−232ck + diff - 2^{32}ck+diff−232
还有一个细节就是,ck+diffck + diffck+diff不用考虑溢出问题,但是ck+diff−232ck + diff - 2^{32}ck+diff−232 也就是 ck−(232−diff)ck - (2^{32} - diff)ck−(232−diff)可能会下溢出,此时就只能用ck+diffck + diffck+diff了
参考代码如下:
uint64_t unwrap(WrappingInt32 n, WrappingInt32 isn, uint64_t checkpoint) {const uint32_t diff = static_cast(n - wrap(checkpoint, isn));if (diff < (1U << 31)) return checkpoint + diff;else {uint64_t res = checkpoint - ((1UL << 32) - static_cast(diff));if (res > checkpoint) res = checkpoint + diff;return res;}
}
当收到一个TCPTCPTCP的报文段时,ReceiverReceiverReceiver只关注TCPTCPTCP报文段头部的mermaid sequenceDiagram Number(seqno)、SYN标志位、FIN标志位和Payload数据部分
需要实现的函数有:
segment_received
该函数为接收到一个TCPTCPTCP报文段时,TCPReceiverTCPReceiverTCPReceiver的处理接口
如果当前TCPreceiverTCPreceiverTCPreceiver还未收到一个带有SYN标志的报文,且当前收到的报文中不带SYN标志,则丢弃该报文
当收到的段中带有SYN标志时,需要保存一下initial sequence number
只要已经收到带SYN标志的报文,则需要将数据部分调用StreamReassemblerStreamReassemblerStreamReassembler的push_substring方法将数据放入装配器中
当报文带有FIN标志时,数据部分的最后一个字节即是该字节流的EOF
调用push_substring方法时,需要将seqno转换为stream index(调用unwrap函数然后将结果减一即可);如果当前报文带有SYN标记,数据部分首字节的seqno需要加一;根据文档,unwrap函数的checkpoint选择最后一个reassembled的字节的absolute seqno
ackno
该函数的功能为返回当前的acknowledge number
注意加上SYN和FIN占有的两个字节就可以啦
window_size
返回当前缓存窗口(暂未放入接收窗口的比特范围)大小(容量减去当前接收窗口中的字节数)