The Transport Layer
TCP Connections and Reliability
RELIABLE every byte sent arrives, or the
JrCodex·9 min read
Jr Codex Computer Networks Notes
Level: Intermediate Prerequisites: Chapter 2: UDP Time to complete: ~25 minutes
Table of Contents
- What TCP Promises
- The Header
- The Three-Way Handshake
- Sequence Numbers and ACKs
- Detecting and Retransmitting Loss
- Closing a Connection
- Summary & Next Steps
1. What TCP Promises
The Guarantees
─────────────────────────────────────────
RELIABLE every byte sent arrives, or the
connection fails and you are told
ORDERED bytes are delivered in the order
sent
DE-DUPLICATED duplicates are discarded
ERROR-CHECKED corrupted segments are detected
and resent
FLOW-CONTROLLED a fast sender cannot overwhelm a
slow receiver (Chapter 4)
CONGESTION- the network is not overwhelmed
CONTROLLED either (Chapter 4)
─────────────────────────────────────────
The Framing It Provides — and Does Not
─────────────────────────────────────────
TCP delivers an ordered BYTE STREAM.
It does NOT preserve message boundaries
(Chapter 1). Everything above must define its own
framing.
Worth restating because it is the most common
practical mistake in TCP programming.
─────────────────────────────────────────
2. The Header
20 Bytes, Minimum
─────────────────────────────────────────
0 16 31
┌─────────────────┬────────────────────────┐
│ Source Port │ Destination Port │
├─────────────────┴────────────────────────┤
│ Sequence Number │
├──────────────────────────────────────────┤
│ Acknowledgement Number │
├────┬────┬───────┬────────────────────────┤
│Off │Rsv│ Flags │ Window │
├────┴───┴───────┼────────────────────────┤
│ Checksum │ Urgent Pointer │
├─────────────────┴────────────────────────┤
│ Options (SACK, timestamps, MSS, │
│ window scale) │
└──────────────────────────────────────────┘
─────────────────────────────────────────
The Flags
─────────────────────────────────────────
SYN synchronise — open a connection
ACK this segment acknowledges data
FIN finished sending — begin closing
RST reset — abort immediately
PSH deliver to the application now, do not
buffer
URG urgent pointer is valid (essentially
unused)
─────────────────────────────────────────
RST vs FIN — a Practical Distinction
─────────────────────────────────────────
FIN a graceful close. "I have finished sending;
data already in flight will still arrive."
RST an abort. "This connection is invalid; drop
it now." Sent when connecting to a closed
port, when a connection is in a bad state,
or on SO_LINGER with a zero timeout.
"Connection reset by peer" means you received an
RST. It usually indicates the far end crashed,
a firewall injected it, or the application closed
abruptly with unread data.
─────────────────────────────────────────
3. The Three-Way Handshake
Opening a Connection
─────────────────────────────────────────
CLIENT SERVER
│ │
│──── SYN, seq=x ──────────────►│
│ │ SYN_RECEIVED
│◄─── SYN-ACK, seq=y, ack=x+1 ──│
│ │
│──── ACK, ack=y+1 ────────────►│
│ │ ESTABLISHED
│ ESTABLISHED │
─────────────────────────────────────────
Why Exactly Three
─────────────────────────────────────────
Both sides must agree on the other's INITIAL
SEQUENCE NUMBER, and each must know that the
other knows.
SYN client tells the server its ISN
SYN-ACK server acknowledges it AND sends its
own
ACK client acknowledges the server's
Two messages would leave the server unsure the
client received its ISN. Four would be redundant,
since the server's acknowledgement and its own
SYN are combined.
Three is the minimum for mutual confirmation.
─────────────────────────────────────────
THE COST: ONE ROUND TRIP BEFORE ANY DATA
─────────────────────────────────────────
On a 100ms path, that is 100ms before the first
byte of the request is even sent.
Add TLS (Module 6, Chapter 3) and it is 200-300ms
before anything useful moves.
This latency is the reason for HTTP keep-alive,
connection pooling, TCP Fast Open, and ultimately
QUIC's 0-RTT handshake (Chapter 5). A large part
of modern protocol design is an attack on these
round trips.
─────────────────────────────────────────
Why the ISN Is Random
─────────────────────────────────────────
If initial sequence numbers were predictable, an
attacker could inject data into a connection
without seeing it — guessing valid sequence
numbers.
Modern stacks generate the ISN from a
cryptographic function of the 4-tuple and a
secret. It also prevents old duplicate segments
from a previous connection with the same tuple
being accepted.
─────────────────────────────────────────
SYN FLOOD
─────────────────────────────────────────
An attacker sends many SYNs with spoofed source
addresses and never completes the handshake.
Each leaves the server holding half-open
connection state, until its table fills and
legitimate connections are refused.
DEFENCE — SYN COOKIES: the server encodes the
connection state into its own ISN and keeps NO
state until the final ACK arrives, which proves
the client is real.
─────────────────────────────────────────
4. Sequence Numbers and ACKs
What They Count
─────────────────────────────────────────
Sequence numbers count BYTES, not segments.
Send 100 bytes starting at seq=1000:
this segment covers bytes 1000-1099
the next segment starts at seq=1100
The ACK number is the NEXT BYTE EXPECTED, not the
last byte received.
receiving bytes 1000-1099 ──► ack = 1100
meaning "I have everything up to 1099; send
1100 next"
─────────────────────────────────────────
CUMULATIVE ACKNOWLEDGEMENT
─────────────────────────────────────────
An ACK confirms EVERYTHING up to that point, not
just one segment.
+ a lost ACK is harmless — the next one covers it
+ fewer ACKs are needed
- it cannot express "I got 1000-1099 and
1200-1299 but not 1100-1199". The ACK can only
say 1100.
SACK (Selective Acknowledgement), a TCP option,
fixes this by listing the received blocks
explicitly — so the sender resends only the
actual gap rather than everything after it.
Essential on lossy paths, and enabled by default
everywhere.
─────────────────────────────────────────
class TCPReceiver:
"""Reassembling an ordered stream from segments that may arrive out of order."""
def __init__(self, isn):
self.next_expected = isn + 1
self.out_of_order = {} # seq -> data, held until the gap fills
self.delivered = bytearray()
def on_segment(self, seq, data):
if seq < self.next_expected:
return self.next_expected # DUPLICATE — already have it, re-ACK
if seq > self.next_expected:
self.out_of_order[seq] = data # a GAP — buffer, do not deliver yet
return self.next_expected # ACK the gap: a DUPLICATE ACK
self.delivered.extend(data) # exactly what we wanted
self.next_expected = seq + len(data)
while self.next_expected in self.out_of_order: # drain what the gap unblocked
buffered = self.out_of_order.pop(self.next_expected)
self.delivered.extend(buffered)
self.next_expected += len(buffered)
return self.next_expected5. Detecting and Retransmitting Loss
Two Detection Mechanisms
─────────────────────────────────────────
1. TIMEOUT (RTO)
The sender starts a timer per segment. No ACK
before it expires ──► assume loss, retransmit.
SLOW: the RTO is conservative, at least
several hundred milliseconds.
2. DUPLICATE ACKS ──► FAST RETRANSMIT
Every out-of-order segment makes the receiver
re-send the same ACK. THREE duplicate ACKs
──► the sender retransmits IMMEDIATELY without
waiting for the timer.
FAST: roughly one round trip.
─────────────────────────────────────────
Why Three Duplicates
─────────────────────────────────────────
Packet REORDERING also produces duplicate ACKs,
and reordering is not loss.
One or two duplicates are likely reordering.
Three strongly suggests a genuine gap.
It is a threshold chosen empirically to balance
fast recovery against spurious retransmission —
a recurring theme in TCP's design.
─────────────────────────────────────────
def estimate_rto(srtt, rttvar, sample_rtt, alpha=0.125, beta=0.25):
"""Jacobson's algorithm. RTO adapts to both the mean AND the variance of RTT."""
if srtt is None:
srtt, rttvar = sample_rtt, sample_rtt / 2
else:
rttvar = (1 - beta) * rttvar + beta * abs(srtt - sample_rtt)
srtt = (1 - alpha) * srtt + alpha * sample_rtt
rto = srtt + 4 * rttvar # ← the variance term is the key insight
return srtt, rttvar, max(rto, 0.2) # never below ~200ms
srtt = rttvar = None
for sample in [0.10, 0.11, 0.09, 0.35, 0.10]: # note the spike
srtt, rttvar, rto = estimate_rto(srtt, rttvar, sample)
print(f"sample {sample:.2f}s ──► RTO {rto:.3f}s")Why the Variance Term Matters
─────────────────────────────────────────
Early TCP used only the mean RTT, and on a
network with variable delay it retransmitted
constantly — packets that were merely late were
assumed lost, adding load to an already congested
network.
Including 4×variance widens the timeout when the
network is JITTERY (Module 1, Chapter 5), so
variability no longer causes spurious
retransmission.
This change, in 1988, is one of the fixes that
ended the internet's congestion collapse.
Chapter 4 covers the rest of it.
─────────────────────────────────────────
6. Closing a Connection
The Four-Way Close
─────────────────────────────────────────
CLIENT SERVER
│──── FIN ─────────────────────►│
│◄─── ACK ──────────────────────│ client half
│ │ is closed
│ (server may still send)│
│◄─── FIN ──────────────────────│
│──── ACK ─────────────────────►│
│ TIME_WAIT (2×MSL) │ CLOSED
│ then CLOSED │
─────────────────────────────────────────
Why Four, Not Three
─────────────────────────────────────────
TCP connections are FULL DUPLEX — two independent
byte streams.
Closing one direction does not close the other.
After the client's FIN, the server may still have
data to send, and TCP allows it.
This HALF-CLOSE is genuinely used: a client can
signal "I have sent my whole request" while
remaining able to receive the response.
─────────────────────────────────────────
TIME_WAIT, Explained Properly
─────────────────────────────────────────
The side that closes FIRST waits 2×MSL (maximum
segment lifetime), typically 60 seconds, before
releasing the 4-tuple.
TWO REASONS:
1. If the final ACK is lost, the peer will resend
its FIN. Something must still exist to
re-acknowledge it — otherwise the peer gets an
RST and reports an error on a connection that
closed cleanly.
2. Delayed segments from this connection must
expire before the same 4-tuple could be reused,
or they would be accepted into a NEW connection
as valid data.
Chapter 1 covered the operational consequences.
Both reasons are correctness, not conservatism.
─────────────────────────────────────────
7. Summary & Next Steps
Key Takeaways
- The three-way handshake is the minimum for both sides to confirm the other's initial sequence number, and it costs one full round trip before any data flows.
- Sequence numbers count bytes and the ACK number names the next byte expected; cumulative ACKs are robust but cannot express gaps, which is what SACK adds.
- Fast retransmit on three duplicate ACKs recovers in about one round trip, while the timeout path is deliberately conservative.
- Including RTT variance in the timeout calculation stopped spurious retransmissions on jittery paths, and was one of the fixes for congestion collapse.
Concept Check
- Why is the handshake three messages rather than two or four?
- What can a cumulative ACK not express, and how does SACK address it?
- Give both reasons TIME_WAIT exists, and explain why it is a correctness feature.
Next Chapter
→ Chapter 4: Flow and Congestion Control
Jr Codex — 1-on-1 Personalized Coaching | Back to Module Index | Back to Computer Networks Index