System Design: Network Protocols — TCP, UDP, HTTP/2, HTTP/3, WebSocket, DNS, TLS Handshake, Connection Pooling

Understanding network protocols is fundamental to designing performant distributed systems. Every API call, database query, and cache lookup traverses the network stack. This guide covers the protocols that backend engineers need to know — TCP, UDP, HTTP versions, WebSocket, DNS, and TLS — with focus on how they affect system design decisions and performance.

TCP: Reliable, Ordered Delivery

TCP (Transmission Control Protocol) provides reliable, ordered, error-checked delivery of bytes. The three-way handshake establishes a connection: client sends SYN, server responds SYN-ACK, client sends ACK. This takes 1 round-trip time (RTT) before any data is sent. For a server 50ms away, the handshake adds 50ms to the first request. TCP guarantees: every byte sent is received in order, data integrity is verified with checksums, and lost packets are automatically retransmitted. Flow control: TCP uses a sliding window to prevent the sender from overwhelming the receiver. The receiver advertises how much buffer space it has; the sender sends at most that much before waiting for acknowledgment. Congestion control: TCP reduces its sending rate when network congestion is detected (packet loss). Algorithms like CUBIC and BBR adapt the sending rate to available bandwidth. Head-of-line blocking: if one packet is lost, all subsequent packets (even from unrelated streams) are blocked until the lost packet is retransmitted. This is a fundamental TCP limitation that HTTP/3 (QUIC) solves.

HTTP/1.1, HTTP/2, and HTTP/3

HTTP/1.1: one request per TCP connection at a time. The browser opens 6 parallel connections per domain to work around this. Each connection requires a separate TCP handshake and TLS handshake. Keep-alive: reuse the same connection for multiple sequential requests (default in HTTP/1.1), avoiding the handshake overhead for subsequent requests. HTTP/2: multiplexing — multiple requests and responses share a single TCP connection interleaved as binary frames. No head-of-line blocking at the HTTP level (but TCP head-of-line blocking still exists). Header compression (HPACK) reduces repetitive header overhead by 90%+. Server push: the server can send resources proactively (deprecated in practice — caching headers work better). HTTP/3: replaces TCP with QUIC (a UDP-based transport with built-in encryption). Eliminates TCP head-of-line blocking: if one QUIC stream loses a packet, other streams on the same connection are unaffected. 0-RTT connection establishment for repeat visitors (QUIC remembers the previous session). Faster connection migration when the client changes networks (WiFi to cellular) because QUIC connections are identified by a connection ID, not IP+port. HTTP/3 is supported by all major browsers and CDNs (Cloudflare, Google, AWS CloudFront).

WebSocket: Bidirectional Real-Time Communication

WebSocket provides full-duplex communication over a single TCP connection. After an HTTP Upgrade handshake, the connection switches from HTTP to the WebSocket protocol. Both client and server can send messages at any time without polling. Use cases: real-time chat, live notifications, collaborative editing, live sports scores, stock price tickers, multiplayer games. WebSocket vs HTTP polling: polling sends a request every N seconds, wasting bandwidth when there is no new data and adding latency (up to N seconds). WebSocket delivers messages instantly with no wasted requests. WebSocket vs Server-Sent Events (SSE): SSE is server-to-client only (unidirectional) over a standard HTTP connection. Simpler than WebSocket, supported by HTTP/2 multiplexing, and automatically reconnects. Use SSE when you only need server-to-client push (notifications, live feeds). Use WebSocket when you need bidirectional communication (chat, collaborative editing). Scaling WebSocket: each WebSocket connection is a persistent TCP connection consuming server memory. A server with 10GB RAM can handle approximately 100K-1M concurrent WebSocket connections (depending on per-connection memory). For millions of connections, use a pub/sub system (Redis Pub/Sub) to fan out messages to WebSocket servers.

TLS Handshake and Performance

TLS (Transport Layer Security) encrypts HTTP traffic (HTTPS). The TLS 1.3 handshake requires 1 RTT: client sends ClientHello (supported cipher suites, key share), server responds ServerHello (chosen cipher suite, key share, encrypted certificate and Finished). After 1 RTT, encrypted data can flow. TLS 1.2 required 2 RTTs. TLS 1.3 also supports 0-RTT resumption for repeat visitors: the client sends encrypted data in the first packet using a pre-shared key from a previous session. This eliminates the handshake latency entirely for returning users. Security trade-off: 0-RTT data is vulnerable to replay attacks (an attacker can capture and resend the first packet). Only use 0-RTT for idempotent requests (GET), never for state-changing operations (POST). Performance optimization: (1) Use TLS 1.3 (faster handshake). (2) Enable OCSP stapling (the server includes the certificate status, avoiding a separate OCSP query by the client). (3) Use connection pooling and keep-alive to amortize the handshake cost across multiple requests. (4) Terminate TLS at the edge (CDN or load balancer) to minimize the RTT to the user.

DNS Resolution and Optimization

DNS (Domain Name System) translates domain names to IP addresses. Resolution path: browser cache -> OS cache -> DNS resolver (ISP or 8.8.8.8) -> root nameserver -> TLD nameserver (.com) -> authoritative nameserver (your domain). A cold DNS lookup can take 50-200ms across multiple hops. Optimization: (1) DNS prefetching — browsers proactively resolve domains of links on the page. Add link rel=”dns-prefetch” href=”https://api.example.com” in HTML. (2) Low TTL for failover — set DNS TTL to 60 seconds for services that need fast failover. When a server fails, update the DNS record and traffic shifts within 60 seconds. (3) High TTL for stability — set TTL to 3600 seconds (1 hour) for stable services to reduce DNS query volume and improve resolution speed (cached results). (4) DNS round-robin — return multiple A records. The client tries them in order, providing basic load balancing and failover. (5) Anycast DNS — deploy DNS servers at multiple geographic locations sharing the same IP address. The network routes DNS queries to the nearest server. All major DNS providers (Cloudflare, Route 53, Google Cloud DNS) use anycast.

Connection Pooling

Establishing a new connection (TCP handshake + TLS handshake) is expensive: 1-3 RTTs before any application data flows. Connection pooling reuses established connections across multiple requests. Database connection pooling: a pool of pre-established database connections (PgBouncer for PostgreSQL, HikariCP for JDBC). When the application needs a database connection, it borrows one from the pool. After the query completes, the connection is returned to the pool (not closed). This amortizes the handshake cost and limits the number of database connections (PostgreSQL has a maximum connection limit, typically 100-500). HTTP connection pooling: HTTP clients maintain a pool of keep-alive connections to frequently accessed servers. The Go http.Client, Java HttpClient, and Python requests.Session all implement connection pooling by default. In microservices, connection pooling between services eliminates the per-request handshake overhead: a warm pool of 10 connections to the auth service handles 1000 requests per second without any new connections. Pool sizing: too small (requests queue waiting for a connection), too large (wastes server resources and may exceed the upstream connection limit). Start with pool_size = 2 * num_cpu_cores + 1 and tune based on latency percentiles.

Scroll to Top