skip to content

In a WebSocket data frame, what do the FIN bit and the 4-bit opcode in the first byte tell the receiver?

level: juniorimportance: must knowfreq 70%

answer

  1. first byte, four fields
  2. one bit for the message end
  3. low four bits name the frame
  4. RSV bits zero without an extension
  5. 0x81 is FIN plus opcode %x1

basics

~20 s

FIN says whether this frame is the last of its message; the 4-bit opcode says what the frame is: %x0 continuation, %x1 text, %x2 binary, %x8 close, %x9 ping, %xA pong. Three RSV bits sit between them.

solid answer

~40 s

Every WebSocket frame starts with one byte holding four things. Bit 7 is `FIN`: set means this frame completes the message, clear means more frames follow. Bits 6-4 are `RSV1`, `RSV2` and `RSV3`, which must be zero unless a negotiated extension gives them meaning. The low four bits are the opcode: `%x0` continuation, `%x1` text, `%x2` binary, `%x8` close, `%x9` ping, `%xA` pong, with `%x3`-`%x7` reserved for future data frames and `%xB`-`%xF` for future control frames. So `0x81` reads as FIN set, no RSV, opcode `%x1` — one complete text frame. Opcodes `%x8` and above are control frames and follow stricter rules than data frames.

code

http · 5 lines
http
81 05 48 65 6c 6c 6f

# 0x81 = 1000 0001 -> FIN=1, RSV1..3=0, opcode=%x1 (text)
# 0x05 = 0000 0101 -> MASK=0, payload length 5
# 48 65 6c 6c 6f   -> the five payload bytes

go deeper

for a junior

Memorise the six named opcodes and what FIN means: %x0 continuation, %x1 text, %x2 binary, %x8 close, %x9 ping, %xA pong, and FIN = last frame of this message.

for a middle

Explain why the opcode's high bit separates control frames from data frames, and why an RSV bit set with no negotiated extension is a protocol error rather than something to ignore.

for a senior

Be able to read a capture: identify a frame from its first byte, spot a fragmented message in progress, and explain what the header deliberately omits — no id, no channel, no length in that byte.

for a principal

The judgment call is what the application must add on top: with no message id and no channel in the header, request/reply pairing and multiplexing are conventions your team defines and every client must share.

## One byte, four fields A WebSocket frame is not a text protocol and has no delimiter you can grep for. It opens with a fixed two-byte header, and the **first byte** alone carries four separate pieces of information packed into single bits and one nibble: | bits | field | meaning | |---|---|---| | bit 7 | `FIN` | 1 = this frame is the final frame of its message; 0 = more frames follow | | bit 6 | `RSV1` | reserved; 0 unless a negotiated extension defines it | | bit 5 | `RSV2` | reserved; 0 unless a negotiated extension defines it | | bit 4 | `RSV3` | reserved; 0 unless a negotiated extension defines it | | bits 3-0 | opcode | what kind of frame this is | The second byte carries the `MASK` bit and the payload length, which are a separate subject. Everything below concerns the first byte only. ## FIN marks a message boundary, not a connection end This is the most common first misreading. `FIN` set does **not** mean the connection is finishing; it means *this message* is finished. A WebSocket connection carries an unbounded sequence of messages, and each message is one or more frames. The overwhelmingly common case — a short command sent whole — is a single frame with `FIN` set. When a sender chooses to split a message, only the last frame of that message has `FIN` set. Note how little that costs: a sender that has not yet computed the total size of what it is sending can emit a first frame with `FIN` clear and keep appending, because the message's end is signalled by a bit on the *last* frame rather than by a length declared up front. ## The opcode nibble Four bits give sixteen values. The specification assigns six and reserves the rest: - **`%x0` continuation** — this frame carries more of a message that an earlier frame began. - **`%x1` text** — the payload is UTF-8 encoded text. - **`%x2` binary** — the payload is arbitrary bytes. - **`%x3`-`%x7`** — reserved for further **non-control** (data) frames. - **`%x8` close** — the connection-close control frame. - **`%x9` ping** — a liveness probe control frame. - **`%xA` pong** — the answer to a ping. - **`%xB`-`%xF`** — reserved for further **control** frames. The split is not arbitrary: the **high bit of the opcode** separates the two classes. Opcodes `%x0`-`%x7` are data frames; `%x8`-`%xF` are control frames. A receiver can therefore decide, from four bits, whether it is holding application payload or protocol machinery — before it has looked at a single byte of the body. Receiving a reserved opcode that no extension has defined is a protocol error, and the endpoint fails the connection rather than guessing. ## The RSV bits and why they are usually zero `RSV1`, `RSV2` and `RSV3` exist so that a negotiated extension can add per-frame signalling without a new frame format. If no extension was agreed when the connection opened, all three **must** be zero, and an endpoint that receives a frame with one of them set on a connection where nothing defines it treats that as a protocol error and fails the connection (the close status for a protocol error is 1002). The rule is strict on purpose: a receiver that quietly ignored a reserved bit would silently mis-deliver payload that an extension had transformed. ## Reading it off the wire A complete, unmasked text frame carrying five bytes looks like this: ```http 81 05 48 65 6c 6c 6f ``` `0x81` is `1000 0001`: `FIN` set, all three RSV bits clear, opcode `%x1`. `0x05` is the length. The remaining five bytes are the payload. Change the first byte to `0x01` and the same bytes become the *opening* fragment of a longer text message; change it to `0x89` and it becomes a ping, which is a control frame and obeys a tighter size rule. ## What the first byte does not tell you It is worth being precise about the absences, because interviewers probe them: 1. **No message identifier.** A WebSocket frame carries no sequence number and no correlation id. If an application needs to pair a reply with a request, it invents that field itself inside the payload. 2. **No length.** The length lives in the second byte and possibly in two or eight bytes after it. 3. **No direction or channel.** Nothing in the byte says who sent it or which logical stream it belongs to; the connection is the channel. Knowing which facts the header supplies, and which the application must supply for itself, is the whole point of the question.

  • Which opcodes are control frames, and how does a receiver tell without a lookup table?
    `%x8` close, `%x9` ping and `%xA` pong are the defined control frames, and `%xB`-`%xF` are reserved for more. The test is the opcode's high bit: values `%x8` and above are control frames, `%x0`-`%x7` are data frames. That one-bit test lets a receiver route a frame before reading its payload.
  • A frame arrives with RSV1 set and no extension was negotiated. What should the endpoint do?
    Fail the connection. An RSV bit only has meaning when a negotiated extension defines it; set with nothing to define it, it is a protocol error, and the matching close status code is 1002. Ignoring the bit would risk delivering payload that an extension had transformed as if it were untransformed.
  • Does FIN set mean the connection is closing?
    No. `FIN` ends a *message*, not the connection, and the ordinary case — a single-frame message — has `FIN` set on every frame. A connection ends through the close control frame, opcode `%x8`, which is an entirely separate mechanism.

saying these in an interview costs you the question

  • Thinks FIN set means the connection is about to close
  • Believes the opcode names a channel or a message id
  • Thinks text and binary frames use different header layouts
  • Assumes RSV bits can be set freely because they are reserved
  • Cannot tell a control frame from a data frame by its opcode