S061 · Networking I — TCP/IP, DNS, Sockets
How bytes cross the internet.
Module M07: Systems & Infrastructure · Session 61 of 130 · Track: SYS 📡
What you'll be able to do after this session
- Explain Networking I to a friend in one minute, out loud, no notes.
- Recognise it when you see it in real code / systems / papers.
- Complete the hands-on exercise at the end without looking up help.
Prerequisites
If you can't answer these in 30 seconds each, redo the linked session first:
Pre-read (skim before the session)
- 🎥 Intuition (5–15 min) · How does the INTERNET work? (Vox / DNS explainer) — 6-minute animated walkthrough of a URL → packets → server round-trip.
- 🎥 Deep dive (30–60 min) · Computer Networking Full Course (freeCodeCamp) — the OSI/TCP-IP stack from cable to HTTP with real captures.
- 🎥 Hands-on demo (10–20 min) · Sockets in Python (Tech With Tim) — writes a TCP client/server from scratch in 15 min.
- 📖 Canonical article · RFC 793 — Transmission Control Protocol — the original TCP spec, still the reference.
- 📖 Book chapter / tutorial · Beej's Guide to Network Programming — the friendliest sockets tutorial ever written.
- 📖 Engineering blog · Cloudflare — A history of HTTP — how TCP/TLS/HTTP evolved and why QUIC exists.
(a) Intuition · 5 min
The one-paragraph mental model. Why this concept exists, what problem it solves, and the analogy that makes it stick.
In one sentence: How bytes cross the internet.
Think about mailing a physical letter across the country. You write the message, put it in an envelope, address it, drop it in a postbox, and trust an invisible chain of trucks, planes, and sorting centres to hand it to the right person. The internet works exactly the same way — except the letter is a few thousand bytes, the envelope is a "packet", the address is an IP number, and the sorting centres are routers. The genius of the design is that no single machine knows the whole route; each router just knows "for that destination, go this way", and packets hop from one to the next until they arrive.
IP (Internet Protocol) is the addressing + routing layer — it delivers packets, but doesn't promise they arrive in order or at all. TCP (Transmission Control Protocol) sits on top and adds the guarantees: "these bytes will arrive, in order, exactly once, or you'll get an error". It does this with sequence numbers, ACKs, and retransmissions — like registered mail with tracking. DNS (Domain Name System) is the phonebook: you type google.com, DNS translates it to 142.250.192.14 (an IP address) so IP knows where to route.
A socket is your program's handle on this whole machinery. When you open("http://x.com"), under the hood Python opens a TCP socket to port 80, which triggers a 3-way handshake (SYN → SYN/ACK → ACK), which sends packets through your ISP's routers to the destination server, whose kernel hands them to a listening process.
Forever-gotcha: TCP is a byte stream, not a message stream. If you send("hello") then send("world"), the receiver may recv() and get "helloworld", or "hell" then "oworld". There are no message boundaries — you must add them yourself (length prefix, delimiter, or a framing protocol like HTTP).
(b) Visual walkthrough · 15 min
Diagrams, worked examples, step-by-step visuals.
Here's what happens when you type curl https://example.com and hit Enter:
Layer cake (TCP/IP model — the one that's actually used):
| Layer | Job | Example |
|---|---|---|
| Application | What you speak | HTTP, SMTP, SSH, DNS |
| Transport | Reliable / unreliable byte channel | TCP (reliable), UDP (fire-and-forget) |
| Internet | Routing packets across networks | IP (v4 = 32-bit, v6 = 128-bit) |
| Link | Actual wire / wifi frame | Ethernet, Wi-Fi 802.11 |
Worked example — the 3-way handshake:
- Client picks random
seq = 1000, sendsSYN(seq=1000). - Server picks random
seq = 5000, repliesSYN(seq=5000), ACK(ack=1001)— meaning "I got up to 1000, next expected is 1001". - Client replies
ACK(ack=5001). Now both sides agree on starting sequence numbers and the connection is established.
Worked example — DNS resolution for blog.example.com:
- Your machine asks your resolver (e.g. 1.1.1.1 or your ISP's).
- Resolver asks a root server: "who handles
.com?" → gets pointer to a TLD server. - Resolver asks the TLD server: "who handles
example.com?" → gets pointer tons1.example.com(authoritative). - Resolver asks the authoritative: "what's
blog.example.com?" → getsA 93.184.216.34. - Resolver caches the answer for the TTL (say 300s) and returns it to you.
Every subsequent lookup within TTL is served from cache — which is why DNS changes take time to propagate.
(c) Hands-on · 20 min
Code you type and run. Not pseudo-code — real, runnable, copy-pasteable.
Below: a minimal TCP echo server + client in pure Python (no frameworks). Save as echo.py.
# echo.py — run server in one terminal, client in another
import socket, sys, threading
HOST, PORT = "127.0.0.1", 9999
def server():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((HOST, PORT))
s.listen(5)
print(f"[server] listening on {HOST}:{PORT}")
while True:
conn, addr = s.accept()
print(f"[server] connected by {addr}")
threading.Thread(target=handle, args=(conn,), daemon=True).start()
def handle(conn):
with conn:
while True:
data = conn.recv(1024) # blocks until bytes arrive
if not data: # empty = peer closed
print("[server] client hung up")
return
print(f"[server] recv {len(data)} bytes: {data!r}")
conn.sendall(b"echo: " + data)
def client(msg: str):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect((HOST, PORT))
s.sendall(msg.encode())
reply = s.recv(4096)
print(f"[client] got {reply!r}")
if __name__ == "__main__":
if sys.argv[1] == "server":
server()
else:
client(" ".join(sys.argv[2:]) or "hello")Run it:
# terminal 1
python3 echo.py server
# terminal 2
python3 echo.py client hello world
python3 echo.py client "another message"Checklist — what to observe:
- In terminal 1, watch
connected by ('127.0.0.1', <port>). The client's port is random and different each run — that's the ephemeral port your OS picks. - Try
ss -tnp | grep 9999(Linux) while the client is running. You'll see two rows: oneLISTENand oneESTAB. - Kill the client with Ctrl-C during
recv. Server prints "client hung up" — that's TCP's FIN travelling back. - Try sending two
sendallcalls back-to-back in the client. On the server,recv(1024)may return both concatenated — proof that TCP is a byte stream, not a message stream. - Run
sudo tcpdump -i lo -X port 9999in a third terminal and re-run the client. You'll literally see the SYN / SYN-ACK / ACK / PSH / FIN packets.
Try this modification: wrap the protocol with a 4-byte length prefix (struct.pack("!I", len(msg)) + msg) so the server can safely reassemble messages even when TCP splits or merges them. This is how real protocols (Kafka, gRPC framing) solve the byte-stream problem.
(d) Production reality · 10 min
How this shows up in real systems, gotchas, war stories, common bugs.
War story #1 — the 500ms mystery. A team noticed API latency was exactly 500ms extra on some requests. Turned out to be Nagle's algorithm interacting with delayed-ACK: the client sent a small header, then a small body, and TCP held the second write hoping to batch it. Fix: setsockopt(TCP_NODELAY, 1) for small-payload latency-sensitive protocols.
War story #2 — DNS TTL disaster. During a failover, an ops team lowered the DNS TTL from 3600 to 60 seconds after the incident started. Because the old TTL was still cached at millions of ISP resolvers, users kept hitting the dead server for the next hour. Rule: lower TTLs before planned failovers, not after.
War story #3 — connection limits. A Node service crashed with "too many open files". Each socket = one file descriptor; the default ulimit is 1024. Under high load with lots of long-lived connections you exhaust FDs. Real teams tune ulimit -n 65535 and use connection pools.
Common bugs & how top teams handle them:
- Half-open connections. Client's wifi drops without sending FIN. Server keeps the socket forever until TCP keepalive (default 2h!) kicks in. Real services set
SO_KEEPALIVEwith 60s idle + application-level heartbeats. - TIME_WAIT flood. After close, the initiator holds the socket in
TIME_WAITfor 2×MSL (~2 min). High-traffic proxies can run out of ephemeral ports. Fix with connection reuse (HTTP keep-alive),SO_REUSEADDR, and — carefully —net.ipv4.tcp_tw_reuse. - DNS as a SPOF. If your app calls
getaddrinfo()on every request without caching, your DNS provider outage = your outage. Netflix and Facebook run internal caching resolvers + short-TTL health-based routing (via something like AWS Route 53 or their own). - Head-of-line blocking. In HTTP/1.1, one slow response blocks the pipeline behind it. HTTP/2 multiplexed streams; HTTP/3 went further and moved to UDP (QUIC) precisely to escape TCP's head-of-line blocking at the transport layer too.
Modern cloud shops (Cloudflare, Google) run at such scale that a single misconfigured MTU or a single BGP route leak becomes global news. If you take one thing away: the network is not reliable, ordered, or fast — TCP just does a good enough job hiding that from you most of the time.
(e) Quiz + exercise · 10 min
- Explain the 3-way handshake in 3 sentences, naming each packet type and what fields matter.
- Why is TCP called a "byte stream" and what practical bug does that create when writing your own protocol?
- What does DNS TTL control, and why does lowering it after an outage help nobody?
- Name two problems
SO_KEEPALIVEsolves and one it doesn't. - When would you pick UDP over TCP? Give one real example (protocol + why).
Stretch problem (connects to S060 — OS Basics): every TCP socket consumes a file descriptor. Your server runs on a Linux box with ulimit -n = 1024. Assuming each request opens 1 socket + 2 log files + 1 DB connection, roughly how many concurrent requests can you serve before hitting the FD ceiling? What kernel-level change and what application-level change would you make to push that to 50 k concurrent connections? Sketch the answer in 5 bullets.
Explain-out-loud test
If you can't teach these 3 things to a friend without notes, redo the session:
- What is Networking I? (one sentence, no jargon)
- When would you use it? (one concrete example)
- When would you NOT use it? (one anti-pattern)
What comes next
- Next session (S062): Networking II — Load Balancers L4 vs L7, Reverse Proxies
- Previous (S060): OS Basics — Processes, Threads, Memory, FDs
- Hub: The 6-Month Learning Plan
Part of a 130-session evergreen learning series. Session structure: (a) intuition · (b) visual walkthrough · (c) hands-on · (d) production reality · (e) quiz + exercise. Duration: 60 minutes.
More from M07 · Systems & Infrastructure
all modules →- 060OS Basics — Processes, Threads, Memory, FDs
- 062Networking II — Load Balancers L4 vs L7, Reverse Proxies
- 063Caching — Cache-Aside, Write-Through, TTLs, Invalidation
- 064CDN — Edge, Cache Hierarchies, Cache-Control
- 065Docker — Images, Layers, Dockerfile, Networking
- 066Kubernetes I — Pods, Deployments, Services