A minimal WebSocket server built from scratch to understand the protocol.
- Protocol upgrade mechanism - HTTP → WebSocket handshake
- Binary framing - Variable length, opcodes, masking
- Bidirectional communication - Server can push without client asking
HTTP is request-response:
Client: "Give me data"
Server: "Here's data"
(waits for next request)
WebSocket allows both sides to send anytime:
Client: "Let's upgrade to WebSocket"
Server: "OK, upgraded"
Client: "hello"
Server: "hi"
Server: "here's an update" ← server initiates!
Server: "another update"
Client: "thanks"
Used for: chat apps, live notifications, multiplayer games, stock tickers.
WebSocket starts as HTTP:
Client sends:
GET / HTTP/1.1
Upgrade: websocket
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Server responds:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
The Sec-WebSocket-Accept is computed by:
- Concatenate client key + magic string
258EAFA5-E914-47DA-95CA-C5AB0DC85B11 - SHA-1 hash
- Base64 encode
This proves the server understands WebSocket (not encryption, just verification).
After handshake, communication switches to binary frames:
┌─────────┬─────────┬──────────┬─────────────┐
│ Byte 0 │ Byte 1 │Bytes 2-5 │ Rest │
│FIN+Code │Mask+Len │Mask Key │ Payload │
└─────────┴─────────┴──────────┴─────────────┘
Byte 0: 0x81 = FIN(1) + opcode(1 = text)
Byte 1: 0x85 = masked(1) + length(5)
Bytes 2-5: 4-byte XOR mask key
Rest: Masked payload data
1= text message2= binary message8= close connection9= ping10= pong
Client→Server is always masked. NOT for encryption (mask is visible).
It prevents cache poisoning attacks where malicious bytes could look like HTTP responses to proxy servers.
cargo runwebsocat ws://127.0.0.1:8080Type messages - they echo back!
main()- TCP listener, accepts connectionshandle_client()- HTTP handshakeextract_websocket_key()- Parse Sec-WebSocket-Key headercompute_accept_key()- SHA1 + Base64 for accept valuehandle_websocket()- Frame loop after handshakeparse_frame()- Decode incoming frames, unmask payloadbuild_frame()- Create outgoing frames (no mask for server)