WebSocket Frames Explained
After the handshake completes, all data exchanged over a WebSocket connection travels as a sequence of frames defined by RFC 6455's binary framing format. Each frame carries a FIN bit indicating whether it's the final piece of a message, an opcode identifying the frame's type, a mask bit, a payload length field, an optional masking key, and the payload data itself — and a single logical message may be sent as one frame or fragmented across several.
Cricket analogy: Like a scorer's entry for each ball recorded with metadata — over number, whether it's the last ball of the over (FIN), what type of delivery it was (opcode), and the actual runs scored (payload) — bundled together as one structured record.
Opcodes: Text, Binary, and Control Frames
The opcode field distinguishes frame types: 0x0 for a continuation frame, 0x1 for text, 0x2 for binary, and the control opcodes 0x8 (close), 0x9 (ping), and 0xA (pong). Control frames must never be fragmented and are capped at 125 bytes of payload, and ping/pong frames give the protocol a lightweight built-in keepalive mechanism for detecting dead connections without waiting for a TCP-level timeout.
Cricket analogy: Like distinguishing a normal delivery (text data) from a boundary signal (six runs), a rain-delay announcement (close), and the umpire's quick 'still playing?' check with the third umpire who confirms with a brief nod (ping/pong keepalive).
// Node.js `ws` library: responding to keepalive pings and
// illustrating a raw frame's conceptual layout
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', (socket) => {
socket.on('ping', () => socket.pong()); // auto keepalive
socket.on('message', (data, isBinary) => {
console.log('Frame payload:', isBinary ? data : data.toString());
});
});
// Conceptual single-frame layout (RFC 6455 5.2), not literal code:
// FIN(1) RSV(3) OPCODE(4) | MASK(1) PAYLOAD_LEN(7/7+16/7+64)
// [MASKING-KEY (32 bits, client frames only)]
// PAYLOAD DATA (masked if from client)Masking: Client-to-Server Frames Must Be Masked
Every frame sent from client to server must be masked: the payload is XORed byte-by-byte with a randomly generated 4-byte masking key included in the frame, while frames sent from server to client must never be masked. This exists purely as a protocol-level defense against proxy cache poisoning — it is not encryption, since the key travels alongside the data in plaintext, and only wss:// (TLS) actually protects confidentiality.
Cricket analogy: Like a stadium requiring every fan-submitted question for the post-match interview to be run through a specific scrambling code before reaching the broadcast desk, purely so a mischievous usher's relay booth can't be tricked into rebroadcasting unrelated content as if it were official commentary — it's not about hiding the question, just proving it came through the proper channel.
Masking exists because early WebSocket drafts discovered that unmasked, attacker-controlled client payloads could be crafted to look like valid HTTP requests to a misbehaving caching proxy sitting between the browser and server — potentially poisoning that proxy's cache with attacker data. XORing every client-to-server frame with a random per-frame key means the raw bytes on the wire never look like plain text the proxy might parse, closing that hole. It is not encryption — wss:// (TLS) is what actually protects confidentiality.
Fragmentation of Large Messages
A large message can be split across multiple frames: the first frame carries the real opcode (text or binary) with FIN set to 0, subsequent frames use the continuation opcode (0x0) with FIN still 0, and the final frame sets FIN to 1 to mark the end of the logical message. This lets a sender stream large payloads incrementally without buffering the entire message in memory, and it's separate from the close frame, which can optionally carry a 2-byte status code followed by a UTF-8 reason string explaining why the connection ended.
Cricket analogy: Like a long post-match press conference broadcast in segments — the opening statement, several follow-up answer segments, and a final closing remark — where only the last segment is marked as the true end, letting broadcasters stream it without waiting for the whole thing to finish.
If you ever write your own low-level frame parser instead of relying on the browser's WebSocket API or a library like ws, remember that a single logical message can be fragmented across several frames (opcode continuation, FIN=0 until the last), and that control frames like ping/pong/close can legally be interleaved between the fragments of a data message. Treating every frame boundary as a message boundary will corrupt fragmented messages.
- Every WebSocket message is transmitted as one or more frames following the format defined in RFC 6455.
- The opcode field identifies the frame type: text (0x1), binary (0x2), continuation (0x0), close (0x8), ping (0x9), pong (0xA).
- Control frames (close, ping, pong) cannot be fragmented and are limited to 125 bytes of payload.
- All frames sent from client to server must be masked with a random 4-byte key using XOR; server-to-client frames must not be masked.
- Masking is a protocol-level defense against proxy cache poisoning, not an encryption mechanism.
- Large messages can be fragmented across multiple frames using FIN=0 and continuation opcodes, letting receivers process data incrementally.
- Most high-level libraries reassemble fragmented frames automatically, but low-level parsers must handle fragmentation and interleaved control frames correctly.
Practice what you learned
1. Which opcode identifies a text data frame in the WebSocket protocol?
2. Why must frames sent from the client to the server be masked?
3. What is true about control frames like ping, pong, and close?
4. How is a large message split across multiple frames (fragmentation) indicated?
5. Does WebSocket frame masking provide encryption?
Was this page helpful?
You May Also Like
The WebSocket Handshake
A step-by-step look at how a WebSocket connection is established, from the initial HTTP Upgrade request to the server's 101 Switching Protocols response.
What Are WebSockets?
An introduction to the WebSocket protocol, a persistent, full-duplex communication channel over a single TCP connection used for real-time web applications.
WebSockets vs Server-Sent Events
Understanding when to choose bidirectional WebSockets versus the simpler, unidirectional Server-Sent Events (SSE) protocol for server-to-client streaming.
Related Reading
Related Study Notes in Web Development
Browse all study notesWebAssembly Study Notes
WebAssembly · 30 topics
Web DevelopmentgRPC Study Notes
Protocol Buffers · 30 topics
Web DevelopmentSpring Boot Study Notes
Java · 30 topics
Web DevelopmentFlask Study Notes
Python · 30 topics
Web DevelopmentDjango Study Notes
Python · 30 topics
Web DevelopmentNext.js Study Notes
JavaScript · 30 topics