An unreliable network underneath, but every byte arrives once, in order — that's the promise TCP keeps.
The internet underneath TCP is a best-effort delivery service: packets can be dropped, duplicated, delayed, or reordered. TCP turns that messy channel into a clean, ordered byte stream between two endpoints.
It does this with three ingredients. First, a three-way handshake agrees on starting sequence numbers and opens the connection. Second, every byte is numbered, and the receiver sends cumulative acknowledgements reporting the next byte it still needs. Third, if an acknowledgement does not arrive in time, the sender retransmits. Watch one segment get lost and recovered below.
The receiver tracks the highest contiguous byte it has received and acknowledges the next byte it expects. So ack = last_contiguous_byte + 1. Because acks are cumulative, one ack confirms everything up to that point. The sender keeps a copy of unacknowledged data and a retransmission timer (the RTO); if the ack does not advance before the timer fires, it resends.
// receiver: cumulative ack = next byte it still needs
on_segment(seg):
if seg.seq == rcv_next: // in order — accept
rcv_next += seg.len
// (a gap leaves rcv_next where it is)
send_ack(ack = rcv_next) // "give me byte rcv_next next"
// sender: retransmit when the ack does not advance
send(seg): buffer[seg.seq] = seg; start_timer(seg, RTO)
on_ack(ack):
drop buffered bytes < ack // confirmed, free them
if ack > snd_una: snd_una = ack // window slides forward
on_timeout(seg):
resend(seg); RTO *= 2 // back off, try again
// SYN and FIN each consume one sequence number, like a phantom byte.
| What you pay | Why |
|---|---|
| One round trip before any data | The handshake (SYN, SYN/ACK, ACK) costs 1 RTT of setup latency. TCP Fast Open can carry data in the SYN to skip part of this. |
| Send buffers on both ends | The sender holds unacknowledged bytes for possible retransmission; the receiver buffers out-of-order data until the gap fills. |
| Acknowledgement traffic | Received data triggers ack packets (delayed acks batch some of this to cut overhead). |
| Throughput ceiling | Roughly window / RTT. Unacknowledged data is capped by the window, so a high RTT throttles you until the window grows. |
seq=100 with ack=101.2 × MSL, holding the port so stray old packets cannot be mistaken for a new connection.recv() may hand you a partial or merged message. You add the framing.The client sends 300 bytes as three 100-byte segments after a handshake that left it at seq=101. Suppose the second segment is lost in flight.
seg 1 seq=101 (bytes 101..200) → arrives → server acks 201
seg 2 seq=201 (bytes 201..300) → LOST
seg 3 seq=301 (bytes 301..400) → arrives, but there is a GAP
server now holds 301..400, yet it still acks 201
(a duplicate ack) — "I'm still waiting on byte 201"
retransmit seq=201 → arrives, fills the gap
now 101..400 is contiguous → server acks 401
The takeaway: the server keeps acking 201 until the hole is filled, then jumps straight to 401 — one cumulative ack covering both the retransmit and the segment that had been waiting behind it. Only the lost segment is resent, not segment 3.
How many messages does the TCP three-way handshake exchange before the connection is established?
The receiver sends ack=201. What does that number mean?