本实验文档的部分翻译用了翻译软件,所以翻译可能有些绕口

编写代码前的准备

你应该在Lab0~2的Sponge代码库的基础上实现本实验

  1. 确保在此之前已经commit了Lab2的内容,之后就不要再修改webget.cc、libsponge顶层目录下的文件了
  2. 在项目目录下运行git fetch,确保代码是最新的
  3. 运行git merge origin/lab3-startercode
  4. 我们可能会添加更多的测试用例,所以之后可能还需要运行上2个步骤
  5. cd build
  6. make -j4
  7. writeups/lab3.md是本实验的checkList,需要提交,请认真填写

    概述

  • 下图展示了TCPSender需要关注的TCP字段
    • 蓝色是需要写入的
    • 红色是需要读取的

image.png

  • TCPSender的职责包括

    • 处理收到的ackno和window size,跟踪接收窗口
    • 不断的发送报文段(包括SYN和FIN)直到ByteStream空或者接收窗口满
    • 持续跟踪那些已经发送但还未收到确认的报文段(称为outstanding segments)
    • 重传超时仍未收到确认的outstanding segments

      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标志。)

image.png

FAQS和特殊情况

  • 我该如何发送一个“segment”
    • Push it on to the _segments_out queue.
    • 你只要认为一旦你将segment放入这个队列,这个队列的owner就会过来取走这个segment (using the public segments_out () accessor method) 然后发送到网络上
  • 为了跟踪outstanding segment,我需要额外存储它们吗?这会浪费存储空间吗?
    • 当你发送一个“segment”时,除了把它放进_segments_out queue,还需要一个数据结构来保存它的引用,以便跟踪这个“outstanding 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

    开发和调试建议

  1. 在tcp_sender.cc中实现TCPSender的公有接口,你可以在tcp_sender.hh中添加任何需要的私有成员
  2. 你可以在编译代码后运行指令make check_lab3进行测试
  3. 注意git的使用

image.png

  1. 注意代码的可读性
  2. 使用现代化的C++语法风格
  3. 如果产生一个segmentation fault

image.png

提交注意事项

  • 仅修改libsponge顶级目录下的.hh和.cc文件,可以为对象添加私有成员,但不要修改公有接口
  • 在提交前,请按顺序运行
    • make format
    • git status(确保commit了所有的改动)
    • make
    • make check_lab3
  • 编辑writeups/lab3.md,文档中需要包含
    • 程序设计结构
    • 实现过程中遇到的挑战
    • 未解决的BUG
  • 提交指南:https://cs144.github.io/submit