Lab3 the TCP sender
0 前言
- 在lab0中,我我们实现了ByteStream这个类似于socket的抽象
- 在lab1中,我们实现了字节重组器,将乱序、重复、丢失的字符串重组成一个字节流。
- 在lab2中,我们将把到来的数据报转换成可靠的字节流。
- 在lab3中,我们实现了整个TCP接收端,用于接收数据,返回ackno()和窗口大小
1 发送方需要做的几件事情
- 将上层(ByteStream)发来的数据切片(写seqno,SYN,FIN,payload)
- 根据接收方回复的ackno和winsize填充数据报
- 发送给网络层,并开始计时
- 记录已发送并且未被收到的报文段
- 每当超时时间到达时,重发上述段
2 几个主要的问题
2.1 发送方怎么知道报文段是否丢失
-
发送端需要缓存所有的未被确认的报文段
-
每过一段时间,调用tick()方法,将上述报文段重发
-
TCPSender创建时会初始化RTO,为初始重传时间
-
每当发送带有数据的报文段,开启定时器,设置RTO
-
当所有帧都被确认后,停止计时器
-
每当计时器到时
-
重传最早未被收到的帧
-
如果窗口大小非0(代表接收端还没有没被接受的数据)
- 增加重传次数,重传次数过多时,可能断掉连接
- 将重传时间翻倍
-
重新开启定时器
-
-
当接收端返回给发送者ackno
- 设置RTO初始RTO
- 如果发送者还有未被确认帧,重设定时器
- 将重传次数清0
2.2 发送端函数主要功能
- void fill_window()
- 从ByteStream中读取数据,以报文段的形式发送了尽可能多的字节,但不超过TCPConfig::MAX_PAYLOAD_SIZE(1452byte)
- 当窗口为0时,发送端仍然要发送一个字节给接收端,表示接收端应该及时的读取数据
- void ack_received(WappingInt32 ackno, const uint16_t window_size)
- 从队列中移除掉已经接受的报文段
- 有空间存在时继续填满窗口
- void tick(const size_t ms_since_last_tick)
- 计时器到时,发送方应该重传未被确认的报文段
- void send_empty_segment()
- 发送一个空字节给接收端,seqno正确,其他不变。
2.3 如何发送一个报文段
- 将报文段推入_segments_out队列中,所有者会调用segments out() 发送至网络层。
2.4 如何在发送一个报文段的同时,同时也可以判断其是否被确认
- 浅拷贝buffer
2.5 在收到ACK前,发送方判断接收方窗口大小是多少
- 1字节
2.6 如果收到的ackno在报文段的中间怎么办?
- 只有完整的报文段才会被接收
2.7 空报文段应该被存储或者重传吗?
- 只有携带数据的报文段才可以被重传
3 tcp发送器
3.1 实现过程中需要注意的点
-
测试程序在调用ack_recevied,close,writebytes时都会间接调用到fill_window(),因此fill_winodw()必须保证一定的健壮性,能够随时接受上层的调用并返回正确的结果。
-
syn,fin在发送时均需要占用窗口的序号,需要保证窗口大于0时才能发送
-
初始状态窗口值置为1
-
如果接收者回复一个0大小的窗口,发送者窗口设为1,以催促接收者尽快读取数据。
-
能够发送的最大数据取决于(_window - _byte_in_flight) 和 (TCP::MAX_PAYLOAD_SIZE)
3.2 具体实现(优化前)
-
tcp_sender.hh
#ifndef SPONGE_LIBSPONGE_TCP_SENDER_HH #define SPONGE_LIBSPONGE_TCP_SENDER_HH #include "byte_stream.hh" #include "tcp_config.hh" #include "tcp_segment.hh" #include "wrapping_integers.hh" #include <functional> #include <queue> //! \brief The "sender" part of a TCP implementation. //! Accepts a ByteStream, divides it up into segments and sends the //! segments, keeps track of which segments are still in-flight, //! maintains the Retransmission Timer, and retransmits in-flight //! segments if the retransmission timer expires. class TCPSender { private: //! our initial sequence number, the number for our SYN. WrappingInt32 _isn; //! outbound queue of segments that the TCPSender wants sent std::queue<TCPSegment> _segments_out{}; std::queue<TCPSegment> _copy_segments_out{}; //! retransmission timer for the connection unsigned int _initial_retransmission_timeout; unsigned int _retransmission_timeout; size_t _cur_time{0}; size_t _bytes_in_flight{0}; unsigned int _consecutive_retransmissions{0}; uint16_t _window_size{0}; bool _clock_running{false}; //! outgoing stream of bytes that have not yet been sent ByteStream _stream; //! the (absolute) sequence number for the next byte to be sent uint64_t _next_seqno{0}; bool _syn{false}; bool _fin{false}; bool _zero{false}; public: //! Initialize a TCPSender TCPSender(const size_t capacity = TCPConfig::DEFAULT_CAPACITY, const uint16_t retx_timeout = TCPConfig::TIMEOUT_DFLT, const std::optional<WrappingInt32> fixed_isn = {}); //! \name "Input" interface for the writer //!@{ ByteStream &stream_in() { return _stream; } const ByteStream &stream_in() const { return _stream; } //!@} //! \name Methods that can cause the TCPSender to send a segment //!@{ //! \brief A new acknowledgment was received void ack_received(const WrappingInt32 ackno, const uint16_t window_size); //! \brief Generate an empty-payload segment (useful for creating empty ACK segments) void send_empty_segment(); //! \brief create and send segments to fill as much of the window as possible void fill_window(); //! \brief Notifies the TCPSender of the passage of time void tick(const size_t ms_since_last_tick); //!@} //! \name Accessors //!@{ //! \brief How many sequence numbers are occupied by segments sent but not yet acknowledged? //! \note count is in "sequence space," i.e. SYN and FIN each count for one byte //! (see TCPSegment::length_in_sequence_space()) size_t bytes_in_flight() const; //! \brief Number of consecutive retransmissions that have occurred in a row unsigned int consecutive_retransmissions() const; //! \brief TCPSegments that the TCPSender has enqueued for transmission. //! \note These must be dequeued and sent by the TCPConnection, //! which will need to fill in the fields that are set by the TCPReceiver //! (ackno and window size) before sending. std::queue<TCPSegment> &segments_out() { return _segments_out; } //!@} //! \name What is the next sequence number? (used for testing) //!@{ //! \brief absolute seqno for the next byte to be sent uint64_t next_seqno_absolute() const { return _next_seqno; } //! \brief relative seqno for the next byte to be sent WrappingInt32 next_seqno() const { return wrap(_next_seqno, _isn); } //!@} }; #endif // SPONGE_LIBSPONGE_TCP_SENDER_HH
-
tcp_sender.cc
#include "tcp_sender.hh" #include "tcp_config.hh" #include <random> // Dummy implementation of a TCP sender // For Lab 3, please replace with a real implementation that passes the // automated checks run by `make check_lab3`. using namespace std; //! \param[in] capacity the capacity of the outgoing byte stream //! \param[in] retx_timeout the initial amount of time to wait before retransmitting the oldest outstanding segment //! \param[in] fixed_isn the Initial Sequence Number to use, if set (otherwise uses a random ISN) 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} , _retransmission_timeout{retx_timeout} , _stream(capacity) {} uint64_t TCPSender::bytes_in_flight() const { return _bytes_in_flight; } void TCPSender::fill_window() { // 先发送一个SYN if (!_syn) { _syn = true; TCPSegment seg_syn; seg_syn.header().syn = true; seg_syn.header().fin = false; seg_syn.header().seqno = _isn; _next_seqno++; _bytes_in_flight += 1; if (!_clock_running) { _clock_running = true; _cur_time = 0; } _segments_out.push(seg_syn); _copy_segments_out.push(seg_syn); return; } // 开始发送数据 size_t send_size = 0; size_t window_size = _window_size; string str = _stream.read(window_size); if (window_size > str.size()) window_size = str.size(); while (send_size < window_size) { TCPSegment seg; size_t sz = std::min(TCPConfig::MAX_PAYLOAD_SIZ_E, window_size - send_size); seg.header().syn = false; seg.header().fin = false; seg.header().seqno = wrap(_next_seqno, _isn); seg.payload() = Buffer(move(str.substr(send_size, sz))); send_size += sz; _window_size -= sz; if (!_clock_running) { _clock_running = true; _cur_time = 0; } // 这里的FIN标志是否有必要放在最后一个报文中 if (!_fin && stream_in().eof() && _window_size > 0) { _fin = true; _window_size--; seg.header().fin = true; } _next_seqno += seg.length_in_sequence_space(); _bytes_in_flight += seg.length_in_sequence_space(); _segments_out.push(seg); _copy_segments_out.push(seg); } // 单独发送fin if (!_fin && stream_in().eof() && _window_size > 0) { _fin = true; TCPSegment seg_fin; seg_fin.header().syn = false; seg_fin.header().fin = true; seg_fin.header().seqno = wrap(_next_seqno, _isn); _window_size--; _next_seqno++; _bytes_in_flight += seg_fin.length_in_sequence_space(); _segments_out.push(seg_fin); _copy_segments_out.push(seg_fin); if (!_clock_running) { _clock_running = true; _cur_time = 0; } return; } } void TCPSender::ack_received(const WrappingInt32 ackno, const uint16_t window_size) { if (next_seqno().raw_value() < ackno.raw_value()) return; bool ok = false; bool valid_ack = false; while (!_copy_segments_out.empty()) { TCPSegment seg = _copy_segments_out.front(); // 一个冗余的ACK if (seg.header().seqno.raw_value() == ackno.raw_value()) valid_ack = true; if (seg.header().seqno.raw_value() + seg.length_in_sequence_space() > ackno.raw_value()) break; ok = true; valid_ack = true; _bytes_in_flight -= seg.length_in_sequence_space(); _copy_segments_out.pop(); _window_size = window_size; } // 如果有多余的空间, fill_window函数会被调用继续填充空间 // 如果所有的报文段都被确定了, 关闭计时器并且重置时间 if (_copy_segments_out.size() == 0) { _clock_running = false; _retransmission_timeout = _initial_retransmission_timeout; _consecutive_retransmissions = 0; _cur_time = 0; } else if (ok) { _clock_running = true; _retransmission_timeout = _initial_retransmission_timeout; _consecutive_retransmissions = 0; _cur_time = 0; } // 如果是有效的ack,改变窗口大小 if (valid_ack) { _window_size = window_size; if (window_size == 0) { _window_size = 1; _zero = true; } else { _zero = false; } } } void TCPSender::tick(const size_t ms_since_last_tick) { if (!_clock_running) return; _cur_time += ms_since_last_tick; // 如果到了重传时间 if (_cur_time >= _retransmission_timeout) { if (_copy_segments_out.size() > 0) { // 重传报文段 TCPSegment seg = _copy_segments_out.front(); _segments_out.push(seg); } // 如果窗口大小大于0 if (!_zero) { _consecutive_retransmissions++; _retransmission_timeout *= 2; } _clock_running = true; _cur_time = 0; } } unsigned int TCPSender::consecutive_retransmissions() const { return _consecutive_retransmissions; } void TCPSender::send_empty_segment() { TCPSegment seg; seg.header().syn = false; seg.header().ack = false; seg.header().seqno = wrap(_next_seqno, _isn); _segments_out.push(seg); _copy_segments_out.push(seg); }
3.3 具体实现(优化后)
-
tcp_sender.cc
#include "tcp_sender.hh" #include "tcp_config.hh" #include <random> // Dummy implementation of a TCP sender // For Lab 3, please replace with a real implementation that passes the // automated checks run by `make check_lab3`. using namespace std; //! \param[in] capacity the capacity of the outgoing byte stream //! \param[in] retx_timeout the initial amount of time to wait before retransmitting the oldest outstanding segment //! \param[in] fixed_isn the Initial Sequence Number to use, if set (otherwise uses a random ISN) 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} , _retransmission_timeout{retx_timeout} , _stream(capacity) {} uint64_t TCPSender::bytes_in_flight() const { return _bytes_in_flight; } void TCPSender::fill_window() { if (_fin) return; while (_bytes_in_flight < _window_size) { TCPSegment seg; if (!_syn) { _syn = true; seg.header().syn = true; } size_t len = min(_window_size - _bytes_in_flight - seg.length_in_sequence_space(), TCPConfig::MAX_PAYLOAD_SIZE); seg.payload() = _stream.read(len); seg.header().seqno = wrap(_next_seqno, _isn); if (!_fin && _stream.eof()) { if (bytes_in_flight() + seg.length_in_sequence_space() < _window_size) { seg.header().fin = true; _fin = true; } } if (seg.length_in_sequence_space() > 0) { _copy_segments_out.push(seg); _segments_out.push(seg); _next_seqno += seg.length_in_sequence_space(); _bytes_in_flight += seg.length_in_sequence_space(); if (!_clock_running) { _clock_running = true; _cur_time = 0; } } else { break; } } } //! \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) { if (next_seqno().raw_value() < ackno.raw_value()) return; bool ok = false, valid_ack = false; while (!_copy_segments_out.empty()) { TCPSegment seg = _copy_segments_out.front(); // 一个冗余的ACK if (seg.header().seqno.raw_value() == ackno.raw_value()) valid_ack = true; if (seg.header().seqno.raw_value() + seg.length_in_sequence_space() > ackno.raw_value()) break; ok = true; valid_ack = true; _bytes_in_flight -= seg.length_in_sequence_space(); _copy_segments_out.pop(); _window_size = window_size; } // 如果有多余的空间, fill_window函数会被调用继续填充空间 // 如果所有的报文段都被确定了, 关闭计时器并且重置时间 if (_copy_segments_out.size() == 0) { _clock_running = false; _retransmission_timeout = _initial_retransmission_timeout; _consecutive_retransmissions = 0; _cur_time = 0; } else if (ok) { _clock_running = true; _retransmission_timeout = _initial_retransmission_timeout; _consecutive_retransmissions = 0; _cur_time = 0; } // 如果是有效的ack,改变窗口大小 if (valid_ack) { _window_size = window_size; if (window_size == 0) { _window_size = 1; _zero = true; } else { _zero = false; } } } //! \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 (!_clock_running) return; _cur_time += ms_since_last_tick; // 如果到了重传时间 if (_cur_time >= _retransmission_timeout) { if (_copy_segments_out.size() > 0) { // 重传报文段 TCPSegment seg = _copy_segments_out.front(); _segments_out.push(seg); } // 如果窗口大小大于0 if (!_zero) { _consecutive_retransmissions++; _retransmission_timeout *= 2; } _clock_running = true; _cur_time = 0; } } unsigned int TCPSender::consecutive_retransmissions() const { return _consecutive_retransmissions; } void TCPSender::send_empty_segment() { TCPSegment seg; seg.header().syn = false; seg.header().ack = false; seg.header().seqno = wrap(_next_seqno, _isn); _segments_out.push(seg); _copy_segments_out.push(seg); }
-
结果截图
3.4 实现后仍然存在问题
-
FIN标识是否一定要放在最后一个报文?
-
如何界定一个有效的ACK报文?
-
是否仍然存在一定的边界问题?
-
当计时器开启时,如果新的计时器到达,是否应该将原有计时器置为0?