Lab 4 Writeup ============= My name: Catfood My SUNet ID: 998244353 I collaborated with: An orange cat This lab took me about `7` hours to do. Program Structure and Design of the TCPConnection: 基本操作:封装了一些常用的操作,方便在实际逻辑中调用。 - `_send_all`,把所有 Sender 推进发送队列里面的分段发出去。 - `_send_rst`,丢弃所有未发送的 segment,推一个空 segment 并附加 RST 标志位。 - `_set_rst`,设置错误状态,标志整个 TCP 链接终止 实现内容主要有两个部分,一个是链接终止的处理,另一个是组合封装 Sender 和 Receiver。 **组合封装:** 如果不考虑连接终止处理的话,这些操作的写法大概是这样的。 - `connect`:发起连接请求,如果不考虑用户瞎调用的情况,那么直接调用 `fill_window` 就可以了,因为默认的第一个包会生成一个单独的 SYN。 - `write`:写入数据并尽可能发送。直接把数据塞进 sender 的字节流里面,然后 `fill_window + send_all` - `tick`:调用 sender 的 tick,记得要发包,因为 sender 可能有重传 - `segment_received`: - 先把收到的 segment 塞进 receiver 里面; - 然后判断是不是一个 ACK,因为需要通知 sender(这里需要特判是否连接已经建立,不然可能会导致 listen 的时候提前发送 SYN(`ack_received` 会调用 `fill_window`,如果对面没连上就 ACK,那 server 就会主动发 SYN,这显然不对)); - 最后需要判断是否需要发一个响应,两种情况:一种是对面发了数据过来,另一种是对面发了 TCP Keep-Alive。这两种都需要发送至少一个包来更新 ackno 和 win,不过如果前面 sender 有发过包了,就不需要特地加一个空包了。 - 在处理完这一切之后,记得 `send_all`。 - `end_input_stream`:发一个 FIN,直接调用 sender 的字节流的 `end_iuput`,然后记得 `fill & send`。 - 其他的接口就一行,看着名字像就把 sender 和 receiver 的接口转发就好了。 **连接终止:** 这是新加的部分,框架设计者选择把它塞进 Connection 部分实现。 讲义上面的四个前提:1. inbound 全部收到并且对面已经通知我们终止(我们收到了 FIN); 2. outbound 终止并且全部发完(我们发了 FIN 过去); 3. outbound 发过去的东西全部收到了 ACK 4. 本地连接相信对面也满足3(也就是对面也全部收到了我们的 ACK) 这里主要的问题在于,我们不知道对面有没有收到我们发过去的 ACK。如果 ACK 掉了,那么对面就会重传;如果这个时候我们没有跑路,那还可以重新发个 ACK,如果我们跑路了,那对面就只能一直重传直到 RST(这显然是很浪费的)。所以需要我们 linger 一段时间,如果这段时间里面对面没有发重传,那么可以认为对面收到了。这段时间需要足够长,以至于对面能够超时重传。 不需要等待的情况就是#4,我们能够保证对面一定会收到我们所有的ACK,也就是对面发了个 FIN,然而我们还有东西要发,那么对面一定会收到我们捎带的 ACK,否则我们会重传(因为我们的 ACK 有数据啊)。 具体的做法如下: - 增加状态:`_time_since_last_segment_received` 用来记录自从上次的收到 segment 之后过了多久,看看对面有多久没有回信了;`_linger_after_streams_finish` 用来表示是否需要在本地结束之后再等一段时间;`_active` 表示当前连接是否终止,默认是 true(但是其实可以在 `connect` 之后再改成 true,不过好像无所谓?) - 两个相关的接口: - `segment_received` 需要重置 `_time_since_last_segment_received`,判断是否不需要 linger,最重要的、如果收到了对面的 RST、那我们也需要立刻终止 - `tick` 需要更新 `_time_since_last_segment_received`,在 sender tick 过后判断重传计数、如果太多就发个 RST 跑路,最后写一长串条件判断连接 linger。 - 判断条件: - linger 条件:讲义描述的是“inbound 在 outbound EOF 之前结束则置为 false(这里指的是 outbound 没发送 FIN)”,否则默认 linger。这个 false 的条件可以从前面的状态机里面找, `sender=SYN_ACKED|SYN_ACKED(also) && recver=FIN_RECV`。 - 终止条件:首先判断是否满足#1和#3,也就是`sender=FIN_ACKED && recver=FIN_RECV`;然后判断是否需要 linger,如果需要,那么还需要判断 `_time_last_segment_received >= 10 * _cfg.rt_timeout`。 Implementation Challenges: CNM,垃圾测试,前面的代码写错了居然测不出来,老子抓 tshark 抓了半天发现 FIN 发完之后的序列号居然是错的,因为当时根本没测 `send_empty_segment` 的序号到底对不对,然后当时不知道为啥脑子抽了写了个 `_next_seqno - 1` 进去,然后 FIN 的序号就一直少一个,直接把对面给整不会了。然后这个 bug 在两边都是自己写的协议的情况下还是对的,因为两边都认为是这样;直到和 Linux 的协议栈对测的时候才测出来,真的离谱。这个破玩意浪费了我两小时,艹。 Remaining Bugs: 大概是没有了吧,毕竟都能 `webget` 了。 - Optional: I had unexpected difficulty with: [describe] - Optional: I think you could make this lab better by: [describe] - Optional: I was surprised by: [1.前面的测试居然有的测不出来; 2.auto 有点不靠谱啊,返回的引用居然直接给老子复制了,必须写 `auto&` 才能得到引用,不然所有的更改都不会反映到内部结构上] - Optional: I'm not sure about: [Keep Alive 好像可以不管来着]