WebSockets: a live two-way connection
Lesson 5 of 5
5WebSockets: a live two-way connection
The big pictureHTTP is question and answer: the client asks, the server replies, and it ends. A WebSocket is an open call: after the handshake the server speaks whenever it wants, so a new message arrives at once without anyone asking.
What the picture shows: A WebSocket's life: it starts as an ordinary HTTP request asking to upgrade, then becomes an open channel carrying messages both ways until one side closes it.
Map of this part
The concepts in order, click any one to jump to it:
Two ways into the same idea
HTTP is like letters: you send a question and wait for the reply, and for fresh news you send another. A WebSocket is like a phone call: the line is open and either side talks whenever it likes. That is why chats, live notifications and multiplayer games are built on it.
The client starts with a GET carrying Upgrade: websocket and Sec-WebSocket-Key, and the server answers 101 Switching Protocols. Then text or binary frames flow on the same TCP connection, with ping/pong frames keeping it alive. The server holds state per connection, so scaling out needs a shared broker (Redis pub/sub) for a broadcast to reach clients on other servers. Alternatives: Server-Sent Events for server-only push, and long polling when something blocks WebSockets.
Before You Start
📨HTTP
You know requests, responses and headers.
⏳asyncio
The async lesson: async and await.
The Core Idea
1Why HTTP is not enough
To learn about a new message over HTTP you must ask every two seconds (polling). Most answers are "nothing new", wasting network and server, and a message waits until the next ask. A WebSocket flips the direction: the server pushes the message the moment it arrives.
2The handshake and messages
In the browser it comes down to four events, open, message, close and error, plus a send method.
const ws = new WebSocket("wss://example.com/chat");
ws.addEventListener("open", () => ws.send(JSON.stringify({ type: "join", name: "Sara" })));
ws.addEventListener("message", (e) => {
const msg = JSON.parse(e.data);
console.log(`${msg.from}: ${msg.text}`);
});
ws.addEventListener("close", () => console.log("disconnected, retry in 2s"));wss:// (encrypted) in production, and send JSON with a type field so you know what each message is.3Broadcasting: a tiny chat server
The server keeps a set of connections. Each incoming message goes to everyone, and on disconnect its owner is removed. Here we simulate clients with asyncio queues so you can see the logic run without a network.
import asyncio
class Room:
def __init__(self):
self.clients = {}
def join(self, name):
self.clients[name] = asyncio.Queue()
return self.clients[name]
def leave(self, name):
self.clients.pop(name, None)
async def broadcast(self, sender, text):
for name, inbox in self.clients.items():
if name != sender:
await inbox.put(f"{sender}: {text}")
async def main():
room = Room()
sara, omar, lina = room.join("sara"), room.join("omar"), room.join("lina")
await room.broadcast("sara", "hello")
room.leave("lina")
await room.broadcast("omar", "hi sara")
for who, inbox in [("sara", sara), ("omar", omar), ("lina", lina)]:
got = []
while not inbox.empty():
got.append(inbox.get_nowait())
print(who, "->", got)
asyncio.run(main())The same logic with the real websockets library:
import asyncio
import websockets
clients = set()
async def handler(ws):
clients.add(ws)
try:
async for message in ws:
websockets.broadcast(clients - {ws}, message)
finally:
clients.discard(ws)
async def main():
async with websockets.serve(handler, "localhost", 8765):
await asyncio.Future()
asyncio.run(main())finally so it always happens.4Scaling and beyond
One server knows only its own clients. Run three copies behind a load balancer and a message on copy 1 never reaches copy 2's clients. The fix is a broker like Redis pub/sub: each copy publishes the message and receives what others publish, then broadcasts it to its own clients.
Worked example
Problem: the café order board refreshes itself with a request every 3 seconds, and 50 screens mean 1000 requests a minute, mostly empty. Analysis: the data changes rarely but the asking never stops. Fix: one WebSocket per screen, and the server pushes {"type": "order", ...} only when an order arrives. Zero empty requests, and the order shows within a fraction of a second.
Myth vs Reality
| Myth | Reality |
|---|---|
| WebSockets fully replace HTTP. | No. Use HTTP for ordinary requests (sign-in, loading a page) and WebSockets only for live parts. |
| A dropped connection reconnects by itself. | The browser does not reconnect automatically. Write reconnection with a growing delay. |
Where you meet it at work
💬Chat
Instant messages and a "typing" indicator.
📈Live dashboards
Prices, orders and monitoring updating in real time.
🎮Multiplayer games
Player moves reach everyone at once.
Test your understanding
What does the server reply to a successful WebSocket upgrade?
101 means the connection switched from HTTP to the WebSocket protocol.
Where do you put removing a client from the set?
finally runs whether the connection ended cleanly or with an error.
Three server copies, and broadcasts do not reach every client. The fix?
Each copy knows only its clients, and the broker carries the message to every copy.
Quick review cards
Check Your Understanding
When do you pick SSE over WebSockets?
When only the server sends and the client sends nothing live, like notifications or a stream of text from a language model.
Why JSON with a type field?
Because there is one channel and many kinds of messages. The field tells the receiver how to react.
What happens if you do not remove a disconnected client?
Ghosts pile up, broadcast writes fail and memory leaks.
Resources for this lesson
Grouped by type. Start with the official docs for precision, or an article or video for the explanation.
Done with this lesson?
Sign in to save your progress.