共计 5474 个字符,预计需要花费 14 分钟才能阅读完成。
CS 144: Introduction to Computer Networking, Fall 2020
https://cs144.github.io/My Repo
https://github.com/wine99/cs1…
总体思路
tick 不须要咱们来调用,参数的意义是间隔上次 tick 被调用过来的工夫,也不须要咱们来设定。咱们只须要在 tick 中实现,通过参数判断过来了多少工夫,须要执行何种操作即可。
留神依据文档,咱们要不须要实现抉择重传,而是相似回退 N,须要存储已发送并且未被确认的段,进行累计确认,超时时只有重传这些段中最早的那一个即可。
TCPReceiver 调用 unwrap 时的 checkpoint 是上一个接管到的报文段的 absolute_seqno,TCPSender 调用 unwrap 时的 checkpoint 是 _next_seqno
。
我的实现中计时器开关的解决:
- 发送新报文段时若计时器未关上,开启
- ack_received() 中,如果有报文段被正确地确认,重置计时器和 RTO,如果所有报文段均被确认(bytes in flight == 0),敞开计时器
- tick() 中,若计时器为敞开状态,间接返回,否则累加计时而后解决超时
增加的成员变量
class TCPSender {
private:
bool _syn_sent = false;
bool _fin_sent = false;
uint64_t _bytes_in_flight = 0;
uint16_t _receiver_window_size = 0;
uint16_t _receiver_free_space = 0;
uint16_t _consecutive_retransmissions = 0;
unsigned int _rto = 0;
unsigned int _time_elapsed = 0;
bool _timer_running = false;
std::queue<TCPSegment> _segments_outstanding{};
// See test code send_window.cc line 113 why the commented code is wrong.
bool ack_valid(uint64_t abs_ackno) {
return abs_ackno <= _next_seqno &&
// abs_ackno >= unwrap(_segments_outstanding.front().header().seqno, _isn, _next_seqno) +
// _segments_outstanding.front().length_in_sequence_space();
abs_ackno >= unwrap(_segments_outstanding.front().header().seqno, _isn, _next_seqno);
}
public:
void send_segment(TCPSegment &seg);
};
send_segment(TCPSegment &seg)
只在fill_window()
中被调用,重传只须要_segments_out.push(_segments_outstanding.front())
_receiver_window_size
保留收到无效(无效的含意见下面ack_valid()
)确认报文段时,报文段携带的接管方窗口大小_receiver_free_space
是在_receiver_window_size
的根底上,再减去已发送的报文段可能占用的空间(_bytes_in_flight
)
fill_window()
实现
- 如果 SYN 未发送,发送而后返回
- 如果 SYN 未被应答,返回
- 如果 FIN 曾经发送,返回
- 如果 _stream 临时没有内容但并没有 EOF,返回
-
如果
_receiver_window_size
不为 0- 当
receiver_free_space
不为 0,尽可能地填充 payload - 如果 _stream 曾经 EOF,且
_receiver_free_space
仍不为 0,填上 FIN(fin 也会占用 _receiver_free_space) - 如果
_receiver_free_space
还不为 0,且 _stream 还有内容,回到步骤 1 持续填充
- 当
-
如果
_receiver_window_size
为 0,则须要发送零窗口探测报文-
如果
_receiver_free_space
为 0- 如果 _stream 曾经 EOF,发送仅携带 FIN 的报文
- 如果 _stream 还有内容,发送仅携带一位数据的报文
- 之所以还须要判断
_receiver_free_space
为 0,是因为这些报文段在此处应该只发送一次,后续的重传由 tick() 函数管制,而当发送了零窗口报文段后_receiver_free_space
的值就会从原来的与_receiver_window_size
相等的 0 变成 -1
-
void TCPSender::fill_window() {if (!_syn_sent) {
_syn_sent = true;
TCPSegment seg;
seg.header().syn = true;
send_segment(seg);
return;
}
if (!_segments_outstanding.empty() && _segments_outstanding.front().header().syn)
return;
if (!_stream.buffer_size() && !_stream.eof())
return;
if (_fin_sent)
return;
if (_receiver_window_size) {while (_receiver_free_space) {
TCPSegment seg;
size_t payload_size = min({_stream.buffer_size(),
static_cast<size_t>(_receiver_free_space),
static_cast<size_t>(TCPConfig::MAX_PAYLOAD_SIZE)});
seg.payload() = _stream.read(payload_size);
if (_stream.eof() && static_cast<size_t>(_receiver_free_space) > payload_size) {seg.header().fin = true;
_fin_sent = true;
}
send_segment(seg);
if (_stream.buffer_empty())
break;
}
} else if (_receiver_free_space == 0) {// The zero-window-detect-segment should only be sent once (retransmition excute by tick function).
// Before it is sent, _receiver_free_space is zero. Then it will be -1.
TCPSegment seg;
if (_stream.eof()) {seg.header().fin = true;
_fin_sent = true;
send_segment(seg);
} else if (!_stream.buffer_empty()) {seg.payload() = _stream.read(1);
send_segment(seg);
}
}
}
void TCPSender::send_segment(TCPSegment &seg) {seg.header().seqno = wrap(_next_seqno, _isn);
_next_seqno += seg.length_in_sequence_space();
_bytes_in_flight += seg.length_in_sequence_space();
if (_syn_sent)
_receiver_free_space -= seg.length_in_sequence_space();
_segments_out.push(seg);
_segments_outstanding.push(seg);
if (!_timer_running) {
_timer_running = true;
_time_elapsed = 0;
}
}
ack_received()
实现
代码比拟直白,留神进行累计确认之后,如果还有未被确认的报文段,_receiver_free_space
的值应为:收到的确认号绝对值 + 窗口大小 – 首个未确认报文的序号绝对值 – 未确认报文段的长度总和。
void TCPSender::ack_received(const WrappingInt32 ackno, const uint16_t window_size) {uint64_t abs_ackno = unwrap(ackno, _isn, _next_seqno);
if (!ack_valid(abs_ackno)) {
// cout << "invalid ackno!\n";
return;
}
_receiver_window_size = window_size;
_receiver_free_space = window_size;
while (!_segments_outstanding.empty()) {TCPSegment seg = _segments_outstanding.front();
if (unwrap(seg.header().seqno, _isn, _next_seqno) + seg.length_in_sequence_space() <= abs_ackno) {_bytes_in_flight -= seg.length_in_sequence_space();
_segments_outstanding.pop();
// Do not do the following operations outside while loop.
// Because if the ack is not corresponding to any segment in the segment_outstanding,
// we should not restart the timer.
_time_elapsed = 0;
_rto = _initial_retransmission_timeout;
_consecutive_retransmissions = 0;
} else {break;}
}
if (!_segments_outstanding.empty()) {
_receiver_free_space = static_cast<uint16_t>(abs_ackno + static_cast<uint64_t>(window_size) -
unwrap(_segments_outstanding.front().header().seqno, _isn, _next_seqno) - _bytes_in_flight);
}
if (!_bytes_in_flight)
_timer_running = false;
// Note that test code will call it again.
fill_window();}
tick()
实现
留神,窗口大小为 0 时不须要减少 RTO。然而发送 SYN 时,窗口为初始值也为 0,而 SYN 超时是须要减少 RTO 的。
void TCPSender::tick(const size_t ms_since_last_tick) {if (!_timer_running)
return;
_time_elapsed += ms_since_last_tick;
if (_time_elapsed >= _rto) {_segments_out.push(_segments_outstanding.front());
if (_receiver_window_size || _segments_outstanding.front().header().syn) {
++_consecutive_retransmissions;
_rto <<= 1;
}
_time_elapsed = 0;
}
}
其余代码
#include <algorithm>
TCPSender::TCPSender(const size_t capacity, const uint16_t retx_timeout, const std::optional<WrappingInt32> fixed_isn)
: _isn(fixed_isn.value_or(WrappingInt32{random_device()()}))
, _initial_retransmission_timeout{retx_timeout}
, _stream(capacity)
, _rto{retx_timeout} {}
uint64_t TCPSender::bytes_in_flight() const { return _bytes_in_flight;}
unsigned int TCPSender::consecutive_retransmissions() const { return _consecutive_retransmissions;}
void TCPSender::send_empty_segment() {
TCPSegment seg;
seg.header().seqno = wrap(_next_seqno, _isn);
_segments_out.push(seg);
}
正文完