> For the complete documentation index, see [llms.txt](https://wandersofb.gitbook.io/blog/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://wandersofb.gitbook.io/blog/cs144/lab3.md).

# lab3

lab3 的难度好大，看文档的第一眼是懵的，TCPSender 怎么这么复杂，它包含了许多 TCP 特性，例如：超时重传、拥塞控制等。导致一开始根本不知道怎么下手，几乎把文档看熟了才有写代码的动力，但是即便写出来也是很多 bug，照着测试 case 改，但是往往是改过这里，那里又通过不了，改了那里，这里又通过不了，最后发现往往是自己在架构的时候出现了问题，需要重构一下。唉，被卡了好几天才通过所有 case，但是代码肯定还是有问题的，等 lab4 再做深刻探讨。

**TCPSender**

TCP sender 说起来简单，就是尽可能的发送 segment，如果发送的 segment 没有被对方确认那么重新传输，直到所有的 segment 都被确认或者由于超时断开连接。或者我们可以更细致的描述一下 TCP sender。

* 跟踪接收方的窗口（处理传入的确认号（**ackno**）和窗口大小（**window size**））
* 尽可能通过读取 `ByteStream`，创建新的 TCP segments（包含 `syn` 和 `fin`，如果需要），并发送它们，直到窗口满了或者 `ByteStream` 空了。
* 追踪那些已经发送但还没确认的段 "outstanding" segments（不知道怎么翻译，暂且叫独立段）
* 如果这些独立段超时了，那么我们重新发送他们。

**几个 tricky 点**

* *如何判断是不是独立段？* 文档说的很详细了，就是实现一个计时器，来掌控时间的流逝，进而判断是否超过了我们规定的时间（RTO）的时候还没有确认这个段。
* *如何确认一个段？* `ack_received` 会确认段并更新我们 sender 的窗口，这个函数会判断 `ackno` 是否符合实际情况，也就是说排除确认了已经确认的段，排除确认了还没发送的段。只要是确认我们 sender 窗口的段都行，甚至如果第一个还未确认的段的部分确认也行，只不过部分确认的话，我们只更新 sender 窗口，不认定整个段确认了。
* *什么是 sender 窗口？* 其实当 `ack_received` 确认段并更新窗口的时候，所谓的这个窗口是 receiver 的适应性窗口，是说 receiver 在这个 `ackno` 下还有多大。而我们的 sender 窗口，在大部分情况下和 receiver 窗口是一致的（确认了完整的段），但是也有特殊情况，就是当确认部分段的情况。画张图会清晰一点。  我们的 sender 窗口会被 `fill_window` 尽可能的填满。而当 `ack_received` 只确认了部分的段的情况下，我们更新 sender 窗口的情况就要小心了，因为它在之前是有负载的，也就是那些未确定的段。要考虑那些已经发送但还没确认完全确认的负载大小。

![](https://1932746554-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FzapZXJESBAYXkYBjTWJz%2Fuploads%2FjPkqh2PsZOZEwIBElZE2%2FIMG_042320220418-153556.png?alt=media\&token=8433eea8-4ec2-451d-b9be-c9ea77899bcd)

* *当 received 窗口为零时怎么办？* 当然我们不能停下我们的发送，如果停下的话且没从 receiver 得到额外的 ack，那么我们就会卡住。TCP 的一个小特性，当 `ack_received` 窗口为零时，那么认定 sender 窗口为 1。进行一个字节的发送（包含 `syn` 和 `fin`）。换句话说，只要我们 `ack_received`，就一定会发送 segments，除非 `ByteStream` 为空或者 fin 已经发送了。

**hint**

* 当 `fill_window` 时要严格按照 absolute\_seqno 顺序。而 `syn` 会是三次握手中的前两次，要独立发送，不携带任何 payload，而 fin 可以携带 payload，也可以不携带，但当 `ByteStream` 为空时且 `eof` 为 true 时，如果我们还没有发送 fin 时，还要发送 fin。
* 如果要发送空 payload，那么不要放入未确认段中，且不要开启 timer。
* window 为零时，我们认定 sender window 为 1。但我们只发送一次空 payload。
* 当未确认段的集合为空且已经发送了 fin，那么关闭 timer，`tick` 无效。

#### 后言

大致的情况就是这些，剩余的没说的文档上都写得很清楚，不会被忽略掉。这个 lab 还是挺难的。等 lab4 完成后再回来补充。
