TCP connections
TCP
Reliable, ordered byte streams between programs: handshake, acknowledgements, retransmission.
TCP numbers every byte, has the receiver acknowledge them, resends what is lost and delivers in order. It opens with a three-way handshake and closes with FINs. It adapts its speed to the path with congestion control. A closed port answers with a reset.
How TCP works, step by step
IP delivers packets to a machine, maybe out of order, maybe not at all. TCP (RFC 9293) turns that into a reliable, ordered stream of bytes between two programs. Every segment carries a header of at least 20 bytes: source and destination ports, a 32-bit sequence number, a 32-bit acknowledgement number, flags (SYN, ACK, FIN, RST, PSH, URG, ECE, CWR), a window and a checksum. A connection is named by four numbers: source address and port, destination address and port.
- SYN. The client picks a random initial sequence number and sends a segment with SYN set. It also offers options: its MSS (1460 bytes on Ethernet: 1500 minus 20 for IP and 20 for TCP), selective acknowledgement, a timestamp and window scaling.
- SYN-ACK. The server picks its own initial number and acknowledges the client's plus one.
- ACK. The client acknowledges the server's. Three segments, and both sides know the other's numbering.
- Data. Each byte has a sequence number. The receiver acknowledges the next byte it expects, and advertises its window: how much more it can take. A sender that gets no acknowledgement in time, or three duplicate ones, retransmits. The receiver puts out-of-order bytes back in order before the program reads them.
- Congestion control. The sender also keeps a congestion window, grown while acknowledgements return and cut when loss shows the path is full. The slower of the two windows sets the pace.
- FIN. Each side closes its direction with FIN, acknowledged by the other. The side that closed first waits in TIME-WAIT, 60 seconds on Linux, so a late segment cannot be mistaken for part of a new connection with the same four numbers.
A SYN to a port where nothing listens is answered with RST, and the program sees "Connection refused" at once.
What it looks like on a Linux machine
In the TCP and UDP lab a laptop sends hello to an echo service on port 7 while tcpdump
records. Options trimmed, the whole connection is ten segments:
$ tcpdump -nn -i eth0 tcp port 7
IP 10.0.1.10.43273 > 10.0.2.10.7: Flags [S], seq 1146127641, win 64240, options [mss 1460,...], length 0
IP 10.0.2.10.7 > 10.0.1.10.43273: Flags [S.], seq 2309196558, ack 1146127642, win 65160, options [mss 1460,...], length 0
IP 10.0.1.10.43273 > 10.0.2.10.7: Flags [.], ack 1, win 502, length 0
IP 10.0.1.10.43273 > 10.0.2.10.7: Flags [P.], seq 1:7, ack 1, win 502, length 6
IP 10.0.2.10.7 > 10.0.1.10.43273: Flags [.], ack 7, win 510, length 0
IP 10.0.1.10.43273 > 10.0.2.10.7: Flags [F.], seq 7, ack 1, win 502, length 0
IP 10.0.2.10.7 > 10.0.1.10.43273: Flags [P.], seq 1:7, ack 8, win 510, length 6
IP 10.0.1.10.43273 > 10.0.2.10.7: Flags [.], ack 7, win 502, length 0
IP 10.0.2.10.7 > 10.0.1.10.43273: Flags [F.], seq 7, ack 8, win 510, length 0
IP 10.0.1.10.43273 > 10.0.2.10.7: Flags [.], ack 8, win 502, length 0
. is ACK. After the handshake tcpdump numbers bytes from the start, so the six bytes of hello and a newline
are 1:7, and ack 7 means "everything before byte 7 arrived". A FIN takes one sequence number too, which is why
the replies say ack 8. The win 502 is scaled: the handshake agreed on a factor of 128.
ss -tan lists connections by state: LISTEN, ESTAB, TIME-WAIT. The laptop's port, 43273, is
ephemeral, taken from /proc/sys/net/ipv4/ip_local_port_range, 32768 to 60999 on Linux.
TCP vs UDP
| TCP | UDP | |
|---|---|---|
| Connection | Three-way handshake before any data | None: the first datagram carries data |
| Delivery | Acknowledged, lost data resent | Sent once, no acknowledgement |
| Order | Bytes delivered in order | Each datagram on its own, in any order |
| Unit | A stream of bytes, no message boundaries | Datagrams, boundaries kept |
| Speed control | Flow control and congestion control | None; the program decides |
| Header | 20 to 60 bytes | 8 bytes |
| Closed port | RST: "Connection refused" | ICMP port unreachable, often unseen |
| Used by | Web, SSH, mail, file transfer, BGP | DNS, DHCP, voice, video, games, WireGuard, QUIC |
Choose TCP when every byte must arrive in order and a short wait is acceptable. Choose UDP when a late packet is worthless, or when the program wants its own recovery: HTTP/3 runs over QUIC, which is built on UDP.
What goes wrong, and how you see it
Connection refused is a quick RST: nothing listens there, or a service is bound to 127.0.0.1 only. ss -tln
on the server shows the address it listens on. Connection timed out means SYNs got no answer at all: a firewall
dropping them, or a host that is down. Linux retries the SYN six times by default before giving up, about two
minutes. Retransmissions in a capture, or a climbing retrans count in ss -ti, mean loss on the path, and
throughput falls with it; the TCP performance lab measures it. Thousands of TIME-WAIT
entries on a busy client can exhaust its ephemeral ports.
Questions people ask
Is TCP faster than UDP? No: UDP has no handshake and no waiting for lost data. TCP is slower to start and to recover, in exchange for complete, ordered data.
Why is it called a three-way handshake? Three segments: SYN, SYN-ACK, ACK. Each side sends its starting number and has it acknowledged.
Can TCP and UDP use the same port number? Yes. TCP port 53 and UDP port 53 are different sockets, and DNS uses both.