概述

  • Lab0和Lab1的代码已经完成了大多数的逻辑,Lab2将使用它们,实现一个TCPReceiver,难点在于每个字节的序号
  • TCPReceiver将incoming TCP segments转化为incoming byte stream
    • TCPReceiver通过segment_received()方法从网络层接收segments,调用StreamReassembler的相关方法,最终写入ByteStream
    • 应用程序从这个ByteStream中读取数据
  • TCPReceiver还负责告诉发送方
    • ACK,下一个待接收的起始字节序号
    • 接收窗口大小,“first unassembled”的字节和“first unaccepted”的字节的序号之间的差值
  • ACK和接收窗口大小共同描述了接收窗口,即接收方允许发送方发送的数据范围,实现了发送方的流量控制
    • ACK就是接收窗口区间的左端点(闭)
    • ACK+接收窗口大小就是接收窗口区间的右端点(开)

image.png

编写代码前的准备

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

  1. 确保在此之前已经commit了Lab1的内容,之后就不要再修改webget.cc、libsponge顶层目录下的文件可
  2. 在项目目录下运行git fetch,确保代码是最新的
  3. 运行git merge origin/lab2-startercode
  4. cd build
  5. make -j4
  6. writeups/lab2.md是本实验的checkList,需要提交,请认真填写

    64位index与32位seqnos的转化

  • 之前实现的StreamReassembler使用64位整数表示字节的stream index,从0开始计数
  • 但是TCP头部中,这个序号是32位的,且从一个随机值开始计数(TCP sequence number
    • Your implementation needs to plan for 32-bit integers to wrap around(循环). 2^32字节只有4GB,一旦序列号达到了2^32-1,那么下一个字节的序列号就要从0开始
    • TCP的起始序列号(Initial Sequence Number,ISN)是一个随机值,很难被猜到或重复
      • 这是为了提高安全性,且为了防止和当前通信双方较早前的通信数据产生混淆
    • SYN和FIN代表字节流数据的开始和结束,所以也要占1个序列号
      • SYN和FIN不属于字节流数据的一部分
      • 它们本身也不是字节,它们只是在逻辑上占据了1个序列号
  • 另一个概念“absolute sequence number”:从0开始计数,不循环计数

下面举一个例子,假设字节流数据为“cat”,则上述三种序号如下表所示
image.png
下表总结了这三种序号
image.png

  • 在Absolute Sequence Number和Stream Index之间转换是简单的,只需要简单的+1或者-1
  • 在TCP Sequence Number和Absolute Sequence Number之间转换就比较复杂,很容易写出BUG
    • 我们已经实现了TCP Sequence Number对应的类WrappingInt32,其包装了uint32_t
    • 使用uint64_t代表Absolute Sequence Number
  • WrappingInt32中的转换函数需要你来实现(这一段有点看不懂)

image.png

  • 你可以运行ctest -R wrap来对WrappingInt32进行单元测试

    实现TCPReceiver

    概述

  • TCPReceiver要实现

    • 从对等实体(发送方)接收segments
    • 使用StreamReassembler将segments组装成ByteStream
    • 计算ACK和接收窗口长度,并将这两个数据用一个segment回传给对等实体
  • 首先复习一个TCP segment的格式
    • 下图中高亮的是本实验重点关注部分,蓝色的是由发送方写入,接收方读取并做出反应
      • seqno
      • SYN、FIN
      • Payload
    • 红色的接收方需要写入的

image.png

image.png
image.png
我们已经在.hh文件中提前实现了TCPReceiver的构造函数、unassembled_bytes方法和stream_out methods方法,下面列举了你需要实现的部分

segment_received()

  • 这是运行过程中被调用最频繁的方法:每次接收到新的segment,这个方法就会被调用
  • 这个方法的需要做

    • 设置初始序列号,如果必要的话
      • 有SYN标志的报文段带着ISN
      • 有SYN标志的报文段也可能带有数据,甚至可能有FIN标志
    • 将数据push进StreamReassembler
      • 有FIN标志的报文段的数据的最后1个字节就是这次通信的字节流数据的最后1个字节
      • 这里需要注意TCP sequence number和Stream Index的转换

        ackno()

  • 返回ACK编号,optional

  • 如果ISN还没有被设置,则返回1个空的optional

    window_size()

  • 返回接收窗口大小

    TCPReceiver在整个连接生命周期中的演变

    在整个连接的生命周期中,TCPReceiver会在几个状态中顺序变化

  1. waiting for a SYN(with empty ackno)
  2. in-progress stream
  3. a stream that’s finished

我们会测试你的TCPReceiver正确处理了segments,并且在这些状态中正确的演化,如下图所示(你在Lab4中才需要关注下图中的error state或者RST标志)
image.png

开发与调试建议

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

image.png

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

image.png

提交注意事项

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