CS144-Lab3-TCPSender

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 中提供的狀態說明圖進行實現:

image

理論上只有 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();
	}
}

3. 測試

image

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章