CS144 Lab 2
Lab Checkpoint 2: the TCP receiver
The TCP Receiver
In this lab, we implement the TCP Receiver. The TCPReceiver
receives messages from the peer’s sender (via the receive()
method) and turns them into calls to a Reassembler
, which eventually writes to the incoming ByteStream
. Applications read from this ByteStream
.
Meanwhile, the TCPReceiver
also generates messages that go back to the peer’s sender, via the send() method. These “receiver messages” tell the sender:
- the index of the “first unassembled” byte, which is called the “acknowledgment number” or “
ackno
.” This is the first byte that the receiver needs from the sender. - the available capacity in the output
ByteStream
. This is called the “window size”.
Together, the ackno
and window size describe describes the receiver’s window: a range of indexes that the TCP sender is allowed to send.
Translating between 64-bit indexes and 32-bit seqnos
In the TCP headers, space is precious, so each byte’s index in the stream is represented not with a 64-bit index but with a 32-bit “sequence number,” or “
seqno
.”
在这一部分,我们需要实现一个Wrap32类中的wrap和unwrap方法来实现64位的absolute sequence number和32位的sequence number之间的转换。
其中,wrap方法负责将一个64位的absolute sequence number转换为一个32位的sequence number,比较简单,只需要一行代码。
unwrap方法负责将一个32位的sequence number转换为64位的absolute sequence number。这里稍微麻烦一点的地方在于一个32位的sequence number理论上可以转换为无穷多个以为间隔的absolute sequence number,需要通过给定的checkpoint来定位我们需要的absolute sequence number。
因此,我们的问题是:
如何找到最接近checkpoint的absolute sequence number?
首先我们要获得这个32位的sequence number与ISN的offset,这个offset将会是最小可能的absolute sequence number,然后每增加就又会得到一个可能的absolute sequence number。
我第一个想到的方法是用一个while循环,每次循环增加,直到找到那个刚好小于checkpoint和刚好大于checkpoint的两个相邻的absolute sequence number,比较并返回那个更接近checkpoint的值。但是这样的话如果checkpoint很大循环次数将会很多很浪费时间,实测这样实现的话test会超时,需要更换思路。
其实解决方案很简单,思考如何根据checkpoint直接定位到相邻可能的absolute sequence number?
最后要注意,测试前可能需要将debug
行注释掉否则可能会超时(至少我的情况是这样)。
Implementing the TCP receiver
在这一部分,我们需要实现TCPReceiver
类,这个类需要一个receive方法来接收TCPSenderMessages
,并将其中的payload交给Reasssembler
;还需要一个send方法来acknowledge收到的message,发送TCPReceiverMessages
。
为什么在
Reassembler
前面需要一个TCPReceiver
类,不能直接将接收到的segment交给Reassembler
?
由TCPSenderMessage
类:
1 |
|
可以看出TCP segment不仅仅包含data(payload),还有seqno
和SYN,FIN,RST flag。
TCPReceiver
的receive方法需要负责:
一句话概括:分析接收到的TCP segment(TCPSenderMessage
)后,将payload交由Reassembler
处理。
具体来说:
-
根据RST flag判断是否有error。
-
根据SYN和FIN flag判断是byte stream的起始segment还是终止segment:如果是起始segment,那么
TCPReceiver
需要记录下ISN(Initial Sequence Number),并且通知Reassembler
我们已经接收到byte stream的起始segment(这是为了让Reassembler
从index 1开始接收substring,因为index 0需要保留给SYN flag);如果是终止segment,TCPReceiver
需要先在自己内部记录下来已经接收到了FIN flag,等到所有存储在Reassembler
中的substrings全部push到byte stream之后(也就是当ByteStream
的写入端关闭之后),再通知Reassembler
我们已经接收到byte stream的终止segment(这是因为当接收到完整的byte stream之后,最后send方法发送的ackno
需要包含最后的FIN flag)。需要注意,虽然SYN和FIN flag本身不属于payload,也不需要push入byte stream,但是他们各需要占据一个sequence number,也就是
Reassembler
的index 0和最后一个index。需要思考需要怎样修改我们之前在Checkpoint1中实现的Reassembler
中的next_byte_index
方法。 -
根据记录下的ISN作为zero point和
Reassembler
在等待的next byte作为checkpoint,运用我们之前实现的Wrap32类将segment中32位的seqno
转化为Reassembler
中从0开始的64位的index(absolute sequence number),并将转化后的index作为first_index,message的payload作为data,交给Reassembler
处理。
TCPReceiver
的send方法需要负责:
一句话概括:根据接收到的TCP segment,发送TCPReceiverMessage
来告知TCP的发送端我们已经已接收到这个segment。
1 |
|
具体来说:
- 如果byte stream有error,设置RST flag为true。
- 设置
ackno
为Reassembler
正在等待的next byte对应的sequence number(用到Wrap32类将64位的next_byte_index(absolute sequence number)转化为32位的sequence number)。 - 设置window_size为
Reassembler
和ByteStream
的available capacity,用来通知TCP的发送端进行flow control。
补充:
以上的步骤都是笔者自己的实现思路,其中对SYN和FIN的处理有些messy,特别是考虑SYN和FIN的sequence number后我修改了Reassembler
获取next_byte_index的方式。并且我用到了Reassembler
中的一些private成员变量和函数,因此在Reassembler
中将TCPReceiver
声明为了友元,破坏了一定的封装性。但无伤大雅,最终能work,读者可梳理思路自行寻求更优的实现。
根据cs1441官方的spec,TCPReceiver
的代码量只在15行左右,但要完全考虑到tests中的所有case实现起来还是有些tricky,笔者实现过程中遇到问题的test包括recv_connect, recv_close, recv_special
,供读者参考。
Finish!
All tests in check2 pass!