TCP: the reliable byte stream

An unreliable network underneath, but every byte arrives once, in order — that's the promise TCP keeps.

The idea

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.

Client Server CLOSED LISTEN
A connection starts CLOSED. Press play, or step through the exchange.

How it works

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.

Cost

What you payWhy
One round trip before any dataThe 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 endsThe sender holds unacknowledged bytes for possible retransmission; the receiver buffers out-of-order data until the gap fills.
Acknowledgement trafficReceived data triggers ack packets (delayed acks batch some of this to cut overhead).
Throughput ceilingRoughly window / RTT. Unacknowledged data is capped by the window, so a high RTT throttles you until the window grows.

Watch out for

Worked example

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.

Check yourself

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?