Lesson 13 of 32 intermediate 5 min read

Before this:Polling vs pushAnatomy of an HTTP request

WebSockets

Key takeaways A WebSocket starts life as an ordinary HTTP request that asks to Upgrade the connection; once the server agrees (101 Switching Protocols), the request/response rules end and the socket becomes a persistent, full-duplex pipe — either side sends messages whenever it likes. That buys the lowest latency and true two-way traffic, and costs you what HTTP had given for free: connection lifecycle — keepalives, detection of silent death, and reconnect logic — is now your client’s job.

WebSockets are the maximal answer to the push problem: not a workaround inside request/response, but a negotiated exit from it. This lesson covers the handshake, what flows afterwards, and the operational duties that come with owning a long-lived connection — duties GopherTrunk’s own web console learned the hard way, as Unit 6 recounts.

The handshake: HTTP’s polite exit

A WebSocket begins as a GET with two special headers:

GET /diag/symbols HTTP/1.1
Host: scanner.local:8080
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

101 Switching Protocols is the last HTTP the connection ever speaks. From the next byte on, the same TCP socket carries WebSocket frames in both directions. Starting as HTTP is the protocol’s cleverest feature: the connection traverses the same ports (80/443), proxies, and TLS setup as the web itself — ws:// is the plain scheme, wss:// the TLS one, and everything from the authentication lesson about TLS applies doubly to a connection that lives for hours.

After the upgrade: messages, not requests

The WebSocket protocol gives you message boundaries (a welcome upgrade from raw TCP’s endless byte stream — the problem message framing examines) and two payload types, text and binary. And that’s all. There are no methods, no paths, no status codes, no headers — no semantics. What messages mean is entirely up to your application: most designs invent a small JSON envelope, e.g. {"type": "spectrum", "bins": [...]} from a scanner streaming display data, or {"type": "subscribe", "channel": "calls"} from a client choosing feeds. You are designing a tiny protocol of your own, with all the contract duties that implies.

Full duplex is the differentiator: both sides send, any time, without waiting. A spectrum panel receives thirty updates a second while sending pan/zoom commands upstream on the same socket. If your data only flows one way — server to client — you’re paying for a capability you don’t use, and the next lesson’s simpler machinery deserves the job.

The new job: keeping the connection alive (and honest)

Here’s what HTTP’s one-shot model had been quietly doing for you: making connection death visible. A request either answers or fails. A long-lived socket, by contrast, can die silently — a NAT router times out the idle mapping, a laptop sleeps, a cable drops — and neither side necessarily learns of it until it next writes. Owning a WebSocket means owning three routines:

  • Keepalives. The protocol has ping/pong frames; send pings periodically and treat a missing pong as a dead connection. This also keeps NAT mappings warm.
  • Timeouts. A connection that hasn’t produced expected traffic within some window is presumed dead — don’t wait for TCP to notice on its own (it can take many minutes).
  • Reconnection with backoff. Clients must expect to reconnect, waiting longer after each consecutive failure (1 s, 2 s, 4 s… capped), and — the subtle part — reset that backoff only when the new connection proves itself by delivering data, not merely by opening. A server that accepts the handshake and immediately closes will otherwise be hammered at full speed by every well-meaning client forever. This single sentence is the hard-won engineering lesson behind GopherTrunk’s console sockets.

Rule of thumb: design every WebSocket client around the assumption that the connection will drop, silently, at the worst moment. Reconnection is the normal path, not the error path.

Where WebSockets earn their keep

Fit Why
Live dashboards at high update rates Sub-second latency, no per-update overhead
Chat, collaborative editing Genuinely two-way, many small messages
Streaming telemetry (spectrum, symbols) Binary frames carry dense data efficiently
Occasional notifications Poor fit — an open socket per idle client is waste; consider SSE or polling

The networking-side view of WebSockets and the browser-side view in the web-dev module complete the picture from below and above.

Quick check: a reconnecting WebSocket client should reset its backoff delay when…

Recap

  • A WebSocket is an HTTP request that upgrades (101 Switching Protocols) into a persistent, full-duplex message pipe over the same port and TLS.
  • The protocol supplies message framing (text/binary) but no semantics — your message vocabulary is a contract you design and must honour.
  • Full duplex suits two-way, high-rate traffic; one-way flows are usually better served by simpler transports.
  • Long-lived sockets die silently: keepalive pings, liveness timeouts, and reconnect with backoff are the client’s job.
  • Reset backoff only when data arrives, never on a successful handshake.

Next up: Server-sent events.