本实验文档的部分翻译用了翻译软件,所以翻译可能有些绕口
编写代码前的准备
你应该在Lab0~2的Sponge代码库的基础上实现本实验
- 确保在此之前已经commit了Lab2的内容,之后就不要再修改webget.cc、libsponge顶层目录下的文件了
- 在项目目录下运行git fetch,确保代码是最新的
- 运行git merge origin/lab3-startercode
- 我们可能会添加更多的测试用例,所以之后可能还需要运行上2个步骤
- cd build
- make -j4
- writeups/lab3.md是本实验的checkList,需要提交,请认真填写
概述
- 下图展示了TCPSender需要关注的TCP字段
- 蓝色是需要写入的
- 红色是需要读取的

TCPSender的职责包括
您的TCPSender将发送一堆TCPSegments,每个将包含来自outgoing ByteStream的(可能为空)子字符串,并用序列号索引以指示其在流中的位置,并在流的开头标记为SYN标志,在结尾标记为FIN标志。
- TCPSender还必须跟踪outstanding segments,直到它们所占据的序列号被完全确认为止。TCPSender的所有者会定期调用TCPSender的tick方法,以指示时间的流逝。TCPSender负责浏览其未完成的TCPSegments的集合,并确定最旧发送的段是否在没有确认的情况下拖延了太长时间(即,没有确认其所有序号)。如果是这样,则需要重新传输(再次发送)。
- 以下是“outstanding for too long”含义的规则(是RFC 6298的简化)。您将要实现这个逻辑,并且它有点详细。我们将在本周为您提供一些合理的单元测试,并在完成全部TCP实施后在实验4中提供更完整的集成测试。只要您通过这些测试的100%且实施合理,就可以了。
1.每隔几毫秒,您的TCPSender的tick方法将被调用,并带有一个参数,该参数指示自上次调用该方法以来已经过了几毫秒。使用它来保持TCPSender处于活动状态的毫秒总数的概念。请不要尝试从操作系统或CPU调用任何”时间”或”时钟”功能。tick方法是您访问时间的唯一途径。这使事情具有确定性和可测试性。
2.构造TCPSender时,会给它一个参数,告诉它重传超时(Retransmission Timeout,RTO)的”初始值”。RTO是重新发送未完成的TCP段之前要等待的毫秒数。RTO的值将随时间变化,但“初始值”保持不变。初始代码将RTO的“初始值”保存在一个称为_initial_retransmission_timeout的成员变量中。
3.您将实现重传计时器:可以在特定时间启动的警报,并且在RTO过去后,警报将关闭(或“到期”)。我们强调,时间流逝的概念来自调用的tick方法,而不是获取一天中的实际时间。
4·每次发送包含数据的段(序列空间中的长度不为零,空串也是一个合法的数据段)时(无论是第一次还是重传),如果计时器未运行,请启动它运行,以便它将在RTO毫秒后过期(对于RTO的当前值)。
5.所有未确认的数据被确认后,停止重传计时器。
6.如果调用了tick且重传计时器已过期:
(a)重新传输TCP接收器尚未完全确认的最早(最低序列号)段。您需要将outstanding segments存储在一些内部数据结构中,以实现此目的。
(b)如果窗口大小不为零:
i.跟踪连续重传的次数。您的TCPConnection将使用此信息来确定连接是否无望(连续的连续重传太多)并且需要中止。
ii.将RTO的值加倍。这称为“指数退避”,它减慢了糟糕网络上的重传速度,从而避免了进一步加重工作负担。
(c)重置重传计时器并启动它,以使其在RTO毫秒后失效(考虑到您可能刚刚将RTO的值加倍了!)。
7、当接收方给发送方一个确认成功接收新数据的确认号(确认号反映的绝对序列号大于任何先前的确认号)
(a)将RTO设置回其“初始值”。
(b)如果发送方有任何未确认的数据,请重新启动重传计时器,以使其在RTO毫秒后过期(对于RTO的当前值)。
(c)将“连续重传”的计数重置为零。
我们建议在单独的类中实现重传计时器的功能,但这取决于您。如果这样做,请将其添加到现有文件(tcp sender.hh和tcp sender.cc)中
实现TCPSender
现在是时候实现TCPSender接口了。它需要处理四个重要事件,每个事件最终都可能发送一个TCPSegment:
1.**void fill_window()**
- TCPSender被要求从ByteStream读取数据尽可能填充接收窗口;
- 您需要确保发送的每个TCPSegment完全适合接收者的窗口。使每个单独的TCPSegment尽可能大,但不大于
TCPConfig::MAX_PAYLOAD_SIZE(1452字节)给出的值。 - 您可以使用
TCPSegment::sequence_space()方法来计算段所占用的序列号总数。请记住,SYN和FIN标志也分别占据一个序列号,这意味着它们在窗口中占据了空间。 - 如果窗口大小为零该怎么办?如果接收方宣布的窗口大小为零,则填充窗口方法的作用应类似于窗口大小为1。
- 发送方可能最终发送了一个字节,字节被接收方拒绝(但未确认),但是这也可能促使接收方发送新的确认段,在该段中它揭示了在其窗口中打开了更多空间。
- 没有这个,发送者将永远不会得知它被允许重新开始发送。
2.**void ack_received( const WrappingInt32 ackno, const uint16t window-size)**
- 从接收方接收到一个段,传达了新的窗口左边缘(= ackno)和右边缘(= ackno+窗口大小)。TCPSender应该查看其未确认的段的集合,并删除所有已经完全确认的段(ackno大于该段中的所有序列号)。如果接收窗口有了新的空间,TCPSender应该再次填充该窗口。
3.**void tick( const size_t ms-since_last_tick )**
- 自上次调用此方法以来已经过了一定的毫秒数。发送方可能需要重新传输未确认的段。
4.**void send_empty_segment ()**
- TCPSender应可以生成并发送在序列空间中长度为零且序列号设置正确的TCPSegment。如果所有者(您将在下周实现的TCPConnection)希望发送一个空的ACK段,这将很有用。
- 注意:像这样的分段,它不占用任何序列号,不需要被跟踪为“outstanding”,也永远不会被重传。
要完成实验3,请阅读文档:https://cs144.github.io/doc/lab3/class_t_c_p_sender.html,并在tcp sender.hh和tcp sender.cc文件中实现完整的TCPSender公共接口。我们希望您希望添加私有方法和成员变量,以及可能的辅助类。
测试相关理论
- 为了测试您的代码,测试套件将期望它在一系列情况下不断演变——从发送第一个SYN段到发送所有数据,再到发送FIN段,最后使FIN段得到确认。
- 我们认为您不希望创建更多状态变量来跟踪这些“状态”,这些状态已经由TCPSender类已经公开的公共接口定义。
- 为了帮助您理解测试输出,以下是在流的整个生命周期中TCPSender预期演变过程的图表。(在实验4之前,您不必担心错误状态或RST标志。)
FAQS和特殊情况
- 我该如何发送一个“segment”
- Push it on to the
_segments_outqueue. - 你只要认为一旦你将segment放入这个队列,这个队列的owner就会过来取走这个segment (using the public segments_out () accessor method) 然后发送到网络上
- Push it on to the
- 为了跟踪outstanding segment,我需要额外存储它们吗?这会浪费存储空间吗?
- 当你发送一个“segment”时,除了把它放进
_segments_outqueue,还需要一个数据结构来保存它的引用,以便跟踪这个“outstanding segment”来应对可能的重传。这不会浪费存储空间,因为我们只是为它多保存了一个引用。
- 当你发送一个“segment”时,除了把它放进
- 在我收到接收者的ACK之前,我的TCPSender应该假定为接收者的窗口为多大?
- 一个字节
- 如果ackno只确认了一个报文段中的部分数据,我应该怎么做?
- TCPSender可以这么实现,即认为一个报文段中的这一部分数据被确认了,后面仅重传剩下的部分
- 但是我们在实现的时候,可以就将一个报文段视为一个整体,只有当其最后一个字节的序号小于ackno的时候,才认为它被确认了
- 如果我发送了3个单独的报文段“a”、“b”、“c”,且它们都需要重传,那么我可以将它们组合成一个大的报文段从而只重传1个报文段吗?
- TCPSender可以这么实现
- 但是我们在实现的时候,将一个报文段视为一个整体,每次都单独重传
- 我需要把空报文段存储为“outstanding”然后在必要时重传它们吗?
- 需要重传的报文段仅仅是那些序号空间的长度不为0的
- 1个没有占用序号空间的报文段(没有payload、SYN或者FIN)不需要被额外存储,也不会被重传
- 更多的FAQs:https://cs144.github.io/lab_faq.html
开发和调试建议
- 在tcp_sender.cc中实现TCPSender的公有接口,你可以在tcp_sender.hh中添加任何需要的私有成员
- 你可以在编译代码后运行指令make check_lab3进行测试
- 注意git的使用

- 注意代码的可读性
- 使用现代化的C++语法风格
- 如果产生一个segmentation fault
提交注意事项
- 仅修改libsponge顶级目录下的.hh和.cc文件,可以为对象添加私有成员,但不要修改公有接口
- 在提交前,请按顺序运行
- make format
- git status(确保commit了所有的改动)
- make
- make check_lab3
- 编辑writeups/lab3.md,文档中需要包含
- 程序设计结构
- 实现过程中遇到的挑战
- 未解决的BUG
- 提交指南:https://cs144.github.io/submit
