Reliability on top of chaos
Lesson 3-1 left us with packets that can arrive late, out of order, or not at all. TCP (Transmission Control Protocol) fixes this with three habits:
- Sequence numbers on every byte, so the receiver can put pieces back in order (you did this by hand with
sort -n). - Acknowledgments (ACKs): the receiver constantly reports "I have everything up to byte N, send from there."
- Retransmission: if the sender does not hear an ACK in time, it sends the piece again.
Before any data flows, both sides must agree to talk and pick starting sequence numbers. That is the famous three-way handshake:
- Client → server: SYN ("I want to connect, my numbering starts at 100")
- Server → client: SYN-ACK ("OK, I heard 100. Mine starts at 300")
- Client → server: ACK ("I heard 300, we're on")
Three messages, one round trip and a half, and the connection exists. Every HTTP request in this course rides on a connection opened exactly this way.
The handshake with real numbers
Each side announces a starting sequence number and acknowledges the other's.
echo "client -> server: SYN (seq=100)" echo "server -> client: SYN-ACK (seq=300, ack=101)" echo "client -> server: ACK (ack=301)" echo "connection established"
Output
client -> server: SYN (seq=100) server -> client: SYN-ACK (seq=300, ack=101) client -> server: ACK (ack=301) connection established
Each ack is the other side's seq plus 1, because an ack number always means "this is the next byte I expect".
The two sides pick independent starting numbers, 100 and 300, and neither is zero. Real implementations choose them randomly, which makes it much harder for an outsider to inject convincing packets into a connection they cannot see.
Counting the crossings explains a cost you will meet again in lesson 3-4. Three messages means one and a half round trips before a single byte of your actual request is sent, which is why connection reuse matters so much on a slow link.
The ack for a SYN carrying seq=500
The server puts ack=501 in its SYN-ACK.
An ack number means "the next byte I expect from you". The server received the client's byte 500, so it expects 501 next.
This next-expected rule is how TCP reports progress and detects gaps. An ack is not a receipt for one packet, it is a running statement about everything received so far.
That framing is what makes the mechanism robust. A lost ack is harmless if a later one arrives, because the later number covers all the earlier bytes too, so TCP does not need a reply for every single segment to stay correct.
Computing acks for a stream of segments
For each incoming segment, the ack is the next byte expected, which is seq + len.
for seg in 100:536 636:536 1172:200; do IFS=':' read -r seq len <<< "$seg" ack=$((seq + len)) echo "got seq=$seq len=$len -> send ack=$ack" done
Output
got seq=100 len=536 -> send ack=636 got seq=636 len=536 -> send ack=1172 got seq=1172 len=200 -> send ack=1372
Reading the stream
- Arithmetic in bash goes inside
$(( )), soack=$((seq + len))adds the two values read from the segment. - Each segment's
seqequals the previousack, which is the signature of a healthy in-order stream with no loss. Reading down that column is how you spot a gap. - If the second segment had arrived with
seq=1172instead, the receiver would still ack 636, repeating itself to say that bytes are missing. That repeated ack is the sender's cue to retransmit, and it is how loss is detected without any explicit error message. - The final segment is 200 bytes rather than 536, the short-last-chunk pattern from lesson 3-1 showing up again.