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