refactor and update writeup for lab3
This commit is contained in:
parent
ea7b9605a4
commit
f600eed6e1
@ -26,47 +26,48 @@ TCPSender::TCPSender(const size_t capacity, const uint16_t retx_timeout, const s
|
|||||||
uint64_t TCPSender::bytes_in_flight() const { return _next_seqno - _unack_seqno; }
|
uint64_t TCPSender::bytes_in_flight() const { return _next_seqno - _unack_seqno; }
|
||||||
|
|
||||||
void TCPSender::fill_window() {
|
void TCPSender::fill_window() {
|
||||||
if (_stream.eof() && _next_seqno - _stream.bytes_read() == 2)
|
|
||||||
return;
|
|
||||||
TCPHeader hdr;
|
|
||||||
hdr.seqno = next_seqno();
|
|
||||||
if (_next_seqno == 0) {
|
|
||||||
hdr.syn = true;
|
|
||||||
}
|
|
||||||
auto _fake_window_size = _window_size ? _window_size : 1UL;
|
auto _fake_window_size = _window_size ? _window_size : 1UL;
|
||||||
auto occupied = _next_seqno - _unack_seqno;
|
auto occupied = _next_seqno - _unack_seqno;
|
||||||
auto data = _stream.read(_fake_window_size - occupied);
|
auto available = _fake_window_size > occupied ? _fake_window_size - occupied : 0;
|
||||||
size_t sendptr = 0;
|
//* judge syn
|
||||||
std::string payload = data.substr(sendptr, TCPConfig::MAX_PAYLOAD_SIZE);
|
auto syn = 0UL;
|
||||||
if (data.size() > TCPConfig::MAX_PAYLOAD_SIZE)
|
if (_next_seqno == 0 && available) {
|
||||||
while (payload.size() == TCPConfig::MAX_PAYLOAD_SIZE) {
|
syn = 1;
|
||||||
TCPSegment seg;
|
available -= syn;
|
||||||
seg.header() = hdr;
|
}
|
||||||
seg.payload() = Buffer(std::move(payload));
|
//* read payload
|
||||||
|
auto payload = _stream.read(available);
|
||||||
|
available -= payload.size();
|
||||||
|
//* judge fin
|
||||||
|
auto fin = 0UL;
|
||||||
|
if (_stream.eof() && available && (_next_seqno < _stream.bytes_read() + 2)) {
|
||||||
|
fin = 1;
|
||||||
|
available -= fin;
|
||||||
|
}
|
||||||
|
//* assemble segments && split payload
|
||||||
|
size_t ptr = 0;
|
||||||
|
do {
|
||||||
|
TCPHeader hdr;
|
||||||
|
TCPSegment seg;
|
||||||
|
if (syn) {
|
||||||
|
hdr.syn = true;
|
||||||
|
syn = 0;
|
||||||
|
}
|
||||||
|
if (fin && ptr + TCPConfig::MAX_PAYLOAD_SIZE >= payload.size()) {
|
||||||
|
hdr.fin = true;
|
||||||
|
}
|
||||||
|
std::string subpayload = payload.substr(ptr, TCPConfig::MAX_PAYLOAD_SIZE);
|
||||||
|
ptr += subpayload.size();
|
||||||
|
hdr.seqno = next_seqno();
|
||||||
|
seg.header() = hdr;
|
||||||
|
seg.payload() = Buffer(std::move(subpayload));
|
||||||
|
if (seg.length_in_sequence_space()) {
|
||||||
_segments_out.push(seg);
|
_segments_out.push(seg);
|
||||||
_outstandings.push(seg);
|
_outstandings.push(seg);
|
||||||
sendptr += TCPConfig::MAX_PAYLOAD_SIZE;
|
|
||||||
_next_seqno += seg.length_in_sequence_space();
|
_next_seqno += seg.length_in_sequence_space();
|
||||||
hdr.syn = false;
|
|
||||||
hdr.seqno = next_seqno();
|
|
||||||
_timer.start();
|
_timer.start();
|
||||||
if (sendptr >= data.size())
|
|
||||||
break;
|
|
||||||
payload = data.substr(sendptr, TCPConfig::MAX_PAYLOAD_SIZE);
|
|
||||||
}
|
}
|
||||||
occupied = _next_seqno - _unack_seqno + payload.size();
|
} while (ptr < payload.size());
|
||||||
if (_stream.eof() && _fake_window_size > occupied) {
|
|
||||||
hdr.fin = true;
|
|
||||||
}
|
|
||||||
TCPSegment seg;
|
|
||||||
seg.header() = hdr;
|
|
||||||
seg.payload() = Buffer(std::move(payload));
|
|
||||||
if (seg.length_in_sequence_space()) {
|
|
||||||
_segments_out.push(seg);
|
|
||||||
_outstandings.push(seg);
|
|
||||||
_next_seqno += seg.length_in_sequence_space();
|
|
||||||
_timer.start();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//! \param ackno The remote receiver's ackno (acknowledgment number)
|
//! \param ackno The remote receiver's ackno (acknowledgment number)
|
||||||
|
|||||||
@ -1,21 +1,76 @@
|
|||||||
Lab 3 Writeup
|
Lab 3 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 `4.5` hours to do.
|
||||||
|
|
||||||
This lab took me about [n] hours to do. I [did/did not] attend the lab session.
|
**Program Structure and Design of the TCPSender:**
|
||||||
|
|
||||||
|
Sender 需要关注两个部分:生成并发送 Segment,接收 ACK 并对状态做出相应的调整。这对应了框架中给出的`fill_window`和`ack_received`这两个函数。除此之外,还需要另外一个计时器来控制超时。
|
||||||
|
|
||||||
|
超时逻辑
|
||||||
|
---
|
||||||
|
按照讲义的顺序,先来理解一下超时的逻辑。比较的反直觉。
|
||||||
|
- 首先明确,在实际的实现中,我们的计时器和理论课上讲的 Go-Back-N 或者是 SR 中的理想化的超时逻辑不一样,这里的计时器只有一个,通过 ACK 来控制回收,而不是每个 packet 或者每个 byte 有一个计时器(虽然或许可以这么做,但是比较消耗资源,写起来也很复杂)。
|
||||||
|
- 涉及计时器的几个状态:
|
||||||
|
1. 发送一个有长度的 Segment,无条件 start 计时器。
|
||||||
|
2. 收到一个ACK:如果这个 ACK 重复上一次或者更久之前的 ACK,那么不管他;如果这个 ACK 表示新的 Segment 被收到
|
||||||
|
1. 将计时器 reset 为全局初始值,重传计数清空
|
||||||
|
2. 如果还有 outstanding data,start 计数器
|
||||||
|
3. 如果没有 outstanding data,stop 计数器(应该是不需要 reset 的)
|
||||||
|
不过这里有一个问题:如果这个 ACK 是半个 packet 会咋样?讲义上没有明确,但是按照其他地方的描述,可以当作不是新的 ACK 来处理。
|
||||||
|
4. 时钟 tick:首先判断是否启动;然后减少计时器;再判断是否超时,如果超时:
|
||||||
|
1. 重传 outstanding 队列里面的第一个 segment。
|
||||||
|
2. 如果此时的 window 不为0,增加重传计数,加倍初始值
|
||||||
|
3. 局部 reset 计数器
|
||||||
|
4. start 计数器
|
||||||
|
|
||||||
|
这样就可以分析出计时器需要的一些接口和状态:
|
||||||
|
- 状态:
|
||||||
|
`is_start` 计时器启动状态
|
||||||
|
`cr_count` 重传计数
|
||||||
|
`ti_count` 超时计数
|
||||||
|
`RTO` 局部初始值
|
||||||
|
- 接口:
|
||||||
|
`start()` `stop()` 启动停止计时器
|
||||||
|
`reset()` `reset_init(size_t RTO)` 重置计时器(仅重置 `ti_count` 和 `is_start`)| 全部重置
|
||||||
|
`retransmit()` 增加重传计数并加倍RTO
|
||||||
|
`tick(size_t ms)` 减少计时器,最少减少到0,单位就是外面 tick 的单位
|
||||||
|
- 包装器:
|
||||||
|
`is_started()`
|
||||||
|
`consecutive_retransmissions()`
|
||||||
|
`expired()`
|
||||||
|
|
||||||
|
发送逻辑
|
||||||
|
---
|
||||||
|
最基本的发送操作,就是把一个 TCPSegment 塞进 `_segment_out` 这个队列里面,然后由外部的调用者来负责交给下层网络。
|
||||||
|
|
||||||
|
`fill_window` 是最主要的发送函数,他的功能可以描述为:尽可能地发送数据,除非字节流空或者 window 不允许。此外,还需要维护 SYN、FIN 以及分段。
|
||||||
|
|
||||||
|
主要的问题在于分段,因为标志位占用空间。
|
||||||
|
|
||||||
|
先不考虑分段,那么发送过程应该是这样的:
|
||||||
|
0. 首先计算可发送数据大小,也就是 `window_size - (_next_seqno - _unack_seqno)`
|
||||||
|
1. 准备需要发送的数据
|
||||||
|
1. 首先判断是否需要发 SYN (判断 `_next_seqno`是不是0)
|
||||||
|
2. 然后从字节流读取所有的可发送数据。(即使读出来的比要求的要少也无所谓。)
|
||||||
|
3. 再判断是否需要发 FIN (判断:EOF、窗口允许、是否已经发过(`_next_seqno < bytes_read + 2`))
|
||||||
|
2. 组装数据为 TCPSegment
|
||||||
|
3. 如果组装出来的队列在序列空间内有长度,那么塞进发送队列里,增加`_next_seqno`;同时单独维护内部的 outstanding 队列(不能用发送队列,因为爱会消失)并启动计时器。否则就不管。
|
||||||
|
|
||||||
|
然后考虑分段的问题:
|
||||||
|
这里用一个循环,每次取原始 payload 的一个 `MAX_PAYLOAD_SIZE` 长度的 `substr`,然后用指针记一下取到哪里了,这里已经不需要考虑窗口了,因为数据准备阶段都已经计算好了。这时候只需要判断是否为第一个和最后一个来附加可能的 SYN 和 FIN 就行了。
|
||||||
|
|
||||||
Program Structure and Design of the TCPSender:
|
|
||||||
[]
|
|
||||||
|
|
||||||
Implementation Challenges:
|
Implementation Challenges:
|
||||||
[]
|
|
||||||
|
把思路理清楚就好了,这个里面的 corner case 不是很离谱,主要的 bug 还是无符号计算溢出,**减法一定要注意啊**。
|
||||||
|
最离谱的反而是如何组装一个 TCPSegment,一开始用 parse 方法,结果发现因为 header 不全直接失败。然后才发现它的包装器返回的是一个,引用?!!直接赋值即可。
|
||||||
|
|
||||||
Remaining Bugs:
|
Remaining Bugs:
|
||||||
[]
|
[]
|
||||||
@ -26,4 +81,4 @@ Remaining Bugs:
|
|||||||
|
|
||||||
- Optional: I was surprised by: [describe]
|
- Optional: I was surprised by: [describe]
|
||||||
|
|
||||||
- Optional: I'm not sure about: [describe]
|
- Optional: I'm not sure about: [这,说实在我都不知道我的代码是怎么通过测试的]
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user