Standford CS144 Lab 2
实验总览
官方文档对实验的描述如下:
In Lab 2, you will implement the
TCPReceiver
, the part of a TCP implementation that handles the incoming byte stream. TheTCPReceiver
translates between incoming TCP segments (the payloads of datagrams carried over the Internet) and the incoming byte stream.The
TCPReceiver
receives segments from the Internet (via thesegment received()
method) and turns them into calls to your StreamReassembler, which eventually writes to the incoming ByteStream. Applications read from this ByteStream, just as you did in Lab 0 by reading from the TCPSocket.
在 Lab2 中,我们要完成 TCP 框架中的TCPReceiver
组件(其中包括我们已经实现的SreamAssembler
和ByteStream
模块)。这个组件的功能有三:
- 接受数据报分片
TCPSegement(the actual datagram payloads)
,并且将其数据报中的数据提取出来,输入到我们上个实验实现的StreamAssembler
中。 - 实现流量控制(flow control),因此我们要不断向
TCPSender
报告滑动窗口(sliding window)大小。 - 通过
segment
的序列号保障数据的可靠传输。
第一部分:64-bits 与 32-bits 序列号间转换
为什么需要做转换
- TCP 报文段的序列号(sequence number)字段的最大长度是 32 字节,而我们的逻辑数据流的每个字节序号(absolute sequence number)的最大长度是 64 字节,所以
sequence number
在达到最大后会重新从开始循环。 - 为了安全考虑,在 TCP 连接的三次握手阶段,
receiver
会随机初始化序号isn
(initial sequence number, 32-bits), 而我们的逻辑流的第一个序号(64-bits)永远是 0。
下表来自文档,表示只包含"cat"三个字节的字节流:
转换的接口
1 | // convert absolute seqno(64-bits) to seqno(32-bits) |
实现时需要注意的是,SYN
和FIN
也是占据一个序列号的,虽然它们不是一个报文段也不是表示 payload 数据的字节,仅表示序列号的起始和结尾!具体的实现参考了CS144 Lab:Lab2 – LRL52 的博客。
第二部分:完善 TCP receiver 的逻辑
如上图所示,TCP receiver
有四种状态:
LISTEN
:初始化receiver
后还未与sender
进行 three-way hand shake,相当与client-server
模型中的server
处于监听状态,监听来自client
的连接请求。此时SYN
初始化序列号还未确定,所以无法进行接发包。SYN_RECV
:处于数据交换阶段,还未接受到包含FIN
字段的数据包,因此连接持续。FIN_RECV
:接受到FIN
的数据包,而且通过重组已经输入到ByteStream
中,数据传输已经完成,不再接受数据报。ERROR
: 错误状态。
测试结果
总结
实验的第一部分涉及绝对序号 (64-bits)和流序号 (32-bits)之间的转换,需要注意的细节还是很多的。第二部分我花了很多时间精力去看tcp_header
和tcp_segment
的接口,搞的很迷糊,最后还是没忍住去参考了网上的实现,发现基本上就没有用到。。我觉得还是自己在这方面的理论没有巩固得特别好,所以这个实验从头到尾都做地磕磕绊绊。
回到 TCP 结构图中看这个实验,我们实现了TCPReceiever
,它将接受从Sender
传来的 TCP 报文段,从中提取负载的报文并输入到之前实现的Reassembler
中。为了实现流量控制,我们还计算了当前滑动窗口的大小,但是并没有报告给Sender
,这会在下一个实验实现。