WebSockets
Overview
Section titled “Overview”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.
HTTP Upgrade Handshake
Section titled “HTTP Upgrade Handshake”Client Request
Section titled “Client Request”GET /chat HTTP/1.1Host: example.com:8080Upgrade: websocketConnection: UpgradeSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==Sec-WebSocket-Version: 13Sec-WebSocket-Protocol: chat, superchatSec-WebSocket-Extensions: permessage-deflate; client_max_window_bitsOrigin: https://example.comKey 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
Server Response
Section titled “Server Response”HTTP/1.1 101 Switching ProtocolsUpgrade: websocketConnection: UpgradeSec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=Sec-WebSocket-Protocol: chatSec-WebSocket-Extensions: permessage-deflate; client_max_window_bitsHandshake Validation
Section titled “Handshake Validation”The Sec-WebSocket-Accept value is computed as follows:
- Take the value of
Sec-WebSocket-Keyfrom the client request:dGhlIHNhbXBsZSBub25jZQ== - Concatenate the globally unique UUID
258EAFA5-E914-47DA-95CA-C5AB0DC85B11 - Compute SHA-1 hash of the concatenated string
- Base64-encode the hash
# Verify the handshake computationKEY="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)WebSocket Framing
Section titled “WebSocket Framing”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) |+---------------------------------------------------------------+Frame Fields
Section titled “Frame Fields”| Field | Bits | Description |
|---|---|---|
| FIN | 1 | 1 = final frame of message, 0 = more frames follow |
| RSV1, RSV2, RSV3 | 3 | Reserved for extensions. Must be 0 unless an extension is negotiated. |
| Opcode | 4 | Frame type (see opcodes table) |
| MASK | 1 | 1 = payload is masked (must be 1 for client-to-server frames) |
| Payload length | 7 | Length of payload: 0-125 = actual length, 126 = next 2 bytes, 127 = next 8 bytes |
| Extended payload length | 16/64 | Actual payload length when payload length is 126 or 127 |
| Masking key | 32 | 4-byte key used to unmask the payload (if MASK=1) |
| Payload data | var | The actual data |
Fragmentation
Section titled “Fragmentation”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
Section titled “Masking”Why Client-to-Server Frames Are Masked
Section titled “Why Client-to-Server Frames Are Masked”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.
XOR Masking Algorithm
Section titled “XOR Masking Algorithm”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 4transformed[i] = original[i] XOR mask[j]Example:
Payload: 0x48 0x65 0x6c 0x6c 0x6f (Hello)Mask key: 0x37 0xfa 0x21 0x3dMasked: 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.
Opcodes
Section titled “Opcodes”| Opcode | Meaning | Description |
|---|---|---|
| 0x0 | Continuation | Continuation of a fragmented message |
| 0x1 | Text | Text frame (UTF-8 encoded) |
| 0x2 | Binary | Binary frame (arbitrary binary data) |
| 0x3-7 | Reserved | For future use |
| 0x8 | Connection Close | Close the connection |
| 0x9 | Ping | Keepalive ping |
| 0xA | Pong | Response to a ping |
| 0xB-F | Reserved | For future use |
Text vs Binary
Section titled “Text vs Binary”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.