Home / Software y Cloud / A call between your browser and the server that never hangs up

A call between your browser and the server that never hangs up

Ilustración de una conexión WebSocket entre navegador y servidor

When you open a website, every click is a letter: your browser asks for something, the server answers, and the exchange is closed. That is fine for reading an article, but what about a chat where the other person’s message appears on its own, a stock price that updates on its own, or a shared cursor in a document? For that you need something that behaves like a phone call, not like an exchange of letters.

The classic web works with letters

The web’s base protocol, HTTP, follows a simple pattern: the client sends a request and the server answers with a page or some data. This is called request-response communication. Even if a single TCP connection (the transport channel between two machines) can be reused, the logic is always the same: every interaction is started by the client, and once the server replies, that exchange is finished.

The server has no way to notify you on its own initiative. If something changes on the server, you do not find out until you ask again. That “ask and wait” model works because a web page is, at heart, something you read. But it is not enough for what we want now: a continuous conversation.

Asking a hundred times a minute (and the polling trick)

The first solution was as simple as it was wasteful: the browser asked the server every few seconds whether anything new had arrived. That is called polling. It works, but you spend an entire request — with its headers and network overhead — even when there is nothing to report.

To improve on it, long-polling was invented: the client makes a request and the server keeps it “open”, without answering, until it finally has something to send; at that moment it replies and the client immediately launches another request. It is like calling someone and staying silent on the line waiting for them to talk. It reduces the overhead, but it is still a hack, one-way per request, with added latency on every cycle.

The real call: the handshake that switches protocols

To have a real conversation, in 2011 the WebSocket was standardized (RFC 6455). The idea is brilliant in its simplicity: it does not open a new connection, but transforms the HTTP connection it already has into a permanent, two-way channel.

Everything starts as a normal HTTP request. The client sends a GET with special headers: it asks to upgrade the protocol with Upgrade: websocket and sends a random value in Sec-WebSocket-Key. If the server accepts, it replies 101 Switching Protocols and computes Sec-WebSocket-Accept by applying SHA-1 to the client’s key joined to a fixed, public string: 258EAFA5-E914-47DA-95CA-C5AB0DC85B11.

That fixed identifier, called the magic GUID, is the heart of the handshake’s security (the initial exchange that agrees on how to communicate): it guarantees that whoever replies is a real WebSocket server, not an attacker trying to trick your browser with replies from another protocol. From that moment on, that TCP connection stops speaking HTTP and becomes a channel where both sides can write whenever they want: full-duplex communication (both directions at once) over a single thread.

Packets, not questions: the frame format

Once the call is established, data no longer travels as HTTP requests but as frames. Each frame is a small binary envelope with a header indicating the message type through an opcode: text (1), binary data (2), close (8), and the control messages ping (9) and pong (10). It also declares the length of what follows.

There is an apparently odd but very important technical detail: every frame sent by the client must be masked, that is, encoded with a random 4-byte key that the receiver uses to decode it. Why? To prevent an attack called cache poisoning: if a malicious browser could inject data that looks like a valid HTTP request inside a WebSocket connection, an intermediate caching server might swallow it and poison its responses. Masking breaks that trick. Frames from the server to the client travel unmasked.

The encrypted tunnel: wss://

What you have seen so far is the unencrypted version, ws://. In production you will almost always use wss://: it is the same handshake, but run over an encrypted TLS connection (the same encryption that protects HTTPS, on port 443). The key exchange, the WebSocket handshake, and the frames all happen inside the secure tunnel. Nobody in the middle of the network can read your messages.

So nobody is left hanging: ping and pong

Connections have a silent problem: routers, firewalls, and home networks usually close TCP connections that have been idle for a while in order to free resources. You think the line is still open, but in reality the other end has gone away.

That is why the protocol includes control messages: either side can send a ping and the other must reply with a pong. It is a heartbeat that keeps the connection alive and lets you detect when the other party has truly disappeared.

Why a call is hard to spread across servers

Here is where WebSocket gets complicated compared with classic HTTP. An HTTP request is short-lived and stateless: any server in a pool can answer it. A WebSocket connection, by contrast, is a long line stuck to one specific server: that process keeps in memory the state of each conversation.

That has two consequences. First, if that server crashes, all its calls are cut and clients must reconnect. Second, a single server can only hold tens of thousands of open sockets at once, and the load balancer (the traffic distributor) cannot send the next request to any node: it must keep each connection on the same place, using sticky sessions or connection-level distribution.

And when one user sends a chat message that thousands of people connected to different servers must receive, another challenge appears: fan-out. The usual solution is a publisher-subscriber (pub/sub) backbone such as Redis Pub/Sub, Kafka, or NATS. The node that receives the message publishes it to that bus, and each subscribed application server receives it and pushes it out to its own sockets. The individual call is only the last mile of a distributed loudspeaker system.

Do you always need a call? Sometimes the radio is enough: SSE

WebSocket is powerful, but it has a simpler rival when you only need one direction: the server talking to the client. That is Server-Sent Events (SSE), also known as EventSource.

SSE works over plain HTTP: it needs no protocol switch and no frame masking. The server just keeps writing text events into the response that the browser receives on the fly. Its big advantage is simplicity: it reconnects on its own, passes through firewalls and proxies without trouble, and can resume the stream where it stopped using event identifiers. It is ideal for notifications, a news feed, live captions, or even for showing the tokens that a language model keeps generating.

The practical rule: if you only need the server to notify you, use SSE. If you need both directions at once with low latency — chat, games, collaborative cursors, live quotes, real-time dashboards — that is where WebSocket wins.

The little snippet that opens the line

Opening a WebSocket in the browser is almost trivial:

const ws = new WebSocket("wss://example.com/room");

ws.onopen = () => ws.send("Hello, I am here");
ws.onmessage = (e) => console.log("Received:", e.data);
ws.onclose = () => console.log("Line cut");

The browser handles the handshake for you, and behind those three lines everything you have read happens: the handshake that switches protocols, the masked frames, and the heartbeats that keep the call alive.

Conclusion: from exchanging letters to a call that never hangs up

The web was born to answer questions, not to hold conversations. Polling and long-polling tried to fake a dialogue with a system built for letters. WebSocket, by contrast, fakes nothing: it turns an HTTP connection into a permanent two-way channel, with its own handshake protocol, frame format, optional encryption, and heartbeat mechanism. And when scale squeezes in, that individual channel becomes the final piece of a distributed broadcast system.

Next time a chat message appears on its own on your screen, remember: it is not that your browser asked again. It is that the line has been open for a while, waiting for someone to talk.