The Layer Model
The internet stack is typically described in four layers:
| Layer | Protocol | What it does |
|---|---|---|
| Application | HTTP, gRPC, DNS | Your actual data |
| Transport | TCP, UDP | End-to-end delivery, ports |
| Internet | IP | Routing across networks |
| Link | Ethernet, Wi-Fi | Physical hop delivery |
Each layer wraps the one above in a header. A TCP segment becomes an IP packet becomes an Ethernet frame. On receipt, each layer strips its header and passes the payload up.
IP — Best-Effort Routing
IP (Internet Protocol) routes packets between machines. It provides no guarantees: packets can be dropped, reordered, or duplicated. Every IP packet carries source + destination address and a TTL that decrements at each router hop (preventing infinite loops).
IPv4 addresses are 32 bits (4.3B addresses — exhausted). IPv6 is 128 bits. NAT (Network Address Translation) lets millions of devices share one public IPv4 address by mapping private IP:port pairs.
TCP — Reliable Ordered Delivery
TCP adds reliability on top of IP:
- Three-way handshake (SYN → SYN-ACK → ACK) establishes a connection and synchronizes sequence numbers.
- Sequence numbers ensure in-order delivery even if packets arrive out-of-order.
- Acknowledgements + retransmission: unacknowledged segments are resent after a timeout.
- Flow control: the receiver advertises a window size — the max unacknowledged bytes the sender may have in-flight.
- Congestion control (CUBIC, BBR): the sender probes available bandwidth and backs off on loss.
Client Server
|---SYN (seq=x)----->|
|<--SYN-ACK(seq=y)---|
|---ACK(seq=y+1)---->|
| Connected |
TCP connection teardown uses a four-way FIN handshake. The TIME_WAIT state (2× MSL, typically 60–120s) prevents stale packets from old connections being misinterpreted by new ones on the same port.
Sockets
The socket API is the OS abstraction for network I/O:
int fd = socket(AF_INET, SOCK_STREAM, 0); // TCP socket
connect(fd, &server_addr, sizeof(server_addr));
write(fd, "GET / HTTP/1.1\r\n\r\n", 18);
read(fd, buf, sizeof(buf));
close(fd);
A socket file descriptor works like any other fd — you can read/write/select/poll/epoll on it. The kernel handles TCP segmentation, retransmission, and reassembly transparently.
epoll — Scalable I/O Multiplexing
select() and poll() scan all registered fds on each call — O(n). epoll (Linux) maintains a kernel-side interest list and returns only ready fds — O(1) for the common case. Node.js, Nginx, and virtually every high-performance server use epoll (or kqueue on macOS) under the hood.
TCP Performance Pitfalls
- Nagle’s algorithm batches small writes to avoid network overhead. For latency-sensitive apps (game servers, trading), disable it with
TCP_NODELAY. - Head-of-line blocking: a lost packet at the front of the stream blocks all subsequent data until it’s retransmitted. HTTP/2 multiplexing over TCP doesn’t eliminate this — it’s inherent to TCP’s ordered delivery guarantee. HTTP/3 solves this with QUIC (UDP-based).
- Connection setup latency: 1.5 RTTs for the handshake + 1 RTT for TLS = significant for short-lived requests. HTTP Keep-Alive and connection pooling reuse connections.