CS144计算机网络Lab2: The TCP Receiver
本文最后更新于:2024年1月30日 下午
The arrangement of modules and dataflow in TCP implementation.
- 接收
TCPSegment
; - 调用字节流重组器;
- 向发送方发送相应确认信号;
- 流量控制。
这次的实验主要分为Translating between 64-bit indexes and 32-bit seqnos
和Implementing the TCP receiver
两个部分。前者是学习实现TCPSegment
中的索引方式;后者是实现TCPReceiver
的主要功能。
A. Translating between 64-bit indexes and 32-bit seqnos
这一部分需要实现TCP
中表示索引的方式,在Lab1
中我们实现了StreamReassembler
,其中使用的index
就是一个64-bit
并从0
开始计数的stream index
。然而TCP
的首部空间寸土寸金,直接占用64-bit
为免也太奢侈了,所以在实践中,通常使用32-bit
的sequence number
或seqno
表示。这样一个用低位数据来表示高位数据的方法自然而然地又了了以下几个挑战:
- 当
32-bit
空间用完(到达2^32-1)
后,需要对其进行重置,从0
开始重新计数,即(mod 2^32)
; - 为了避免以前连接中旧数据包造成混淆,在
TPC
连接建立阶段需要随机确定一个32-bit
的初始值即ISN(Initial Sequence Number)
,尽量保证序列号不可被预判或与之前重复; - 为了保证开始信号、字节数据和数据结束信号可靠接收,在
TCP
中,SYN(beginning of stream)
和FIN(end of stream)
分别占用两个序列号,SYN
占用第一个序列号也就是ISN
,数据字节后续的序列号如ISN+1 (mod 2^32)
、ISN+2 (mod 2^32)
等等,最后FIN
占用流后的一个序列号。
在TCP中,索引有以下三种:
An Example of Bitstream "cat".
Three Different Types of Indexing Involved in TCP.
可以看到,absloute seqno与stream index
之间非常好转换,而seqno
与absolute seqno
由于空间大小不一样,转换需要进行额外的操作。这一部分需要做的就是,根据头文件wrapping integers.hh
完成wrapping integers.cc
中的两个转换函数。
Convert absolute seqno → seqno.
对于64-bit
的absolute seqno
,先对它取模,确保在32-bit
范围内,之后加上ISN
再取模,得到32-bit
的seqno
。
1 |
|
Convert seqno → absolute seqno.
对于32-bit
的seqno
,需要找到距离checkpoint最近的absolute seqno,
- 先确定
seqno
与ISN之间的偏移量; - 在数轴上,位于
checkpoint
左侧的为absolute_seqno_left
,右侧的为absolute_seqno_right
,均初始化为absolute_seqno_wrap
; - 不断以
2^32
为单位增长absolute_seqno_wrap
,直到absolute_seqno_wrap > checkpoint
,此时获取到了absolute_seqno_left
与absolute_seqno_right
,选取他们之中距离checkpoint
最近的即为absolute_seqno
。
1 |
|
修改与改进
在测试test4
的时候遇到了问题:
1 |
|
是因为没有考虑absolute_seqno_wrap == checkpoint
的情况,于是增加了特判。除此之外为了防止absolute_seqno_wrap
溢出增加了新的判定在while() {}
中:
1 |
|
然后又跑了一下,在test4
怎么也不出结果,以为有死循环
,但是查了半天逻辑并无错误,看了一眼测试,,,跑1000000
轮,原来只是单纯的慢
罢了。
那么如何改进,分析一下:
前面的方案是通过循环累加确定checkpoint
附近的absolute seqno
,这个流程可以通过取模+除法操作替代,这就意味着计算未接近checkpoint
的32-bit
区间是毫无意义的,我们只需要考虑checkpoint
所在的当前区间以及其前后区间即可。
当前以及其前后32bit区间.
那么这样的话就有以下几种情况:
- 在
checkpoint
与当前区间的absolute seqno
重合时,checkpoint
的位置就是absolute seqno
; - 当
checkpoint
的位置在当前区间中的absolute seqno
的右侧时,会存在两个情况:checkpoint
在最后一个区间中,只有当前区间才有absolute seqno
;checkpoint
不在最后一个区间中,只有在当前区间和其后区间可能存在absolute seqno
;
- 当
checkpoint
的位置在当前区间中的absolute seqno
的左侧时,也会存在两个情况:checkpoint
在第一个区间中,只有当前区间才有absolute seqno
;checkpoint
不在第一个区间中,只有在当前区间和前一个区间可能存在absolute seqno
。
最后通过了测试的代码如下。
1 |
|
B. Implementing the TCP receiver
TCPReceiver
在运行过程中会收到这样格式的TCPSegment
:
TCPSegment.
现在,需要根据tcp_receiver.hh
来实现一下TCPReceiver
:
首先需要增加三个变量:
_isn
:储存存入第一次成功建立连接时的seqno
;_syn
:判断连接是否建立;- _fin:判断连接是否终止。
1 |
|
segment_received
的实现思路是:
- 首先判断
seg.header().syn
是否为true
且当前链接未建立时,设置_isn
为seqno
并建立连接,否则当链接不存在时,直接返回。 - 之后向
reassembler
中推送收到的TCPSegment
中的数据; - 最后当且仅当
seg.header().fin
为true
时,将_fin
记录为终止状态。
ackno
的实现思路是:
- 首先判断链接是否建立,如果为建立则直接返回
nullopt
; - 如果建立了则返回下一个需要进行整合的
seqno
,从absolute seqno
的角度来看即为_reassembler.stream_out().bytes_written() + 1
,同时需要注意syn
与fin
也会分别占用了一个seqno,
1 |
|
在写segment_received
的时候,在recv_specal
这个测试中卡了好久,这里面设计了很多syn
、fin
与数据结合的Segment
,需要特别注意下。