Skip to content

WebSockets | Networking - Wyatt's Notes

WebSockets (RFC 6455) provide full-duplex, bidirectional communication over a single TCP connection. Unlike HTTP, which follows a request-response model, WebSocket allows either side to send data at Any time after the connection is established. This makes WebSockets the protocol of choice for Real-time applications: chat, collaboration, financial tickers, live dashboards, gaming, and IoT Device control.

WebSockets start as an HTTP upgrade. The client sends a regular HTTP request with an Upgrade: websocket header. If the server agrees, it responds with 101 Switching ProtocolsAnd The connection becomes a WebSocket from that point forward.

GET /chat HTTP/1.1
Host: example.com:8080
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
Origin: https://example.com

Key headers:

  • Upgrade: websocket — signals the protocol upgrade request
  • Connection: Upgrade — required by HTTP/1.1 to indicate a connection-level upgrade
  • Sec-WebSocket-Key — base64-encoded 16-byte random value (used in the handshake validation)
  • Sec-WebSocket-Version — must be 13 (the current WebSocket protocol version)
  • Sec-WebSocket-Protocol — optional; lists application-level subprotocols the client supports
  • Sec-WebSocket-Extensions — optional; lists protocol-level extensions the client supports
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits

The Sec-WebSocket-Accept value is computed as follows:

  1. Take the value of Sec-WebSocket-Key from the client request: dGhlIHNhbXBsZSBub25jZQ==
  2. Concatenate the globally unique UUID 258EAFA5-E914-47DA-95CA-C5AB0DC85B11
  3. Compute SHA-1 hash of the concatenated string
  4. Base64-encode the hash
Terminal window
## Verify the handshake computation
KEY="dGhlIHNhbXBsZSBub25jZQ=="
GUID="258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
ACCEPT=$(printf "%s%s" "$KEY" "$GUID" | openssl dgst -sha1 -binary | base64)
echo "$ACCEPT"
## Output: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

This mechanism prevents a regular HTTP client from accidentally establishing a WebSocket connection, And prevents a WebSocket client from accidentally connecting to a non-WebSocket server.

sequenceDiagram
participant C as Client
participant S as Server
C->>S: HTTP GET /chat (Upgrade: websocket)
Note right of S: Validate Sec-WebSocket-Key<br/>Compute Sec-WebSocket-Accept
S->>C: 101 Switching Protocols
Note over C,S: TCP connection is now a WebSocket
C->>S: WebSocket frame (text data)
S->>C: WebSocket frame (text data)
S->>C: WebSocket frame (text data)
C->>S: WebSocket frame (close)
S->>C: WebSocket frame (close)

After the handshake, communication uses a binary frame format:

0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len | Extended payload length |
|I|S|S|S| (4) |A| (7) | (16/64) |
|N|V|V|V| |S| | (if payload len==126/127) |
| |1|2|3| |K| | |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
| Extended payload length continued, if payload len == 127 |
+ - - - - - - - - - - - - - - - +-------------------------------+
| |Masking-key, if MASK set to 1 |
+-------------------------------+-------------------------------+
| Masking-key (continued) | Payload Data |
+-------------------------------- - - - - - - - - - - - - - - - +
: Payload Data continued ... :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| Payload Data (continued) |
+---------------------------------------------------------------+
FieldBitsDescription
FIN11 = final frame of message, 0 = more frames follow
RSV1, RSV2, RSV33Reserved for extensions. Must be 0 unless an extension is negotiated.
Opcode4Frame type (see opcodes table)
MASK11 = payload is masked (must be 1 for client-to-server frames)
Payload length7Length of payload: 0-125 = actual length, 126 = next 2 bytes, 127 = next 8 bytes
Extended payload length16/64Actual payload length when payload length is 126 or 127
Masking key324-byte key used to unmask the payload (if MASK=1)
Payload datavarThe actual data

A single WebSocket message can be split across multiple frames. The first frame has FIN=0 and the Opcode indicates the message type. Subsequent frames have FIN=0 and opcode=0x0 (continuation). The Final frame has FIN=1 and opcode=0x0.

Frame 1: FIN=0, opcode=0x1 (text), payload="Hello "
Frame 2: FIN=0, opcode=0x0 (continuation), payload="World"
Frame 3: FIN=1, opcode=0x0 (continuation), payload="!"

Control frames (ping, pong, close) MUST NOT be fragmented. They must fit in a single frame with FIN=1.

Masking prevents cache poisoning attacks. A malicious client could craft a WebSocket frame that Looks like an HTTP request or response and inject it into a shared cache (e.g., a CDN or proxy). Masking ensures that the payload on the wire differs from the actual payload, making it infeasible To craft a frame that matches a specific HTTP pattern.

The RFC 6455 specification requires all client-to-server frames to be masked. Server-to-client Frames MUST NOT be masked.

The masking key is 4 bytes. Each byte of the payload is XORed with the corresponding byte of the key (cycling through the key):

j = i MOD 4
transformed[i] = original[i] XOR mask[j]

Example:

Payload: 0x48 0x65 0x6c 0x6c 0x6f (Hello)
Mask key: 0x37 0xfa 0x21 0x3d
Masked: 0x7f 0x9f 0x4d 0x55 0x58
Verification:
0x48 XOR 0x37 = 0x7f (i=0, j=0)
0x65 XOR 0xfa = 0x9f (i=1, j=1)
0x6c XOR 0x21 = 0x4d (i=2, j=2)
0x6c XOR 0x3d = 0x55 (i=3, j=3)
0x6f XOR 0x37 = 0x58 (i=4, j=0, wraps)

WebSockets are like a phone call versus sending letters. HTTP is like sending letters back and forth - each message requires a new request and response. WebSockets are like picking up the phone - once connected, either party can speak at any time without waiting for the other to finish. The upgrade handshake is like calling someone and asking “Can we switch from letters to a phone call?” The server agrees, and from then on, the connection stays open for instant two-way communication. This is essential for real-time applications like chat, gaming, and live feeds where you need immediate updates without the overhead of establishing a new connection for each message.

  • HTTP - How the WebSocket handshake upgrades from an HTTP connection
  • HTTP/2 and HTTP/3 - How HTTP/2 Server Push and streams relate to WebSocket real-time patterns
  • TLS - How WSS (WebSocket Secure) wraps WebSocket connections in TLS