How WebSockets Work, Why They Matter, and How to Build Seamless Real-Time Experiences
Ever used a chat app, a live stock ticker, or a multiplayer game and wondered how they instantly update without you refreshing the page? That magic is most likely powered by WebSockets.
Unlike traditional HTTP requests, WebSockets create a persistent connection between the client and the server, allowing real-time, two-way communication. But how does it actually work under the hood? Why do we need WebSockets when we already have HTTP? Let’s break it all down.
Before WebSockets, real-time communication over the web was a bit of a hack. We had:
Polling: Clients send requests to the server at fixed intervals (e.g., every 5 seconds) to check if there’s any new data. This wastes bandwidth and creates unnecessary load on the server.
Long Polling: A slightly better approach where the server holds the request open until there’s new data. When data is available, the server responds, and the client immediately sends a new request. This is better than polling but still inefficient.
Server-Sent Events (SSE): Allows servers to push updates to clients but only works in one direction (server → client). This is not suitable for applications that require two-way communication.
Enter WebSockets—a full-duplex, low-latency communication protocol designed to solve these issues.
WebSockets provide a bidirectional, persistent connection between a client and a server over a single TCP connection. This means once a WebSocket connection is established, both the client and server can send and receive messages instantly without needing to make new requests.
Handshake:
WebSockets start as an HTTP request. The client sends an HTTP request with an Upgrade
header to ask the server to switch protocols.
If the server supports WebSockets, it responds with 101 Switching Protocols
, upgrading the connection.
Example WebSocket Handshake Request (from client):
GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Response from Server:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Persistent Connection:
Once the handshake is complete, the connection remains open. No more HTTP requests are needed. The client and server can now freely exchange messages over the same connection.
Message Framing:
Unlike HTTP, which sends full documents, WebSockets work with frames.
Each WebSocket frame has a small overhead (~2-14 bytes) compared to an HTTP request (~700-800 bytes).
Messages can be binary or text.
Closing the Connection:
Either the client or server can close the connection using a CLOSE
frame.
Scaling WebSockets can be tricky because they require a persistent connection to be maintained between the client and the server. Unlike HTTP, which is stateless, WebSockets need a few extra considerations when scaling:
Load Balancing: Since WebSockets are stateful, you need sticky sessions or WebSocket-aware load balancers to ensure clients consistently connect to the same server instance.
Message Broadcasting: If multiple clients are connected and need to receive the same messages, using a message broker like Redis Pub/Sub can help distribute messages efficiently across multiple WebSocket servers.
Horizontal Scaling: Instead of one giant WebSocket server, you can run multiple instances and distribute connections among them using Kubernetes or a cloud service that supports WebSockets.
WebSocket Gateways: Services like AWS API Gateway provide managed WebSocket support, handling scaling and reconnections for you, but they come with limitations in state handling.
Let’s implement a simple real-time data synchronization between a server and multiple clients using WebSockets.
Imagine we are building a real-time collaborative text editor, where multiple users can edit the same document simultaneously, and every change is instantly synchronized across all clients.
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
let documentState = ""; // Shared document state
wss.on('connection', (ws) => {
console.log('New client connected');
// Send current document state to the newly connected client
ws.send(JSON.stringify({ type: 'sync', content: documentState }));
ws.on('message', (message) => {
const data = JSON.parse(message);
if (data.type === 'edit') {
documentState = data.content; // Update document state
console.log(`Document updated: ${documentState}`);
// Broadcast the updated document state to all connected clients
wss.clients.forEach(client => {
if (client !== ws && client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify({ type: 'sync', content: documentState }));
}
});
}
});
ws.on('close', () => {
console.log('Client disconnected');
});
});
Client Establishes a Connection: When a new client connects, it establishes a WebSocket connection with the server.
Server Sends Initial Data: The server immediately sends the current state of the document to the client.
User Makes an Edit: When a user types something in the editor, the client sends an edit
message containing the updated text to the server.
Server Updates Global State: The server updates its stored documentState
variable with the new content.
Broadcast to Other Clients: The server loops through all connected clients (except the sender) and sends them the updated document state.
Clients Sync in Real-Time: All clients listening for updates receive the new state and update their UI instantly, ensuring everyone sees the same content at the same time.
const socket = new WebSocket('ws://localhost:8080');
socket.onopen = () => {
console.log('Connected to server');
};
socket.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === 'sync') {
document.getElementById('editor').value = data.content;
}
};
function sendEdit() {
const newText = document.getElementById('editor').value;
socket.send(JSON.stringify({ type: 'edit', content: newText }));
}
<!DOCTYPE html>
<html>
<head>
<title>Real-Time Editor</title>
</head>
<body>
<textarea id="editor" oninput="sendEdit()"></textarea>
<script src="client.js"></script>
</body>
</html>
WebSockets are perfect for scenarios where real-time, bidirectional communication is required.
If you’re building a chat app like WhatsApp or Slack, a stock market dashboard, a multiplayer game, or even a collaborative document editor, WebSockets make everything smooth and responsive. They allow instant updates without constant polling, making the application feel snappy and efficient.
However, WebSockets aren’t always the best choice. If you’re just making simple API calls or fetching static data, using traditional REST APIs or GraphQL might be more efficient. WebSockets shine in use cases where real-time, event-driven communication is necessary.
WebSockets provide a powerful way to build real-time applications without the inefficiencies of traditional polling. They establish a full-duplex connection that enables instant, bidirectional communication between the client and server.
With WebSockets, chat apps, live dashboards, gaming platforms, and even collaborative editing tools can function with near-instant updates, making the web a truly interactive space.
If you're working on anything real-time, give WebSockets a shot—they're efficient, reliable, and surprisingly fun to implement !!
- Jagadhiswaran Devaraj
📢 Stay Connected & Dive Deep into Tech!
🚀 Follow me for hardcore technical insights on JavaScript, Full-Stack Development, AI, and Scaling Systems:
🐦 X (Twitter): jags
✍️ Read more on Medium: https://medium.com/@jwaran78
💼 Connect with me on LinkedIn: https://www.linkedin.com/in/jagadhiswaran-devaraj/
Let’s geek out over code, architecture, and all things in tech! 💡🔥
Join Jagadhiswaran on Peerlist!
Join amazing folks like Jagadhiswaran and thousands of other people in tech.
Create ProfileJoin with Jagadhiswaran’s personal invite link.
0
7
0