DEEP DIVE: WEBSOCKET

WebSocket: A Protocol Where the Server Gets to Speak First

HTTP was built on a one-way model where the client asks and the server answers. For chat, notifications, and real-time sync (anything the server needs to push on its own initiative), that shape has been a bottleneck for as long as the web has needed to feel live. From the handshake through the frame format, WebSocket's role as a P2P signaling channel, and its relationship to the newer WebTransport, here is the bidirectional protocol in full.

Why it exists: the limits of the request/response model

The web's foundational request/response model leaves no way for the server to hand over new data the moment it appears; it has to wait for the client to ask again. For use cases like chat, live price updates, and notifications, where the server itself wants to initiate delivery, developers made do with polling, where the client asks again every few seconds, or long polling (Comet), where the server deliberately holds the response open until it has something to say. Both carry real costs: HTTP header overhead on every new connection, the churn of repeatedly opening and closing connections, and noticeable latency. WebSocket, standardized as RFC 6455 in 2011, was designed to solve this inefficiency at the root. It provides a full-duplex channel over a single TCP connection, where either the client or the server can send a message at any moment, independent of the other side.

Handshake and connection setup

A WebSocket connection does not start out speaking some exotic protocol; it begins life as an ordinary HTTP request. The client sends an HTTP/1.1 GET request carrying Upgrade: websocket and Connection: Upgrade headers, along with a Sec-WebSocket-Key, a random 16-byte value, base64-encoded. If the server agrees to switch protocols, it replies with status 101 Switching Protocols and a Sec-WebSocket-Accept header. That value is computed by concatenating the received key with a fixed GUID, 258EAFA5-E914-47DA-95CA-C5AB0DC85B11, taking the SHA-1 hash, and base64-encoding the result. The client performs the same computation itself and checks it against what came back, confirming that the response really did come from a server that understands WebSocket. Once the handshake completes, HTTP semantics disappear from that TCP connection entirely, and all further traffic switches to the frame format described below. Two URL schemes are used: plain-text ws:// (default port 80) and TLS-protected wss:// (default port 443, with certificate validation just like HTTPS).

Frame structure: how messages get chopped up

After the handshake, WebSocket traffic is not sent as raw messages; it is broken into discrete frames with a fixed layout. A single logical message can also be split across multiple frames (fragmentation).

FIN bit
A single bit marking whether this frame is the final fragment of a message. When it is 0, more frames follow, and the receiver reassembles the original message by concatenating the fragments carrying the "continuation" opcode (0x0).
Opcode
A 4-bit field identifying the frame type. Besides the data frames, text (0x1, UTF-8-encoded payload) and binary (0x2), there are control frames: close (0x8) to end the connection, and ping (0x9) / pong (0xA) for keepalive.
Payload length
Encoded with a variable-length scheme: when the length does not fit in the base 7-bit field, the value 126 signals a 16-bit extended length and 127 signals a 64-bit extended length placed right after. This keeps overhead low for small messages while still accommodating large payloads.
Masking
Frames sent from client to server are required to XOR the entire payload with a 4-byte masking key (frames from server to client must not be masked). This is not about confidentiality; it is an RFC requirement meant to stop a malicious web page from crafting a payload that poisons the cache of a transparent proxy sitting on the path.

Keepalive, subprotocols, and the origin model

  • Ping/pong: sending a ping frame (opcode 0x9) is expected to elicit a pong frame (0xA) carrying the same payload. No response signals a dead connection, and the periodic exchange itself also keeps proxies and NAT devices from timing out an idle TCP connection on their own.
  • Subprotocols: during the handshake, the client lists candidates in a Sec-WebSocket-Protocol header, and the server picks and confirms one, letting both sides agree on an application-specific message format over the same WebSocket connection (for example, MQTT, a lightweight IoT messaging protocol, over WebSocket, or graphql-ws for GraphQL subscriptions).
  • Origin model: unlike XMLHttpRequest or fetch, WebSocket connections are not subject to the Same-Origin Policy. The browser attaches an Origin header to the handshake request, but it is entirely up to the server implementation whether to validate it and reject the request. Skipping that check opens the door to Cross-Site WebSocket Hijacking (CSWSH), which abuses an already-authenticated session.

Its role in a P2P context

WebSocket itself is a client-server technology, but it plays an important supporting role around P2P systems.

  • Signaling channel for WebRTC: as covered in NAT traversal, WebRTC lets peers exchange media and data directly, but it deliberately does not specify how peers exchange the SDP offer/answer and ICE candidates needed before that connection can be established; that "signaling" mechanism is left open. Most implementations use WebSocket to talk to a signaling server for that pre-negotiation step.
  • Relay communication in Nostr: in Nostr, the exchange of EVENT, REQ, and CLOSE JSON messages between clients and relays runs directly over WebSocket, making it a core part of the protocol itself rather than an implementation detail.
  • Subscription APIs on blockchain nodes: JSON-RPC interfaces on nodes such as Ethereum expose subscription endpoints like eth_subscribe over WebSocket, letting a client receive new blocks or logs in real time without resorting to polling.
  • Real-time state sync for virtual worlds: it is also the default choice for low-latency bidirectional communication in scenarios like P2P and the metaverse, where a server needs to keep many participants' real-time state in sync.

Operational challenges

Unlike short-lived, stateless HTTP requests, WebSocket assumes you will hold open a large number of stateful, long-lived connections at once. That assumption brings its own set of operational headaches.

  • Scaling: many general-purpose L7 (application-layer) load balancers are designed around short-lived connections and do not distribute long-lived ones well. The number of connections a single server can hold at once is bounded by OS resources like file descriptors, and a "reconnection storm" (a flood of clients reconnecting simultaneously right after a deploy or a network blip) needs to be managed with exponential backoff and jitter.
  • Compatibility with proxies and corporate firewalls: some older proxies and firewalls mishandle the Upgrade header, or forcibly close idle connections after a short timeout, and connectivity through corporate networks is a recurring source of trouble.
  • Its place in the HTTP/2 and HTTP/3 era: HTTP/2 was designed to multiplex many requests over a single connection, but its original specification did not play well with WebSocket's Upgrade mechanism. RFC 8441 (2018) closed that gap, defining a way to tunnel WebSocket inside a single HTTP/2 stream using the extended CONNECT method. HTTP/3, built on QUIC, and WebTransport are the newer options that extend this same line of thinking.

It is worth noting that Socket.IO, often confused with WebSocket, is not the same thing. It is a higher-level library with its own handshake and message framing that automatically falls back to HTTP long polling when WebSocket is unavailable, and it adds features like reconnection and room-based broadcasting. It cannot interoperate directly with a plain WebSocket client.

Comparing polling, SSE, WebSocket, and WebTransport

WebSocket is far from the only way to get real-time data from server to client; simpler mechanisms and newer ones are both frequently the better fit, depending on the use case.

AspectPolling / long pollingSSEWebSocketWebTransport
DirectionClient-initiated, effectively one-way (server to client)One-way, server to clientBidirectional, full-duplexBidirectional, with multiple streams plus datagrams
MultiplexingA new request is issued for each roundA single HTTP connection carries a continuous one-way event streamA single logical stream over one TCP connectionMultiple streams and datagrams multiplexed over QUIC
Reliability / orderingGuaranteed per requestTCP ordering, plus resumption via Last-Event-IDTCP ordering; reconnection after a drop must be implemented by the applicationChoice of reliable, ordered streams or lightweight unordered datagrams
TransportHTTP/TCPHTTP/TCPTCP (via an HTTP Upgrade)QUIC/UDP
Browser support maturityThe most battle-testedSupported in major browsers, though adoption is limitedMature across all browsers and by far the most widely usedComparatively new and still maturing; see WebTransport in depth

Related pages

WebSocket's ideas connect to several other deep dives on this site. WebTransport in depth is the newer alternative that handles streams and datagrams over QUIC; Nostr adopts WebSocket directly as its relay communication channel; NAT traversal is the foundation for finding a signaling peer in the first place; and P2P and the metaverse, where real-time responsiveness matters most, is built on the bidirectional communication WebSocket provides.

Back to top page