Networking
TCP — Transmission Control Protocol
A connection-oriented transport built on top of IP that turns IP's best-effort, unordered packet delivery into a reliable, ordered byte stream — at the cost of connection setup, state, and variable latency.
- What it is
- A connection-oriented transport that turns IP's unreliable packets into a reliable, ordered byte stream.
- Mental model
- A phone call: you dial first (handshake), talk in order, and both sides know if the line drops.
- Key numbers
- 3-way handshake before any data flows; minimum 20-byte header; retransmission on unacknowledged segments.
- Reach for it when
- You need guaranteed, in-order delivery and can accept connection setup cost and variable latency.
Three-way handshake
Before any data flows, both sides synchronize initial sequence numbers:
- SYN — client picks an initial sequence number and sends it.
- SYN-ACK — server acknowledges the client's SYN and sends its own.
- ACK — client acknowledges the server's SYN. Connection is now established.
This is why TCP has connection setup latency that UDP doesn't — at least one full round-trip before the first byte of application data can even be sent.
How reliability actually works
Every byte sent has a sequence number. The receiver sends back acknowledgments (ACKs) naming the next byte it expects. If the sender doesn't see an ACK within a computed retransmission timeout, it resends the unacknowledged data — TCP doesn't require IP to retry anything; it achieves reliability entirely by retransmitting from the endpoints.
Because segments carry sequence numbers, the receiver can also reorder packets that arrived out of order (a normal occurrence when IP routes packets independently) before handing bytes to the application in the correct order.
Flow control and windows
The receiver advertises a window size — how many unacknowledged bytes it's currently willing to buffer — preventing a fast sender from overrunning a slow receiver. Separately, congestion control (e.g. slow start, congestion avoidance) makes the sender ramp up its send rate cautiously and back off sharply on loss, to avoid overwhelming the network path itself rather than just the receiver.
Connection teardown
A graceful close uses a FIN/ACK exchange from each side independently
(TCP connections are full-duplex, so each direction closes separately).
The side that closes first enters TIME_WAIT for a period to
absorb any stray late segments before fully releasing the connection —
the reason a server under heavy short-lived-connection churn can
accumulate many sockets in that state.
TCP vs. UDP: why and when
Neither is "better" — they're opposite tradeoffs of the same problem, and the right choice depends entirely on what a dropped or out-of-order packet should mean to the application. A common early misconception is that "TCP/IP" and "UDP" are somehow different families — they're not. Both TCP and UDP sit at the exact same layer, built directly on the exact same IP underneath; "TCP/IP" is just the historical name of the whole suite, not a claim that UDP is somehow separate from it. See OSI vs. TCP/IP Model for the full explanation of that naming.
| TCP | UDP | |
|---|---|---|
| Connection setup | 3-way handshake, one RTT before data | None — first packet is the data |
| Delivery guarantee | Retransmits until acknowledged | None — lost is lost |
| Ordering | Guaranteed, reassembled by sequence number | None — datagrams can arrive in any order |
| Message boundaries | None — byte stream | Preserved — one send is one receive |
| Header overhead | 20+ bytes | 8 bytes |
| Congestion control | Built in — backs off automatically under network load | None by default — a UDP sender can keep blasting a congested link unless the application adds its own backoff |
| Choose it when | Correctness of every byte matters more than latency (file transfer, control commands, FOTA) | Low, predictable overhead matters more than guaranteed delivery, or you'll build your own reliability (real-time media, telemetry, discovery) |
Ask one question: is a late retransmission worse than the original loss?
- Sending a 10 MB firmware image — a missing byte silently corrupts the whole image. A late-but-correct byte is always better than a fast-but-wrong one. TCP's retransmission is exactly what you want, and its extra round trips are a cost worth paying.
- Streaming a live video call — a frame that arrives 400ms late (because TCP is busy retransmitting an old, now-irrelevant frame) is worse than that frame never arriving at all — the viewer just wants the freshest frame available. UDP's willingness to simply drop it and move on is the correct behavior here, not a limitation.
Everything in the table above falls out of that one distinction. It's also why a newer transport, QUIC, exists at all: it builds TCP-like reliability on top of UDP instead of using TCP directly, to get reliability without inheriting some of TCP's specific costs — see that page for why that's not a contradiction.
Real-world applications
- HTTP/HTTPS — essentially all web traffic rides on TCP, which is why a slow connection setup is felt as page-load latency.
- Firmware-over-the-air (FOTA) updates — reliable, ordered delivery matters enormously for shipping a firmware image — a corrupted or reordered byte can brick a device.
- SSH/remote device management — interactive terminal sessions to embedded Linux boards depend on TCP's ordering guarantees.
- MQTT/AMQP — both messaging protocols (see MQTT, AMQP) are built on top of TCP specifically for its reliability.
Gotchas
- TCP is a byte stream, not a message protocol — a single
send()call is not guaranteed to correspond to a singlerecv()on the other end. Applications that need message framing add their own (length prefixes, delimiters). - Nagle's algorithm can batch small writes, adding latency for latency-sensitive small-message traffic —
TCP_NODELAYdisables it when that matters. - A "connection" is only a local abstraction: intermediate routers hold no per-connection state at all — see IP.