The TCP/IP model is the foundation of all modern internet communication. Understanding it makes you a better debugger, a more thoughtful architect, and a more confident engineer when things go wrong at 3 am.
The Four Layers
Unlike the OSI model's seven layers (which is a conceptual reference model), TCP/IP condenses the stack into four practical layers, each with a clear responsibility:
- Application Layer — The protocols your applications speak: HTTP/HTTPS, DNS, SMTP, FTP, WebSocket.
- Transport Layer — Manages end-to-end communication. Two protocols live here: TCP (reliable) and UDP (fast).
- Internet Layer — Handles logical addressing and routing. IP (v4 and v6), ICMP, and ARP operate here.
- Link Layer — The physical transmission medium: Ethernet frames, Wi-Fi (802.11), and MAC addressing.
When you visit a website, data travels down the stack on the sender side (each layer adding a header) and back up the stack on the receiver side (each layer stripping its header). This is called encapsulation and decapsulation.
The Three-Way Handshake
Before any data can flow over a TCP connection, both sides must agree to communicate. This is called the three-way handshake and it establishes sequence numbers that guarantee in-order delivery.
Client ──SYN (seq=x) ────────────────→ Server
Client ←─ SYN-ACK (seq=y, ack=x+1) ── Server
Client ──ACK (ack=y+1) ─────────────→ Server
✓ Connection established
Data can now flow in both directions.
The handshake takes at minimum one round-trip time (RTT) before the first byte of application data can be sent. This is a key reason HTTPS connections feel slower on high-latency links — TLS adds another 1–2 RTTs on top.
TCP vs UDP: The Core Trade-off
TCP provides reliability guarantees through acknowledgements, retransmission of lost packets, and flow control. UDP provides none of these — it simply fires packets and hopes they arrive.
That sounds like UDP loses every time, but the opposite is true for latency-sensitive applications. In live video streaming, a dropped packet is better than a delayed one — receiving a video frame 500ms late is far worse than skipping it entirely. Online gaming, DNS lookups, and VoIP all use UDP for this reason.
import socket
# TCP client — connection-oriented
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('example.com', 80))
s.sendall(b'GET / HTTP/1.1\r\nHost: example.com\r\n\r\n')
# UDP client — connectionless (fire and forget)
u = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
u.sendto(b'ping', ('8.8.8.8', 53))
HTTP/2 and Multiplexing
HTTP/1.1 opens a separate TCP connection for each resource, or queues requests on a single connection (head-of-line blocking). HTTP/2 introduces multiplexing — many logical streams over a single TCP connection — dramatically reducing connection overhead.
Every web request you make triggers this entire stack — DNS lookup, TCP handshake, TLS negotiation, HTTP request/response — in under 100ms on a good connection. That is a remarkable feat of engineering.