Building a Reliable Stream

Opening Question

What if an application needs all the data, in order, despite loss?

Real-World Story

Imagine mailing a long document, too long for one envelope, as a stack of numbered pages: “Page 1 of 40,” “Page 2 of 40,” and so on. The postal service, exactly as in the postcard story, makes no promises about any individual page — some may arrive quickly, some slowly, and every so often, one goes missing entirely.

But because each page is numbered, the recipient can tell, just by looking at what’s arrived, exactly what’s missing. If pages 1 through 12 and 14 through 40 have shown up but page 13 never did, the recipient doesn’t need to guess — they can write back specifically: “please resend page 13.” And because every page carries its position in the sequence, the recipient can also reassemble the whole document in the correct order even if the pages didn’t arrive in that order, simply by sorting them by number before reading.

Nothing about the underlying mail service changed. It’s still exactly as unreliable and unordered as it was for postcards. What changed is that the sender and receiver are now doing extra work — numbering, tracking what’s arrived, requesting what’s missing — that turns an unreliable, unordered delivery service into something that behaves, from the reader’s perspective, like a single complete, correctly ordered document.

Worked Example

Suppose a sender transmits three numbered segments of data — call them segments 1, 2, and 3 — and, due to the vagaries of IP’s best-effort delivery (Ch. 10), they arrive at the receiver in the order 1, 3, 2, with the acknowledgement for segment 1 lost somewhere on the way back to the sender.

The receiver, tracking sequence numbers, can immediately tell segment 3 arrived out of order: it has segment 1, then segment 3, with a gap where segment 2 should be. It doesn’t discard segment 3 — it holds onto it, because it already knows exactly where segment 3 belongs once the gap is filled. When segment 2 finally arrives, the receiver has everything it needs to deliver segments 1, 2, and 3 to the application, in the correct order, even though they didn’t arrive in that order.

Meanwhile, the sender is dealing with a different problem: it never got an acknowledgement for segment 1, because that acknowledgement itself was lost, not because segment 1 failed to arrive. Since the sender can’t distinguish “segment 1 was lost” from “the acknowledgement for segment 1 was lost” — both look identical from where the sender sits, as silence — it does the only thing it safely can: it retransmits segment 1. The receiver, which already has segment 1, recognizes the retransmitted copy by its sequence number and simply discards the duplicate rather than delivering the same data to the application twice.

Notice what happened here: loss, reordering, and duplication all occurred, and the receiving application never saw any of it. It received exactly segments 1, 2, and 3, once each, in the correct order — the entire mess was absorbed and resolved below the application, using only sequence numbers and acknowledgements.

Core Intuition

TCP doesn’t change what IP actually does — packets can still be lost, delayed, duplicated, or reordered exactly as before. What TCP adds is a layer of bookkeeping, maintained jointly by both endpoints, that tracks what’s been sent, what’s been confirmed received, and what needs to be resent — turning an unreliable, unordered, packet-at-a-time delivery service into what looks, to the application on each end, like one continuous, ordered, reliable stream of bytes.

Technical Explanation

TCP (Transmission Control Protocol) is a transport-layer protocol that establishes a connection — a period of coordinated, shared state between two specific endpoints — before any application data flows, and formally closes it afterward. This is a fundamentally different model from UDP’s one-shot, connectionless datagrams (Ch. 13): TCP endpoints agree, in advance, to track an ongoing conversation together.

That agreement is established through a handshake: before sending any application data, the two endpoints exchange a short series of messages to confirm both sides are present, willing to communicate, and to agree on initial sequence numbers to use for tracking data going forward. Only once this handshake completes does either side send actual application data.

Unlike UDP, TCP does not preserve individual message boundaries. Instead, it exposes a byte stream: from the application’s perspective, sending data over TCP is like writing continuously into one long, ordered sequence of bytes, and reading from a TCP connection is like reading continuously from that same sequence on the other end — with no built-in notion of “here’s where one particular send ended and the next one began.” (An application that needs message boundaries on top of TCP has to add its own framing — a length field, a delimiter — since TCP itself won’t preserve one.) This is a deliberate, load-bearing design choice worth stating precisely, because it’s also one of the most common points of confusion: TCP transmits an ordered byte stream, not application-level messages.

Every byte in that stream is tracked by a sequence number, which lets the receiver know exactly where any given piece of data belongs in the overall stream, regardless of the order individual packets actually arrive in. The receiver sends back acknowledgements — confirmations of which sequence numbers it has successfully received — which tell the sender what’s gotten through safely.

When an acknowledgement doesn’t arrive within an expected window, the sender assumes the data (or the acknowledgement itself) was lost and performs a retransmission — resending the unacknowledged data. Because the receiver is tracking sequence numbers, it can perform duplicate detection: recognizing when a retransmitted segment is data it already has, and discarding the duplicate rather than passing it to the application a second time. The combination of sequence numbers, acknowledgements, and duplicate detection is also what enables ordering: the receiver can hold out-of-order data until the gaps before it are filled, then deliver everything to the application in the correct sequence, exactly as the worked example above showed.

It’s worth being precise about what TCP’s guarantees actually cover, because it’s easy to overstate them. TCP guarantees that data is delivered, in order, without duplication, into the receiving computer’s transport-layer buffer — not that the receiving application successfully processed it correctly, or even read it at all. A confirmed TCP delivery says “the bytes arrived intact and in order at the operating system level”; what the application on the other end actually did with them is a separate question TCP has no visibility into.

sequenceDiagram
    participant C as Client
    participant S as Server
    C->>S: SYN (I'd like to connect; my starting sequence number is X)
    S->>C: SYN-ACK (agreed; my starting sequence number is Y; I acknowledge X)
    C->>S: ACK (acknowledging Y — connection established)
    Note over C,S: Application data now flows as an ordered byte stream

Alt text: A three-message TCP handshake — SYN, SYN-ACK, ACK — establishing agreed starting sequence numbers before any application data is sent.

Packet-Journey Checkpoint

Before the café laptop’s browser can send its HTTPS request to example.net, it first completes a TCP handshake with the server — three messages exchanged before a single byte of the actual request has gone anywhere. Everything that follows — the encrypted TLS negotiation (Ch. 18) and the HTTP request and response themselves (Ch. 19) — rides inside this one ordered, reliable byte stream, trusting TCP to have already solved loss, reordering, and duplication underneath it.

Common Misconceptions

TCP transmits application messages rather than a byte stream.

Why it’s wrong: TCP has no concept of where one application “send” ends and the next begins — it only tracks a continuous sequence of bytes. Any message structure the application relies on has to be added by the application itself.

Correct intuition: Two separate send() calls from an application can arrive at the other end merged into one read, or one send() can arrive split across multiple reads — TCP makes no promises either way about preserving send boundaries.

Analogy: Numbered document pages: the postal analogy tracks bytes’ positions, the way page numbers track a document’s positions — neither one is really about preserving where the sender happened to pause.

One acknowledgement corresponds to one application request.

Why it’s wrong: Acknowledgements confirm receipt of ranges of bytes in the stream, entirely independent of how the application chose to chunk its own sends.

Correct intuition: An acknowledgement is bookkeeping about the byte stream’s integrity, not a receipt for a specific application-level action.

Analogy: Confirming that pages 1 through 20 of the document arrived says nothing about whether that corresponds to one chapter, two chapters, or half a chapter, from the author’s perspective.

TCP guarantees that the remote application processed the data.

Why it’s wrong: TCP’s guarantees stop at reliable, ordered, duplicate-free delivery into the receiving transport-layer buffer. Whether the receiving application read that data, processed it correctly, or crashed immediately afterward is entirely outside TCP’s visibility.

Correct intuition: A successful TCP delivery is a strong claim about the network and the transport layer, and a claim of exactly zero strength about the receiving application’s own behavior.

Analogy: A courier confirming a stack of numbered pages was delivered to the front desk says nothing about whether anyone in the building actually read them.

Retransmission means the network itself repaired the packet.

Why it’s wrong: IP has no repair mechanism (Ch. 10); a lost or corrupted packet is simply gone. Retransmission is the sending endpoint noticing an acknowledgement never arrived and sending a fresh copy — an end-to-end response to loss, not an in-network fix.

Correct intuition: Nothing “fixes” a lost packet; TCP just notices the absence and tries again, using entirely new packets.

Analogy: A missing page 13 isn’t found and forwarded by the postal service — the sender has to print and mail a fresh copy from scratch.

A TCP connection is a permanent physical circuit.

Why it’s wrong: A TCP connection is shared state — sequence numbers, acknowledgement tracking, buffers — maintained in software at both endpoints. No dedicated physical path is reserved for it; its packets travel over the same shared, best-effort IP infrastructure as everything else, potentially via different physical paths at different times.

Correct intuition: “Connection” here means agreed, tracked state between two endpoints, not a private wire between them.

Analogy: Two pen pals maintaining an ongoing, numbered correspondence share an agreement, not a dedicated road between their two houses.

Practical Implications

Understanding TCP as byte-stream-not-messages explains a whole class of application bugs where data gets “merged” or “split” unexpectedly — the fix is always for the application to add its own explicit framing, because TCP was never going to preserve message boundaries on its own. It also reframes what “the connection dropped” or “the request timed out” actually mean: a TCP-layer problem (lost packets triggering repeated, ultimately failed retransmission) is a different failure from an application-layer problem (the server received everything fine but the application logic hung) — and distinguishing the two is exactly the kind of layer-specific diagnosis Chapter 29 will return to.

Key Takeaway

TCP creates an ordered, reliable byte stream by maintaining shared endpoint state above an unreliable packet service.

What to Remember

  • TCP establishes a connection via a handshake before any application data is sent, and formally closes it afterward.
  • TCP exposes an ordered byte stream, not discrete application messages — message boundaries, if needed, are the application’s own responsibility.
  • Sequence numbers let the receiver detect gaps, reorder out-of-sequence data, and identify duplicates.
  • Acknowledgements tell the sender what’s been received; a missing acknowledgement triggers retransmission.
  • Duplicate detection lets the receiver safely discard retransmitted data it already has, without ever delivering it twice.
  • TCP’s guarantee stops at reliable delivery into the receiving transport buffer — it says nothing about what the receiving application actually did with the data.

The Next Obvious Question

How can reliability avoid overwhelming the receiver or the network itself?