lab3

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

TCPSender

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

  • 跟踪接收方的窗口(处理传入的确认号(ackno)和窗口大小(window size))

  • 尽可能通过读取 ByteStream,创建新的 TCP segments(包含 synfin,如果需要),并发送它们,直到窗口满了或者 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 窗口的情况就要小心了,因为它在之前是有负载的,也就是那些未确定的段。要考虑那些已经发送但还没确认完全确认的负载大小。

  • 当 received 窗口为零时怎么办? 当然我们不能停下我们的发送,如果停下的话且没从 receiver 得到额外的 ack,那么我们就会卡住。TCP 的一个小特性,当 ack_received 窗口为零时,那么认定 sender 窗口为 1。进行一个字节的发送(包含 synfin)。换句话说,只要我们 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 完成后再回来补充。

最后更新于