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_received
method 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_received
to update theTCPSenderA
‘s acknowledge number and its window size. - Then we should call the
fill_window
method and pops theTCPSenderB
‘s_segments_out
toTCPConnectionB
‘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
end
flag 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
tick
function
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_received
method 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_WAIT
timer, set the_linger_after_streams_finish
to 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_received
and_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) { |