在Lab2和Lab3实现了TCP的Sender和Receiver,但是在建立TCP连接时,每个TCP实体既是Sender,又是Receiver,因此在Lab4的主要工作就是在一个TCP实体中统筹Receiver和Sender,同时完成建立连接和关闭连接的工作。

在Lab3的Sender中,通过将报文段放入_segments_out队列,来表示此报文段已经发送给对等实体的Receiver了。
但是其实Lab3中实现的Sender是孤立的,在发送报文时,报文首部只有seqno SYN FIN,但并无所在的TCP实体的ackno和win,即Sender的_segments_out的报文其实都不完整的。因此TCPConnection将TCP实体中接收方的ackno和win一起合并到报文段中,同时创建一个新队列来放置有完整首部的报文段。
void TCPConnection::transform_segments_out(){while(!_sender.segments_out().empty()){_sender.segments_out().front().header().ack = _receiver.ackno().has_value();if (_receiver.ackno().has_value())_sender.segments_out().front().header().ackno = _receiver.ackno().value();_sender.segments_out().front().header().win = min(_receiver.window_size(), static_cast((1 << 16) - 1));_segments_out.push(_sender.segments_out().front());_sender.segments_out().pop();}
}
rst,这是连接reset的重置标志位,用于强制终止一条TCP连接。当一个TCP报文段被标记为RST时,它表示着TCP连接的一个异常终止。因此TCP实体若接收到一个rst为1的报文段,就进入关闭连接的状态:将Receiver和Sender的字节流均设error,字节流无法继续读取和接收。segment_received(TCPSegment)接口给Receiver处理,将报文段按序合并到交付上层的字节流。ack标志位为1,则说明报文段的ackno有效,此时需要通过Sender的ack_received(ackno, win)给Sender处理,进行流量控制。ack报文段syn fin标志位,还是需要发送1条空报文,这个空报文会带有ackno,以对收到的报文进行确认。_linger_after_streams_finish设为false。ackno和win以上六点中,第1点为特殊判断,第2点针对本地TCP实体的Sender和Receiver,第3 4 5点针对远程实体的交互,第6点保证报文段的完整性。
void TCPConnection::segment_received(const TCPSegment &seg) { _time_since_last_segment_received = 0;TCPHeader header = seg.header();//! if the rst flag is set, sets both the inbound and outbound streams to the error state //! and kills the connection permanentlyif(header.rst){inbound_stream().set_error();outbound_stream().set_error();_is_rst_set = true;return;}//! gives the segment to the TCPReceiver_receiver.segment_received(seg);//! if the ack flag is set, tells the TCPSender about //! the fields it cares about on incoming segments: ackno and window size.if(header.ack){_sender.ack_received(header.ackno, header.win);}//! if the incoming segment occupied any sequence numbers, //!the TCPConnection makes sure that at least one segment is sent in reply.if(seg.length_in_sequence_space()){_sender.fill_window();if(_sender.segments_out().empty()){_sender.send_empty_segment();}}//! If the inbound stream ends before the TCPConnection has reached EOF on its outbound stream, this variable needs to be set to false.if(inbound_stream().input_ended() && !outbound_stream().eof()) _linger_after_streams_finish = false;//! responding to a “keep-alive” segment.if (_receiver.ackno().has_value() && (seg.length_in_sequence_space() == 0)&& header.seqno == _receiver.ackno().value() - 1) {_sender.send_empty_segment(); }//! reflect an update in the ackno and window size.transform_segments_out();
}
TCP连接的关闭有2种可能,若是异常关闭,即之前收到或发出了1个rst报文段,则TCP已关闭。否则就是TCP双方的数据已传输完毕,因此需要考虑实体的Sender和Receiver状态:
在以上2个条件都满足后,只需要再发生一件事情就可以关闭连接:*远程确认1条件,即只要远程确认本地已经接收了远程的所有数据。现在假设本地可能经过的2种状态:
本地在状态1,fin报文段必然还未发送,从状态1到状态2的转移过程中,至少会给远程发送1个报文段,而这个报文必然会携带本地给远程的ackno。只要本地从状态1到状态2,远程必然可以确认1条件(满足*条件)。因此,在segment_received(TCPSegment)中用_linger_after_streams_finish标记状态1,将其置为假,这样只要达到过状态1且当前处于状态2且_linger_after_streams_finish为假,则可立刻关闭连接。
另一种关闭连接的情况是,本地没有达到过状态1,直接到状态2,即本地是更早结束流发送的一方。此时远程的状态会是状态1,未达到状态2,发给远程的FINACK报文段可能因为网络拥塞等问题丢失,需要等待一段时间,看需不需要重传FINACK报文段,但经过足够长的时间,远程没有发来任何回应,则可仍为远程已达到状态2,可安心地关闭连接了。
总而言之,早发完数据的TCP实体需要等待,而晚发送完的不需要等待,因为晚结束的知道对方已经结束了,没必要等待对方了。
bool TCPConnection::active() const { if(_is_rst_set) return false;//Prereq #1 The inbound stream has been fully assembled and has endedif(inbound_stream().input_ended() && _receiver.unassembled_bytes() == 0 //Prereq #2 The outbound stream has been ended by the local application and fully sent&& outbound_stream().eof() && _sender.next_seqno_absolute() == outbound_stream().bytes_written() + 2//Prereq #3 The outbound stream has been fully acknowledged by the remote peer&& bytes_in_flight() == 0// At any point where prerequisites #1 through #3 are satisfied, the connection is “done” (and// active() should return false) if linger after streams finish is false.&& (!_linger_after_streams_finish// Otherwise you need to linger: the connection is only done after enough time (10 × cfg.rt timeout) has// elapsed since the last segment was received.|| time_since_last_segment_received() >= 10 * _cfg.rt_timeout)) return false;return true;}