add write up
This commit is contained in:
parent
b60b909a19
commit
5a35a07ab0
@ -35,17 +35,20 @@ void TCPConnection::segment_received(const TCPSegment &seg) {
|
||||
}
|
||||
// if the inbound seg has len it should be responded
|
||||
// except that ack_received() has send a seg, so no more is needed
|
||||
if (seg.length_in_sequence_space() && _sender.segments_out().empty()) {
|
||||
// Keep-alive is suggested in RFC1122, but it is optional, though implemented here
|
||||
if ((seg.length_in_sequence_space() ||
|
||||
(_receiver.ackno().has_value() && seg.header().seqno == _receiver.ackno().value() - 1)) &&
|
||||
_sender.segments_out().empty()) {
|
||||
_sender.fill_window(); // it is necessary to repond to SYN with a SYNACK
|
||||
if (_sender.segments_out().empty()) {
|
||||
_sender.send_empty_segment();
|
||||
}
|
||||
}
|
||||
_send_all();
|
||||
// TODO: Keep-alive but it seems to be use
|
||||
// recver in FIN_RECV
|
||||
// sender in SYN_ACKED
|
||||
if (_receiver.stream_out().input_ended() && !_sender.stream_in().eof()) {
|
||||
if (_receiver.stream_out().input_ended() &&
|
||||
(!_sender.stream_in().eof() || _sender.next_seqno_absolute() < _sender.stream_in().bytes_written() + 2)) {
|
||||
_linger_after_streams_finish = false;
|
||||
}
|
||||
}
|
||||
@ -74,8 +77,9 @@ void TCPConnection::tick(const size_t ms_since_last_tick) {
|
||||
// for sender, it is the FIN_ACKED state
|
||||
// for recver, it is the FIN_RECV state
|
||||
// and plus linger
|
||||
if ((!_sender.bytes_in_flight() && _sender.stream_in().eof()) &&
|
||||
(_receiver.stream_out().input_ended() && !_receiver.unassembled_bytes()) &&
|
||||
if ((!_sender.bytes_in_flight() && _sender.stream_in().eof() &&
|
||||
_sender.next_seqno_absolute() == _sender.stream_in().bytes_written() + 2) &&
|
||||
(_receiver.stream_out().input_ended()) &&
|
||||
(!_linger_after_streams_finish || _time_last_segment_received >= 10 * _cfg.rt_timeout)) {
|
||||
_active = false;
|
||||
}
|
||||
|
||||
@ -1,29 +1,63 @@
|
||||
Lab 4 Writeup
|
||||
=============
|
||||
|
||||
My name: [your name here]
|
||||
My name: Catfood
|
||||
|
||||
My SUNet ID: [your sunetid here]
|
||||
My SUNet ID: 998244353
|
||||
|
||||
I collaborated with: [list sunetids here]
|
||||
I collaborated with: An orange cat
|
||||
|
||||
I would like to thank/reward these classmates for their help: [list sunetids here]
|
||||
|
||||
This lab took me about [n] hours to do. I [did/did not] attend the lab session.
|
||||
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: [describe]
|
||||
- Optional: I was surprised by: [1.前面的测试居然有的测不出来; 2.auto 有点不靠谱啊,返回的引用居然直接给老子复制了,必须写 `auto&` 才能得到引用,不然所有的更改都不会反映到内部结构上]
|
||||
|
||||
- Optional: I'm not sure about: [describe]
|
||||
- Optional: I'm not sure about: [Keep Alive 好像可以不管来着]
|
||||
|
||||
Loading…
Reference in New Issue
Block a user