CS144 Labs总结

一门 Computer Networking 课程, 总体任务是实现一个 TCP 协议

cs144 github

前言

TCP 相关的 Lab 到这就做完了, 有幸找到了这门对我来说难度适中质量也很高的课程 , 更加深入了 TCP 协议的理解 , 后续的长期目标是 看 RFC 文档和 Linux 协议栈实现,学习工程级别上 TCP 做了哪些优化 , 完成 CMU15-441 课程的TCP Reno 拥塞控制算法

Lab0

一个类 curl 程序

第一个 lab 是一个简单的网页内容获取程序 , 直接调用 Linux 的 TCP 协议栈接口 ,会在之后完成 lab4 时,使用自己写的 TCP 协议替换系统实现

基于内存的可靠字节流

lab0的另一个任务 , 实际上是一个字节流的队列 , 和 Java 里的 stream 设计类似, 一端 writer 写入,一端 reader 读出,写入一端有关闭操作 , 重点还是要理解 eof 的判断

Writer 的接口

// Write a string of bytes into the stream. Write as many
// as will fit, and return the number of bytes written.
size_t write(const std::string &data);
// Returns the number of additional bytes that the stream has space for
size_t remaining_capacity() const;
// Signal that the byte stream has reached its ending
void end_input();
// Indicate that the stream suffered an error
void set_error();

Reader 的接口

// Peek at next "len" bytes of the stream
std::string peek_output(const size_t len) const;
// Remove ``len'' bytes from the buffer
void pop_output(const size_t len);
// Read (i.e., copy and then pop) the next "len" bytes of the stream
std::string read(const size_t len);
bool input_ended() const; // `true` if the stream input has ended
bool eof() const; // `true` if the output has reached the ending
bool error() const; // `true` if the stream has suffered an error
size_t buffer_size() const; // the maximum amount that can currently be peeked/read
bool buffer_empty() const; // `true` if the buffer is empty
size_t bytes_written() const; // Total number of bytes written
size_t bytes_read() const; // Total number of bytes popped

额外添加的 member

private:
    // Your code here -- add private members as necessary.
    size_t capcity,bytesInPipe,bytesReadTotal,bytesWriteTotal;
    bool _ended;
    std::deque<char> bytesPipe;

Lab1

lab0 实现的是 tcp 协议和应用层交互的接口(提供可靠字节流) , 而 lab1 的任务则是将不连续的segment 重组成连续的字节流 , case 很多, 需要仔细阅读提供的测试用例 , 需要解决的主要问题: segment 乱序,overlapping,增量更新, 第二次重写时发现拆解成字节为单位来处理会更简单一些, 直接放代码了..

tcp_reassembler.cc tcp_reassembler.hh

Lab2

难度从这个 lab 开始稳步上升 , lab2 第一个任务是实现一个称为 WrappingInt32 的数据类型 , 在 lab1 时对字节的索引值是使用的 uint64, 正常情况下不容易耗尽, 但是由于 tcp 协议在网络中传输, 还需要考虑到减少网络流量的问题 , 加上 ISN 是随机初始的值 , int32 一定不够用 , 于是设计出了一个"环形"的 int32 , 2^0+17 , 2^1+17 在 WrappinggInt32 的值是相同的 , 但后者是绕了一圈后的 seqno , 如何把它们区分开呢? receiver 会存储一个"最后的 int64 的 seqno"作为 checkpoint, 我们所要做的就是找到离 checkpoint 最近的一个值

我的思路是找到这个 int32 值绕圈后第一次比 checkpoint大的值 , 然后再往回绕一圈找到第一个比 cp 小的值 , 这里遇到了一个 Corner case , 当第一个比 checkpoint 大的值就在第一圈的时候,往回绕成为了负数 , 但实现使用的是 uint64,相减后变成了一个很大的值 , 此时只需要在之前判断减数是否大于被减数即可

image-20191120140931373

第二个任务是实现一个 TCP Receiver , 主要职责是对syn,fin 和 payload 进行接收处理,并"告知"对方 ackno和 window_size (实际上 receiver 不负责发送, 告知的这一步会交给TCPConnection这个上级 处理,receiver 只生成 ackno) . 需要注意一点这个 lab 对上一个 lab 的 reassembler 增加了新的 case , 主要是容量限制 , 如果没有仔细阅读 guide 就开始做(比如我) , 回去fix bug 吧

tcp header 的构成

 uint16_t sport = 0;         //!< source port
    uint16_t dport = 0;         //!< destination port
    WrappingInt32 seqno{0};     //!< sequence number
    WrappingInt32 ackno{0};     //!< ack number
    uint8_t doff = LENGTH / 4;  //!< data offset
    bool urg = false;           //!< urgent flag
    bool ack = false;           //!< ack flag
    bool psh = false;           //!< push flag
    bool rst = false;           //!< rst flag
    bool syn = false;           //!< syn flag
    bool fin = false;           //!< fin flag
    uint16_t win = 0;           //!< window size
    uint16_t cksum = 0;         //!< checksum
    uint16_t uptr = 0;          //!< urgent pointer

Lab3

Lab3 是 TCP Sender 的实现 , 主要职责就是根据 win_size , 从 outbound bytestream 中取出数据,组成 segment 发送 , 耐心做就能做出来 , 一个排查了比较久的 bug 是我在每次超时,触发重传时忘记了判断重传队列为空的情况 , 此时 cpp 的队列front() 方法不会报错,而是 会返回一个默认值 segment , 导致我一直摸不着头脑是在哪里产生的奇怪 seqno 和 doff 值

Lab4

Lab4 是 TCPConnection , 也是本门课最难的一个lab,是 receiver 和 sender 的"上级", 职责是将它们产生的内容组合起来, 真正地发送出去 , 维护整个 TCP 有限状态机的转换 , 还有为了实现 clean shutdown 作出的权衡

Lab5 和 Lab6

粗略看了一下 guide 难度不高,也很有趣 , 会抽空完成

Lab5 是自己完成 IP 包的构成,序列化反序列化, 并通过 network interface 转换成链路层的 Ethernet frame , 也就是要通过 ARP 或者 ip->mac 映射表 , 找到下一跳的 mac 地址

Lab6 是 IP Router , 利用 Lab5 完成的 network interface , 根据 ip prefix length 路由到不同的口上....好吧记不清了