CS144:lab3

写在前面

此次实验是写一个TCP sender。主要的难点在于填充window,接受ack,以及设计定时器实现重传机制。其基本的思想是根据receiver的窗口大小,封装TCP segment,未收到ack确认的数据报作为outstanding segments存储在额外的数据结构中,也属于window中的一部分。一旦接受到ack,即可将相应的数据报从窗口中移出。另外,还需要一个定时器来表明网络中的拥塞情况,一旦定时器超时,则认为相应的数据报丢失,需要进行重传。数据报丢失有两种情况,一是网络拥塞所致,此时需要将定时器的rto时间翻倍;另一种情况是由于receiver的窗口大小已满所致,即发送速率快过接受速率,此时只需要重启定时器即可,不需要再改动rto。

实验部分

一、定时器RTOtimer类

定时器主要用于判定传送的数据报是否丢失需要重传,需要设计一个定时器类,类成员对象和成员函数如下:

  • _rto:timer重传所需要的时间
  • _start_time:定时器启动的时刻
  • _start:表明定时器是否启动
  • reset():当_window_size不等于0时,翻倍_rto
  • reset(const size_t rto):收到新的ack时,用初始rto重置定时器
  • stop():当收到fin的ack时,关闭定时器
  • has_expired:根据当前时间判定定时器是否到期了
class RTOtimer{
private:
    size_t _rto;
    size_t _start_time;
    bool _start;
public:
    RTOtimer(const size_t rto):_rto(rto),_start_time(0),_start(false){}
    void start(size_t time){
	    _start_time=time;
	    _start=true;
    }    
    void reset() {_rto=_rto*2;} 
    void reset(const size_t rto){_rto=rto;}
    void stop(){_start=false;}
    bool has_expired(size_t time)const{
	    return _start&&(time-_start_time)>=_rto;
    }
};

二、TCPSender

2.1 TCPSender所需的额外的类成员变量

  • _bytes_in_flight:用于记录已发送但还未被确认的字节数
  • _consecutive_retransmission:当window_size不为0时,连续重传次数
  • _outstanding_segments:已发送但还未被确认的数据报
  • _time:当前时间
  • _window_size:窗口大小,初始化为1,用于发送syn
  • _timer:定时器
  • _ackno:确认的字节序列
  • finish:表明fin是否已经被发送了
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{};

    //! retransmission timer for the connection
    unsigned int _initial_retransmission_timeout;

    //! 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};

    size_t _bytes_in_flight=0;
    size_t _consecutive_retransmission=0;
    std::queue<TCPSegment> _outstanding_segments{};
    size_t _time=0;
    uint16_t _window_size=1;
    RTOtimer _timer;
    WrappingInt32 _ackno;
    bool finish=false;
... ...

2.2 fill_window()

该函数的设计逻辑如下:

  • 计算发送空间:_window_size由两部分组成,一部分是outstanding_segments,另一部分是可以用于发送的空间,因此需要根据还未被确认的字节数,即_bytes_in_flight,来计算可以发送的空间大小space。另外,当_window_size为0时,需要将space置为1,因为需要用一个数据报来获得receiver的窗口大小,否则发送方将永远无法发送数据。
  • 生成数据报:若还有空间且!finish(因为每次收到ack都会调用fill_window,避免已经收到了fin后再次调用fill_window,再次生成fin),那么可以生产数据报。先判断是否需要生产syn,是产生syn数据报,并启动定时器;否则生成普通的数据报。这里需要注意,若还有space且字节数已经到达了eof,需要生成fin数据报,fin可以是单独占用一个数据报,也可以携带payload,取决于payload是否为0,生成完fin后需要将finish置true。当payload为0时,退出循环,避免生成无效数据。

该函数的代码实现如下:

void TCPSender::fill_window() {
    size_t space=_window_size-(next_seqno()-_ackno);
    if(_window_size==0) space=1-(next_seqno()-_ackno);
    while(space&&!finish){
	    TCPSegment seg;
	    //发送syn
	    if(_next_seqno==0){
	        seg.header().syn=true;
	        seg.header().seqno=_isn;
	        _next_seqno++;
	        _bytes_in_flight++;
	        _segments_out.push(seg);
	        _outstanding_segments.push(seg);
	        space--;
	        _timer.start(_time);
	        continue;
	    }
	    //发送数据和fin
	    size_t payload=stream_in().buffer_size()<space?
			stream_in().buffer_size():space;
	    payload=payload<TCPConfig::MAX_PAYLOAD_SIZE?
			payload:TCPConfig::MAX_PAYLOAD_SIZE;
	    if(payload!=0){
	        seg.payload()=stream_in().read(payload);
	        seg.header().seqno=next_seqno();
	        _bytes_in_flight+=payload;
	        _next_seqno+=payload;
	        space-=payload;
	    }
	    if(payload==0 && !stream_in().eof()) break;
	    //若还有空间且到达eof,置fin	
	    if(space && stream_in().eof()){
	        //如果没有payload,要置header.seqno
	        if(!payload){
		        seg.header().seqno=next_seqno();
	        }
	        seg.header().fin=true;
	        space--;
	        _next_seqno++;
	        _bytes_in_flight++;
	        finish=true;
	    }
	    //入队
	    _segments_out.push(seg);
	    _outstanding_segments.push(seg);
	    if(payload==0) break;
    }
  
}

2.3 ack_received

该函数接受两个参数,分别是ackno和window_size,具体的实现逻辑如下:

  • 更新window_size和ackno:前者直接将window_size赋给_window_size即可。后者需要考虑三种情况。一是当_ackno=_isn,说明此时发送的是syn,网络中可能存在垃圾的数据报,因此若收到的ackno不等于_isn+1,说明收到了垃圾,不再进行处理;二是收到的ackno的绝对序列号小于等于_ackno的绝对序列号,说明收到的ack重复了,直接忽视;三若不属于前两种情况,那么说明收到的ack序列号有效,更新序列号,_bytes_in_flight以及_consecutive_retransmission即可。
  • 处理outstanding_segments:当收到的ackno超过了outstanding_segment的最大序列号,那么就要把相应的数据报从队列中pop出来。
  • 关闭定时器:如果收到了fin ack,那么需要将定时器关闭。
  • 重置定时器:由于收到了新的ack,需要对定时器重置。用初始的rto重置定时器,并启动。

代码实现如下:

void TCPSender::ack_received(const WrappingInt32 ackno, const uint16_t window_size) { 
    //更新window_size
    _window_size=window_size;

    //网络中存在垃圾segments
    if(_ackno==_isn && ackno!=_ackno+1) return;
 
    
    //如果收到了重复的ack,那么就不理会
    if(unwrap(ackno,_isn,_next_seqno)<=unwrap(_ackno,_isn,_next_seqno)) return;

    //更新bytes_in_flight和ackno以及连续重传数
    _bytes_in_flight-=ackno-_ackno;
    _ackno=ackno;
    _consecutive_retransmission=0;


    //更新outstanding segments,确认过的出队
    while(!_outstanding_segments.empty()){
	    TCPSegment front=_outstanding_segments.front();
        if(static_cast<size_t>(_ackno-       
                front.header().seqno)>=front.length_in_sequence_space())
	        _outstanding_segments.pop();
	    else break;
    }
    //确认fin
    if(finish && _outstanding_segments.empty()){
	    _timer.stop();
	    return;
    }
    
    //重置定时器
    _timer.reset(_initial_retransmission_timeout);
    _timer.start(_time);
    
}

2.4 tick

该函数传入一个参数,表明与上一次调用该函数的时间间隔,函数设计思路如下:

  • 根据参数,获得当前的时间
  • 判断定时器是否超时,若超时,则重传,若window_size不为0,还需要对重传次数加1并且将定时器的rto值翻倍。重传结束后需要再次启动定时器。

代码实现如下:

void TCPSender::tick(const size_t ms_since_last_tick) {
    //更新时间	
    _time+=ms_since_last_tick;

    //超时重传
    if(_timer.has_expired(_time)){
	    _segments_out.push(_outstanding_segments.front());
	    if(_window_size){
	       _consecutive_retransmission++;
	      _timer.reset(); 
	    }
	    _timer.start(_time);	
    }
}

2.5 其他函数

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}
    , _stream(capacity),_timer(retx_timeout),_ackno(_isn) {}

uint64_t TCPSender::bytes_in_flight() const { return _bytes_in_flight; }

unsigned int TCPSender::consecutive_retransmissions() const { return     _consecutive_retransmission; }
   
void TCPSender::send_empty_segment() {
    TCPSegment seg;
    seg.header().seqno=next_seqno();
    _segments_out.push(seg); 
}

2.6 实验结果

 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值