Skip to content
Close Handshake
step 1/5

Reading — step 1 of 5

Read

~3 min readOperations

Close Handshake

Why does WebSocket bother with a close handshake when either side could just close the TCP socket? Because "the connection ended" and "the conversation ended" are different facts. A bare TCP teardown can mean a crash, a network drop, or an attacker truncating the stream mid-message. The CLOSE exchange makes clean shutdown distinguishable: both sides know all data was delivered and the ending was intentional.

Endpoint A -> CLOSE frame (opcode 0x8, code + reason)
Endpoint B -> CLOSE frame in reply
TCP teardown (RFC 6455: the server SHOULD close TCP first)

After sending CLOSE you may not send data frames anymore; after the exchange completes, the socket comes down.

The close payload, byte by byte

A CLOSE frame's payload is optional, but if present it is:

bytes 0-1 : status code, big-endian u16
bytes 2+  : UTF-8 reason string (human-readable, optional)

The whole payload obeys the control-frame cap of 125 bytes, so the reason has at most 123 bytes. Code 1000 with reason "OK" is the 4 bytes 03 e8 4f 4b.

Standard codes (RFC 6455 §7.4.1):

CodeMeaning
1000Normal closure
1001Going away (tab closed, server shutting down)
1002Protocol error
1003Unsupported data type
1007Invalid payload (e.g. broken UTF-8 in a TEXT message)
1008Policy violation
1009Message too big
1011Server encountered an internal error
3000–3999Registered for libraries/frameworks
4000–4999Private application use

The codes that never touch the wire

Three codes are reserved as local signals only — an endpoint MUST NOT send them, but your API reports them:

  • 1005 — no status code was present. A CLOSE frame with an empty payload is perfectly legal; the receiving side reports 1005 to application code.
  • 1006 — abnormal closure. TCP died with no CLOSE frame at all (the half-open laptop from last lesson). Nothing was received; the library synthesizes 1006 locally.
  • 1015 — the TLS handshake failed before a WebSocket ever existed; again purely a local report.

Ill-formed payloads exist too, and a parser must reject them: a 1-byte payload is a protocol error (a status code is two bytes — one byte is a truncated code), and a reason that fails UTF-8 validation is invalid (the RFC requires close reasons to be UTF-8).

python

Your exercise

Parse close payloads with exactly those five behaviors. The classic mistake the grader catches is byte order: the code is big-endian, so payload 03e8 must print code=1000 — read it little-endian and you'll print code=59395. The edge cases each have one exact expected line: single byte abERR truncated; payload 03e8c328ERR invalid-utf8 (0xc3 opens a 2-byte UTF-8 sequence but 0x28 is not a continuation byte); and 03e8636166c3a9code=1000 reason=café — decode the reason as UTF-8, not latin-1, or the accent mangles.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…