Stanford CS144: Lab 3

1 文档解读

lab3 是实现一个tcpSender,主要功能是:从streamIn中读取数据,然后封装到TCPSegment中。

需要考虑的是发送多少数据,如何切分发送。

注意lab 3的内容是发送方无限制的,即不考虑 发送方的存储能力,只考虑 接收方的窗口。所以是没有书上的慢启动等部分。

1.1 Responsibility of TCPSender

  • 记录接收者的确认号 acknos 和 window size
  • 通过读取数据并发送,去尽可能填满window。这表示sender需要发送数据,直到streamIn中的数据被读完,或者window被填满。 注意该功能实现在 fill_window()中,而该函数会在以下几种情况下被调用(from 测试代码):
    • 向stream中写入完毕后
    • ackReceived后
    • close后
    • 测试类初始化时
  • 需要缓存已经发送但未被确认的TCPSegment,称为outstanding segment
  • time out之后重新发送 outstanding segment

1.2 tick

lab 3,直接使用了一个按输入参数改变的计时器,不是物理上的时间,tick函数通过参数使得 timer++,直到timer 超过了RTO,就发生重发事件。

计时器的计时功能其实很简单,后面看代码就行了。

计时器相关要点

  • TCPSender初始化的时候会有一个 initial_retransmission_timeout,这就是RTO的起始值。
  • 如果发送了任何数据且timer未启动,就启动 timer。
  • 当没有缓存的TCPSegment的时候就关闭timer。
  • 如果tick被调用(测试样例中是显式调用的),且timer超时:
    • 重传第一个缓存的TCPSegment(注意,理论上是可以再把这个TCPSegment切分,如果ack大于他的index,但这边不需要去实现)
    • 如果window size 为非零:
      • 需要追踪连续重传的次数。当多次重传无果,可能表示连接有问题,然后断开连接。
      • RTO加倍,发生重传可能是因为此时网络拥堵,所以加倍RTO来减少不必要的带宽。
    • 当timer out之后需要 reset
  • 当收到了更大的ack(有新的数据被确认了):
    • 把RTO重置为 起始值
    • 如果还有outstanding data,就重置 timer
    • 将连续重传的次数置为0

1.3 接口分析

 void fill_window()

调用 fill_window 时,在window size 允许的情况下,尽可能发送数据。

每个TCPSegment的切分依据是:window size 和 payload 不能超过 MAX_PAYLOAD_SIZE . 注意 window size 是 length_in_sequence space() 相关的,但是MAX_PAYLOAD_SIZE是payload相关的。

当window_size是0的时候,需要把它当作1处理。否则接收方不会再收到任何信息。

void ack_received( const WrappingInt32 ackno, const uint16 t window_size)

调用 ack_received之后,需要修改目前的 _ackno 和 _window_size。并且删除缓存的已被确认的TCPSegment。

void tick( const size t ms_since_last_tick )

查看是否超时,若超时则执行重传。

void send_empty_segment()

发送不带任何信息的TCPSegment,包括FIN 和SYN。在这个lab中没有用,后面会有用。

1.4 疑问分析

(1) 当fill_window发送了数据需要减小_window_size吗?

显然是需要去修改一个变量,来确定自己需要发送的数据量,但是又不能修改_window_size,因为改了之后,后面的对于window_size的判断就会出问题。

所以新建一个sender_window_size,来标记 sender还可以发送的数据。

(2) windows的size和FIN 和SYN有关吗

相关

(3)对于比已知ack号小的ack的处理?

直接丢弃可以通过lab3的case

(4) 给定的 _segments_out 表示什么?

push进入_segments_out就表示发送。所以需要新建一个数据结构来存储outstanding segments。

2 代码

2.1 添加成员

  • hasFin:需要处理后面一个不断读取FIN的corner case。
// tcp_sender.hh
  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> _segments_storage{};

    //! retransmission timer for the connection
    unsigned int _initial_retransmission_timeout;
    unsigned int _now_retransmission_timeout{};

    size_t _timer{};
    bool _isRunning{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};

    uint64_t _ack_seqno{0};

    size_t _window_size{1};

    size_t _sender_window_size{1};

    size_t _num_consecutive_retransmissions{0};

    bool hasFIN = {false};

2.2 fill_window()

因为不断地debug,然后修改代码,所以会比较乱。列一下要点:

  • 当 _sender_window_size 存在地时候就要尽可能发送数据。

  • _sender_window_size 是在 ack_received() 中处理的,fill_window()只需要减少发送的量即可。

  • 上面有说过,window_size和 MAX_PAYLOAD_SIZE的限制是不同的,所以要注意这部分。

  • 这边设置了一个has_FIN,是为了避免发生重复读出eof的情况。

  • 对于无信息TCPSegment,fill_window是不发送的。

void TCPSender::fill_window() {
    while(_sender_window_size){
        // Get the send size.
        size_t  send_size  = std::max(_sender_window_size,1ul);

        // If it is the first byte,SYN will cost 1 byte.
        if(_next_seqno == 0)
            --send_size;


        Buffer buf = Buffer(stream_in().read(std::min(TCPConfig::MAX_PAYLOAD_SIZE,send_size)));

        // Get the tcpSegment
        TCPSegment tcpSegment = TCPSegment();
        tcpSegment.payload() = buf;
        tcpSegment.header().seqno = wrap(_next_seqno,_isn);


        // Add the SYN flag
        if(_next_seqno == 0){
            tcpSegment.header().syn = true;
        }

        // Add the FIN flag,when the stram_in is read out.

        if(tcpSegment.length_in_sequence_space() < send_size && stream_in().eof()){
            tcpSegment.header().fin = true;
        }

        if(tcpSegment.length_in_sequence_space() == 0 || (hasFIN && tcpSegment.length_in_sequence_space() == 1 && tcpSegment.header().fin))
            return;

        // start the timer
        if(!_isRunning){
            _isRunning = true;
            _timer = 0;
        }


        if(tcpSegment.header().fin)
            hasFIN = true;

        _segments_out.push(tcpSegment);
        _segments_storage.push(tcpSegment);


        if(_sender_window_size >= tcpSegment.length_in_sequence_space())
            _sender_window_size -=  tcpSegment.length_in_sequence_space();
        _next_seqno += tcpSegment.length_in_sequence_space();
    }
}

2.3 ack_received()

  • 收到比目前 _ack_seqno 还要小的情况,直接return。
  • 修改_window_size和_sender_window_size
  • 删除已被确认的outstanding TCPSegments
  • 如果删完了outstanding TCPSegments,就关闭 timer。
void TCPSender::ack_received(const WrappingInt32 ackno, const uint16_t window_size) {
    const uint64_t old_ack_seqno = _ack_seqno;
    const uint64_t tmp = unwrap(ackno,_isn,_ack_seqno);

    // If ack < _ack_seqno,discard it.
    if(tmp >= _ack_seqno)
        _ack_seqno = tmp;
    else
        return;

    // Modify the _window_size and _ack_seqno.

    _window_size = window_size;
    if(_next_seqno - tmp <= _window_size)
        _sender_window_size = _window_size - (_next_seqno - tmp);

    //If the _window_size == 0,let _sender_window_size be 1.
    if(_window_size == 0)
        _sender_window_size = 1;

    // Delete the ack segment storage.
    while(!_segments_storage.empty()){
        auto seg = _segments_storage.front();
        uint64_t index = unwrap(seg.header().seqno,_isn,_ack_seqno);
        if(_ack_seqno >= index + seg.length_in_sequence_space())
            _segments_storage.pop();
        else
            break;
    }

    if(_ack_seqno > old_ack_seqno){
        _now_retransmission_timeout = _initial_retransmission_timeout;
        _num_consecutive_retransmissions = 0;
        if(!_segments_storage.empty())
            _timer = 0;
    }

    // If no storage,stop the timer
    if(_segments_storage.empty()){
        _timer = 0;
        _isRunning = false;
    }

}

2.4 其余接口

直接看代码

void TCPSender::tick(const size_t ms_since_last_tick) {
    // If the timer is not running,return;
    if(!_isRunning)
        return;
    _timer += ms_since_last_tick;
    if(_timer >= _now_retransmission_timeout){
        _segments_out.push(_segments_storage.front());
        _timer = 0;

        if(_window_size != 0){
            _num_consecutive_retransmissions++;
            _now_retransmission_timeout *= 2;
        }

    }
}

unsigned int TCPSender::consecutive_retransmissions() const { return _num_consecutive_retransmissions; }

void TCPSender::send_empty_segment() {
    TCPSegment tcpSegment;
    _segments_out.push(tcpSegment);
}

uint64_t TCPSender::bytes_in_flight() const {
    return _next_seqno - _ack_seqno; }

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值