lab 地址 :lab3-doc
代碼實現:lab3-code
完整目錄:
0. ByteStream
1. StreamReassembler
2. TCPReceiver
3. TCPSender
4. TCPConnection
5. ARP
6. IP-Router
1. 目標
lab2 實現完 TCPReceiver
後,需要實現 TCPSender
,相對於前者,Sender 要實現的功能相對更復雜一些,需要支持:
- 根據 Sender 當前的狀態對可發送窗口進行填充,發包
- Sender 需要根據對方通知的窗口大小和 ackno 來確認對方當前收到的字節流進度
- 需要支持超時重傳機制,根據時間變化(RTO),定時重傳那些還沒有 ack 的報文
2. 實現
2.1 窗口填充
這裏主要根據 lab 中提供的狀態說明圖進行實現:
理論上只有 3 種情況需要處理:
- 初始化,需要發送 SYN 包,即 CLOSED 狀態,此刻需要填充 Syn 包
- ByteStream 中還有數據可寫入發送,且對方窗口大小足夠,此時正常按照 payload 大小限制填充數據包
- ByteStream 已經 eof,但是 Fin 包還未發送,此刻需要填充 Fin 包
爲了配合重傳機制的實現,當填充成功一個數據包的同時,也需要對其進行緩存備份,代碼如下:
void TCPSender::fill_window() {
// CLOSED
if (next_seqno_absolute() == 0) {
// need to send the syn
TCPSegment seg;
seg.header().syn = true;
seg.header().seqno = _isn;
_segments_out.push(seg);
_next_seqno += seg.length_in_sequence_space();
_bytes_in_flight += seg.length_in_sequence_space();
// cache the segments
_cache_segment(next_seqno_absolute() , seg);
}
// SYN_ACKED
if (not stream_in().eof() && next_seqno_absolute() > bytes_in_flight()) {
// if windows size equal zero , the fill window method should act like the window size is one
size_t max_seg_size = TCPConfig::MAX_PAYLOAD_SIZE;
size_t remaining_wsz = _peer_windows_size ? _peer_windows_size : 1;
size_t flight_size = bytes_in_flight();
if (remaining_wsz < flight_size) {
return;
}
remaining_wsz -= flight_size;
string read_stream = _stream.read(min(remaining_wsz, max_seg_size));
while (!read_stream.empty()) {
TCPSegment seg;
seg.header().seqno = next_seqno();
seg.payload() = Buffer(std::move(read_stream));
remaining_wsz -= seg.length_in_sequence_space();
if (stream_in().eof() && next_seqno_absolute() < stream_in().bytes_written() + 2 && remaining_wsz >= 1) {
seg.header().fin = true;
remaining_wsz -= 1;
}
_next_seqno += seg.length_in_sequence_space();
_bytes_in_flight += seg.length_in_sequence_space();
_segments_out.push(seg);
// cache the seg
_cache_segment(next_seqno_absolute(), seg);
read_stream = _stream.read(min(remaining_wsz, max_seg_size));
}
}
// SYN_ACKED
if (stream_in().eof() && next_seqno_absolute() < stream_in().bytes_written() + 2) {
// stream has reached EOF, but FIN flag hasn't been sent yet. so send the fin
size_t remaining_wsz = _peer_windows_size ? _peer_windows_size : 1;
size_t flight_size = bytes_in_flight();
if (remaining_wsz <= flight_size) {
return;
}
TCPSegment seg;
seg.header().seqno = next_seqno();
seg.header().fin = true;
_next_seqno += seg.length_in_sequence_space();
_bytes_in_flight += seg.length_in_sequence_space();
_segments_out.push(seg);
// cache the segments
_cache_segment(next_seqno_absolute(), seg);
}
}
2.2 定時重傳
每次發送數據包的會對其進行緩存,當超過 RTO 時間沒有收到這個包的確認時,就需要執行重傳,更新等待時間爲 當前 RTO * 2,假如有多個數據包超時,每次只會重傳 seqno 最小的數據包。
//! \param[in] ms_since_last_tick the number of milliseconds since the last call to this method
void TCPSender::tick(const size_t ms_since_last_tick) {
if (_segments_cache.empty()) {
return;
}
_time += ms_since_last_tick;
if (_time >= _initial_retransmission_timeout && _retx_times <= TCPConfig::MAX_RETX_ATTEMPTS) {
_segments_out.push(_segments_cache.begin()->second);
_time = 0;
if (_peer_windows_size != 0) {
_initial_retransmission_timeout *= 2;
_retx_times += 1;
}
}
}
需要注意的是,只有當對方通知的窗口大小不爲 0 的時候,才更新等待時間爲 RTO * 2。
2.3 Ack 確認
Ack 確認的邏輯主要有幾點:
- 如果有有效確認(即緩存的未確認數據包有被成功確認的情況),則需要:
- 更新重傳機制相關變量(重傳時間,重傳次數)
- 刪除確認成功數據包的緩存
- 填充窗口,因爲對端成功確認了數據,對端可用窗口變大了
- 更新對端窗口大小
//! \param ackno The remote receiver's ackno (acknowledgment number)
//! \param window_size The remote receiver's advertised window size
void TCPSender::ack_received(const WrappingInt32 ackno, const uint16_t window_size) {
int left_need_ack_bytes = next_seqno() - ackno;
if (left_need_ack_bytes < 0) {
return;
}
// pop the cache segments;
vector<uint64_t> remove_acknos;
for (auto & seg : _segments_cache) {
uint64_t abs_ackno = unwrap(ackno, _isn, seg.first);
if (seg.first <= abs_ackno) {
remove_acknos.push_back(seg.first);
}
}
for (auto & remove_ackno : remove_acknos) {
_segments_cache.erase(remove_ackno);
}
_peer_windows_size = window_size;
// valid ackno
if (!remove_acknos.empty()) {
_bytes_in_flight = left_need_ack_bytes;
_time = 0;
_retx_times = 0;
_initial_retransmission_timeout = _default_initial_retransmission_timeout;
fill_window();
}
}