5.7 KiB
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_alltick:调用 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。
- linger 条件:讲义描述的是“inbound 在 outbound EOF 之前结束则置为 false(这里指的是 outbound 没发送 FIN)”,否则默认 linger。这个 false 的条件可以从前面的状态机里面找,
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 好像可以不管来着]