TCP
A reliable ordered byte stream over an unreliable network: the handshake, sequence numbers, acknowledgments, loss recovery, flow control, congestion control, head-of-line blocking and the connection lifecycle.
IP loses, duplicates, reorders and delays packets and says nothing about it; TCP turns that into a connection over which bytes arrive exactly once, in order, at a rate the receiver and the network can absorb — by numbering every byte, acknowledging what arrived, retransmitting what did not, and windowing what is in flight — and it hands the application a stream, not messages.
SYN, SYN-ACK, ACK: three segments in which each side proposes its initial sequence number and hears the other’s acknowledged, options are agreed, and the server moves the connection from a half-open queue to the accept queue — one full round trip before a single byte of data, which is the cost every short connection pays.
Every byte in a TCP stream has a number; a segment carries the number of its first byte, an ACK carries the number of the next byte the receiver wants, and out-of-order segments wait in a reassembly buffer until the hole before them is filled — which is why numbers are byte offsets, why cumulative ACKs cannot describe a gap, and why SACK exists.
When a segment is lost, TCP learns it either from three duplicate ACKs (fast, one round trip) or from a retransmission timer (slow, at least 200 ms on Linux and doubling), retransmits, and — because loss is also read as congestion — cuts its sending rate; the application sees only a stall, and a loss rate of 1% can cost most of a link’s throughput.
The receiver tells the sender, in every ACK, how many more bytes it has room for; the sender never has more than that in flight; when the application stops reading, the buffer fills, the window goes to zero and the sender stops — so a slow consumer throttles a fast producer all the way back through the network, which is backpressure by design.
No router tells a sender how much capacity is left, so the sender probes: it keeps a congestion window that grows exponentially, then linearly, and shrinks sharply when loss or an ECN mark says a queue is full — the sawtooth that shares links fairly, and the mechanism that CUBIC and BBR each implement with different signals.
Because TCP delivers bytes strictly in order, one lost segment holds back every byte behind it even when those bytes have already arrived — a property that HTTP/1.1 dodged with six parallel connections, that HTTP/2’s multiplexing made worse by putting every request on one stream, and that HTTP/3 addresses with QUIC’s independent streams, without eliminating ordering costs inside a stream or in the application.
A TCP connection is a state machine on both ends: established by the handshake, torn down by a FIN in each direction, aborted by RST — and the side that closes first sits in TIME_WAIT for a minute holding the port pair, while a side that never calls close() sits in CLOSE_WAIT forever; these states explain ECONNRESET, ephemeral-port exhaustion, descriptor leaks, and why many short connections are expensive.