CS144: Introduction to Computer Networking Lab 3
Contents
You can get the whole series from here
First, you should read carefully read the docs provided by the lab.
It will be your
TCPSender
‘s responsibility to:
- Keep track of the receiver’s window
- Fill the window when possible, by reading from the
ByteStream
, creating new TCP segments, and sending them. The sender should keep sending segments until either the window is full or theByteStream
is empty.- Keep track of which segments have been sent but not yet acknowledged by the receiver——we call these “outstanding” segments.
- Re-send outstanding segments if enough time passes since they were sent, and they haven’t been acknowledged yet.
Retransmission timer
According to the docs, the first thing we need to do is to implement a class for retransmission timer. So I have created two files retransmission_timer.hh
and retransmission_timer.cc
. However, before we implement this class in action, we should understand the requirements:
tick(const size_t ms_since_last_tick)
method is aimed at simulating the time. So every time thetick
is called, we need to add thems_since_last_tick
to the_accumulate_time
. When the_accumulate_time
is greater than the_rto
, the timer has elapsed. So we need to call a function calledtick_callback
every timetick
is called.- If the timer has elapsed, and the window size is not zero: double the value of the
_rto
, so we need a function calledhandle_expired
. Also, we need to set the_accumulate_time
to be 0. - Every time a segment containing data is sent, if the timer is not running, start it running. So we need to maintain a state. I use a function called
start_timer
to start the timer. - When receiver gives the sender an
ackno
that acknowledges the successful receipt of new data: we usereset_timer
to set the_rto
to its initial value and clear the_accumulate_timer
. If all the outstanding segments are received, we should stop the timer calledstop_timer
.
At now, we can code.
1 | // file: retransmission_timer.hh |
1 | // file: retransmission_timer.cc |
TCP sender
There are four public interfaces we need to implement for TCPSender
:
void fill_window()
.void ack_received(const WrappingInt32 ackno, const uint16_t window_size)
.void tick(const size_t ms_since_last_tick)
.void send_empty_segment()
.
Well, the main purpose for fill_window()
is to translate ByteStream to TCPSegment. And we need to push these segments to _segments_out
. And writes a reference copy to _outstanding_segments
.In this process, there are some many things we need to consider:
- We should record the receiver’s acknowledge number
_receiver_ack
and window size_receiver_window_size
to know whether we could transmit the new segments to the receiver. - We also need to consider about the ByteStream size.
- We need to think about the data structure of the
_outstanding_segments
. Infull_window()
, it should support insertion.
For ack_received()
, when we receiving the acknowledge number from the receiver, first we update _receiver_ack
and delete the fully acknowledged segments from _outstanding_segments
and calls fill_window()
. So the data structure for _outstanding_segments
should support deletion.
For tick()
, it should check whether the retransmission timer has expired. If so, it should retransmit the earliest (lowest sequence number) segment. So we need to make the _outstanding_segments
sorted.
The data structure of outstanding segments
I decide to use list
to represent the data structure of outstanding segments. The reasons are as follows:
- We could easily make it sorted.
- We could add and delete a segment fast.
Some private members
Before implementing the interfaces, we can define the following fields:
_retransmission_timer
: the timer defined above._receiver_ack
: the absolute receiver ack._receiver_window_size
: the receiver window size which should be 1 when initialized._outstanding_segments
: the outstanding segments_consecutive_retransmissions
: the consecutive retransmissions.window_not_full
: a helper function to tell whether the window is not full.
1 | class TCPSender { |
fill_window
There are two special cases we should consider for the fill_window
functions:
- TCP connection
- TCP disconnection
For the TCP connection where _next_seqno == 0
, we should set the syn
to be true
and sends the datagram out.
For the TCP disconnection, things could be much more complicated. There are two cases:
- An empty payload which indicates the disconnection where
stream_in().eof()
istrue
. - Nonempty payload which indicates the disconnection where
stream_in().eof()
istrue
.
For transferring the data, we want to transfer as much as we want. We should consider about the following three things:
- For a TCP segment, the length which should not exceed
TCPConfig::MAX_PAYLOAD_SIZE
. - We can only transfer the length which should not exceed
_receiver_window_size - bytes_in_flight()
. - We can only transfer the length which should not exceed
stream_in().buffer_size()
.
At the end, we need to start the timer.
Well, It may seem easy, however, there are some many corner cases. Please look at the following code for details.
1 | void TCPSender::fill_window() { |
ack_received
For receiving new acknowledge number from the receiver, which means the ackno
is greater than the next sequence number need to be sent. And ackno
is less than the current _receiver_ack
. We just return.
And next we need to step by step to check the _outstanding_segments
, if the ackno
can accept the full segments, we update the _receiver_ack
value. If not, we break the loop. Also, we need to update the value of _receiver_window_size
. However, there would be a corner case: what if the window size is zero:
If the receiver has announced a window size of zero, the fill window method should act like the window size is one. The sender might end up sending a single byte that gets rejected (and not acknowledged) by the receiver, but this can also provoke the receiver into sending a new acknowledgment segment where it reveals that more space has opened up in its window. Without this, the sender would never learn that it was allowed to start sending again.
And also we should not double the retransmission timeout in this case. So we need to test whether the _receiver_window_size
equals to 0 in tick
method.
1 | void TCPSender::ack_received(const WrappingInt32 ackno, const uint16_t window_size) { |
tick
tick
is easy, because the most important logic we have already implemented:
1 | void TCPSender::tick(const size_t ms_since_last_tick) { |
send_empty_segment
It is easy to implement send_empty_segment
. Just change the seq
of the segment.
1 | void TCPSender::send_empty_segment() { |