CS144: Introduction to Computer Networking Lab 4
Contents
You can get the whole series from here
Before we write the code, we should think what would TCPConnection do. If you carefully read the docs provided by lab. You will understand the requirements easily. But the most important thing is to understand how TCPConnection combines the TCPReceiver and TCPSender and achieves the functionality.
Here, I give an example to illustrate the process. I named the two instances of TCPConnection called TCPConnectionA and TCPConnectionB. And the TCPConnectionA has two classes TCPReceiverA and TCPSenderA, respectively, the TCPConnectionB has two classes TCPReceiverB and TCPSenderB.
When TCPConnectionA sends the segment to TCPConnectionB, it will calls the TCPSenderA‘s fill_window() method to extract the data to the segments. It will maintain the absolute sequence number for the next byte to be sent. However, there is one thing we do not consider, how about the acknowledge number in the header?. We need to do this in the TCPConnection class.
And when TCPConnectionB using segment_received method to handle the received segments. It should do the following things:
- It should uses
TCPReceiverB‘s methodsegment_receivedmethod which updates its acknowledge number which should later be sent to theTCPConnectionA. Now we can understand the above question: the acknowledge number comes from itself receiver’s stored acknowledge number. - Next it should use
TCPSenderB‘sack_receivedto update theTCPSenderA‘s acknowledge number and its window size. - Then we should call the
fill_windowmethod and pops theTCPSenderB‘s_segments_outtoTCPConnectionB‘s_segments_out. And also we need to change the header part, set the acknowledge number to theTCPReceiverB‘s acknowledge number.
I am really appreciating the nice abstraction for this design. You can look at the following picture for better understanding.

Easy functions
There are some easy functions which just some getters. We can implement these functions immediately.
1 | class TCPConnection { |
1 | size_t TCPConnection::remaining_outbound_capacity() const { return _sender.stream_in().remaining_capacity(); } |
TCP Connection
It’s not hard to write the code about sending the data or receiving the data. I have already talked about a lot above. However, we need to handle the connection and close carefully.
Connect
In the TCPSender class, we actually do not consider the ack. So we need to use _sender.send_empty_segment() to produce a new segment when connecting.
Close
The most difficult part is how to gracefully close the TCPConnection. For unclean shutdown, it’s easy. We just send or receive a segment with the RST flag set.
However, for clean shutdown. There are so many things we need to do.
Prereq #1 The inbound stream has been fully assembled and has ended. I use a private function called
check_inbound_stream_assembled_and_ended.1
bool TCPConnection::check_inbound_stream_assembled_and_ended() { return _receiver.stream_out().eof(); }
Prereq #2 The outbound stream has been ended by the local application and fully sent the fact that it ended to the remote peer. Remember, I have provided a
endflag but without no public method to get that value. So I do this and uses a function calledcheck_outbound_stream_ended_and_send_fin.1
bool TCPConnection::check_outbound_stream_ended_and_send_fin() { return _sender.stream_in().eof() && _sender.is_end(); }
Prereq #3 The outbound stream has been fully acknowledged by the remote peer. I use a function called
check_outbound_fully_acknowledged.1
bool TCPConnection::check_outbound_fully_acknowledged() { return _sender.bytes_in_flight() == 0; }
Prereq #4 It’s important to understand the reason why there are two situations. We would implement this in the
tickfunction
We can code now.
Auxiliary functions
Here, I first define a function called set_ack_and_window, it inspects the _receiver‘s acknowledge number and window size. And updates the corresponding fields of itself.
1 | void TCPConnection::set_ack_and_window(TCPSegment &seg) { |
And I define send_new_segments, which recursively adds new segments to the _segments_out. The most importantly, we should indicate whether we could write new segments. There are situations the segment we need to send with no payload but with SYN or FIN set or a keep-alive message.
1 | bool TCPConnection::send_new_segments() { |
Also, there are two situations we should send a segment with RST set. So I use a function named send_rst_flag_segment:
1 | void TCPConnection::send_rst_flag_segment() { |
Also, I define set_error function to handle the RST set segment or we want to send a new RST set segment.
1 | void TCPConnection::set_error() { |
connect
Now we comes to the most important part, when the client wants to connect the server, it calls the connect. It is simple enough, because I have done the job in the TCPSender.
1 | void TCPConnection::connect() { |
segment_received
For segment_received, it is tricky.
- We handle the segment with RST set. It is easy.
- We should call
_receiver.segment_receivedmethod to update the acknowledge number and window size. - We check whether the inbound stream is end (the opposite sender would tell us this information), if so, we are the passive, we don’t need the
TIME_WAITtimer, set the_linger_after_streams_finishto befalse. - When the received segment with ACK set, we should first checkout whether we should accept the segment. If the
receiver_ackno()doesn’t exist, we just return. Otherwise, we should call_sender.ack_receivedand_fill_window()and callssend_new_segments. - Next, we need to handle the situation with no payload.
1 | void TCPConnection::segment_received(const TCPSegment &seg) { |
write
Simple enough.
1 | size_t TCPConnection::write(const string &data) { |
end_input_stream
end_input_stream aims at doing active close. So we first signal the _sender‘s ByteStream to be end and calls _fill_window.
1 | void TCPConnection::end_input_stream() { |
tick
For tick, we should retransmit the segment. If the retransmission time consecutive_retransmissions() is greater than _cfg.MAX_RETX_ATTEMPTS, we should produce a segment with RST set. However, this is not the most important point. When tick is called, for passive closer, it just returns. For active closer we need to make sure that the passive closer has successfully received the ACK sent by the active closer. But we have no idea, so if the passive closer doesn’t retransmit the segment in a period of time, we can think that passive closer has already been closed. Thus we can close the active closer.
1 | void TCPConnection::tick(const size_t ms_since_last_tick) { |