Skip to content

WebSockets

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)

Masking adds minimal overhead (4 bytes per frame) and a small amount of CPU for the XOR operation. On modern hardware, this is negligible even at high throughput.

OpcodeMeaningDescription
0x0ContinuationContinuation of a fragmented message
0x1TextText frame (UTF-8 encoded)
0x2BinaryBinary frame (arbitrary binary data)
0x3-7ReservedFor future use
0x8Connection CloseClose the connection
0x9PingKeepalive ping
0xAPongResponse to a ping
0xB-FReservedFor future use

Text frames carry UTF-8 encoded data. Binary frames carry arbitrary bytes. Choose based on the Application:

  • Text: Human-readable messages (JSON, XML, plain text). Easier to debug.
  • Binary: Efficient for binary protocols (Protocol Buffers, MessagePack, custom binary formats). No base64 encoding overhead.