Lab3 the TCP sender

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?

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值