WebSocket basics: two-way communication in six callbacks
RFC 6455's handshake, masking, fragmentation, control frames and compression all complete inside XServer and the xrt layer — your script is six functions at the end of that pipeline.
Overview
21.1 Intro
The pain list of hand-written WebSocket: Upgrade handshake validation (SHA-1+Base64 of Sec-WebSocket-Key), frame header parsing (mask bit, extended lengths), fragment reassembly, interleaved control frames, the close state machine, permessage-deflate negotiation. XServer's split of labor: the host does the handshake and validation (400 on failure), xrtWsStreamAttach does the frame layer — callbacks receive whole messages, and sending is one function call.
21.2 Handshake & callbacks
- Handshakes route by vhost (Host/SNI like HTTP); routing failure 404
- A host exporting neither WsText nor WsBinary → 503 (service exists, business not written)
- Pong is answered automatically by the xrt layer — the WsPing/WsPong callbacks are for observation only (heartbeat stats)
- Hard wire boundary during handshake: guards against slow-loris attacks that fill ReadLimit then wait forever
21.3 The send family & echo
static void WsOpen(XS_HostInfo* pHost, xwsstream* pWs)
{
(void)xrtWsStreamText(pWs, xrtStrView("connected"));
}
static void WsText(XS_HostInfo* pHost, xwsstream* pWs, xstrview tText)
{
(void)xrtWsStreamText(pWs, tText); /* echo */
}
static void WsBinary(XS_HostInfo* pHost, xwsstream* pWs, xbytesview tData)
{
(void)xrtWsStreamBinary(pWs, tData);
}
What just happened: the send family is led by xrtWsStreamText / Binary (copying sends); the ...Ref (borrowed send — the buffer must not move before the call returns) and ...Take (ownership transfer, zero-copy for large messages) variants — choose by where the message comes from.
21.4 Connection context
Nearly all WS business needs "per-connection state" (user identity, subscription lists). The recommended approach: a connection registry (your own map: connection pointer → context), inserted in WsOpen, removed in WsClose:
static xmutex G_WsLock;
static xmap* G_WsConns; /* xwsstream* → ConnCtx* (cross-callback borrowing is yours) */
typedef struct ConnCtx {
char sUser[64];
uint64 iLastSeen;
} ConnCtx;
static void WsOpen(XS_HostInfo* pHost, xwsstream* pWs)
{
ConnCtx* pCtx = xrtCalloc(1, sizeof(ConnCtx));
/* insert (under lock) ... */
}
static void WsClose(XS_HostInfo* pHost, xwsstream* pWs,
uint16 iCode, xstrview tReason)
{
/* remove and free (under lock); WsClose is guaranteed terminal */
}
Note: the host owns the connection's lifetime; you own your context's lifetime — the registry pattern keeps the two exactly in sync. This table is also ch. 22's broadcasting infrastructure.
21.5 Contract
- Callbacks: the six run on worker threads, concurrently; message views (tText/tData) are valid inside the callback.
- Terminal: WsClose is each connection's terminal callback — registry cleanup is most reliable here.
- Sending: Text/Binary copy, Ref borrows, Take transfers — pick the variant by buffer ownership.
Common mistakes
- Symptom
- Later reads return garbled data.
- Cause
- tText/tData are borrowed views valid in the callback — copy before storing (the registry holds your own ConnCtx, never views).
- Symptom
- Sporadic garbled message content.
- Cause
- xrtWsStreamTextRef borrows your buffer — the function returning ≠ sending finished (under backpressure data still sits in the buffer). To reuse immediately, use the copying or Take variant.
Exercises
After echo works, send text / binary / a large message (1MB) with any WS client.
Convention: the first message is "login:name"; the registry records identity, and every later echo is prefixed "[name] ".
Use WsPing to track RTT stats; proactively close connections silent for 60 seconds.
Summary
| Point | Conclusion |
|---|---|
| Host's job | handshake validation (400/404/503) + the Attach frame layer |
| Six callbacks | Open/Text/Binary/Ping/Pong/Close; Pong answered automatically |
| Send family | Text/Binary (copy), Ref (borrow), Take (transfer) |
| Context | your own registry: insert on Open, remove on Close (terminal guarantee) |
- xrt reference · websocket_stream — streaming sends and reference-count semantics
- xrt reference · websocket_upgrade — the library-level handshake details