The WebSocket Protocol

WebSockets & Real-Time Communication — The WebSocket Protocol
WebSockets & Real-Time Communication
Chapter 2 · The WebSocket Protocol

🔌 The WebSocket Protocol

Chapter 1 established why WebSockets exist. This chapter goes under the hood of how they actually work — starting with a detail that surprises a lot of people: a WebSocket connection begins as a completely ordinary HTTP request.

The HTTP Upgrade Handshake

The client sends a normal-looking HTTP GET request, but with headers asking the server to switch protocols mid-connection:

The Raw Handshake, Side by Side

// Client request GET /chat HTTP/1.1 Host: example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== Sec-WebSocket-Version: 13 // Server response — note the status code HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

Sec-WebSocket-Accept is computed by hashing the client's key together with a fixed constant — proof the server actually understood the request, not just an echo. Once the 101 response arrives, the HTTP connection is done being HTTP — the same underlying TCP connection now carries the WebSocket protocol instead.

Why Start as HTTP at All?

Because it starts as an ordinary request, a WebSocket handshake sails through the same ports (80/443) and most proxies/firewalls that already allow normal web traffic — no special network configuration needed in most environments.

One Handshake, Then Done

The Upgrade dance happens exactly once, at connection start — everything after this is the WebSocket protocol itself, not more HTTP requests.

↔️ Full-Duplex vs Half-Duplex

Even a kept-alive HTTP connection is fundamentally half-duplex — one side sends a complete request, then waits for a complete response, strict turn-taking. A WebSocket connection is genuinely full-duplex: either side can send a message at any moment, including while the other side is still sending one.

Frames, Not More HTTP Requests

After the handshake, messages travel as lightweight binary frames — no headers, no status lines, just a small frame header plus the payload:

OpcodeMeaning
0x1Text frame (UTF-8 payload)
0x2Binary frame
0x8Close
0x9Ping
0xAPong

Masking (Client → Server Only)

Every frame the browser sends is XOR-masked with a random key — a security measure preventing a malicious page from crafting bytes that could poison a misbehaving proxy's cache. Server-to-client frames are never masked.

Minimal Overhead

A small text message can travel in a frame with as little as 2 bytes of header — compare that to a full set of HTTP headers on every polling request from Chapter 1.

Per-Message Overhead: Polling vs an Open WebSocket

HTTP Polling

Every single message is a brand-new HTTP request: request line, a full set of headers (cookies, user-agent, etc.), a fresh TCP handshake if the connection isn't kept alive — easily hundreds of bytes of overhead per message.

Established WebSocket

The connection and its headers exist exactly once, at handshake time. Every message after that is just a frame — a few bytes of header plus the actual payload.

🔒 ws:// vs wss://

The exact same relationship as http/https from the HTTPS/TLS course — wss:// runs the WebSocket connection over TLS, encrypting everything after the handshake the same way HTTPS encrypts a normal page:

const insecure = new WebSocket("ws://example.com/chat"); // plaintext — never in production const secure = new WebSocket("wss://example.com/chat"); // TLS-encrypted — the only real choice

Ping/Pong: Protocol-Level Keepalive

The protocol itself includes ping and pong frames — the server (or client) periodically sends a ping, and the other side automatically replies with a pong, letting either end detect a connection that's gone dead without any application code writing that logic. This is distinct from any application-level heartbeat message you might add on top of it.

Closing Properly: The Close Handshake

A well-behaved close exchanges close frames carrying a status code and optional reason, rather than either side just abruptly dropping the TCP connection:

CodeMeaning
1000Normal closure
1001Going away (e.g. page navigating off)
1006Abnormal closure (connection just died — no close frame was ever received)

💻 Coding Challenges

Challenge 1: Read a Handshake

Given a captured handshake request missing the Upgrade header, explain what the server should do in response, and why the connection would fail to establish.

Goal: Practice recognizing the exact headers required for a valid WebSocket handshake.

→ Solution

Challenge 2: Explain Full-Duplex With a Scenario

Describe a concrete scenario in a chat application where full-duplex communication produces a noticeably better user experience than a half-duplex (request/response) alternative could.

Goal: Practice connecting the abstract protocol property to a real, observable user-facing difference.

→ Solution

Challenge 3: Diagnose a Mixed-Content Block

A page served over https://example.com tries to open new WebSocket("ws://example.com/chat") and the connection is silently blocked by the browser. Explain why, and provide the fix.

Goal: Practice connecting the ws/wss distinction to a real, commonly-hit browser security restriction.

→ Solution

⚠️ Gotcha: A WebSocket Is One Extremely Long-Lived Connection

It's easy to mentally model a WebSocket as "a request that stays open" and treat it like any other HTTP request for logging, monitoring, or load-balancing purposes — it isn't. A single WebSocket connection can live for hours, carries no natural request boundary the way HTTP does, and needs to stay pinned to the same backend server for its entire lifetime (Chapter 7 covers why). Tooling built around "count requests per second" or "route each request independently" often needs real adjustment to handle a connection that's alive, and expected to stay alive, for far longer than any normal HTTP request ever would.

🎯 What's Next

With the protocol itself understood, the next chapter puts it to work: Building a Raw WebSocket Server — Node's ws library, and handling connection, message, and close events directly.