WebSockets

Intermediate
Application Layer

WebSocket provides a full-duplex, persistent connection over a single TCP connection — enabling the server to push data to the client without polling, ideal for chat, live feeds, and collaborative apps.

Overview

HTTP is inherently request-response: the client always initiates, the server responds, and the connection may close. For real-time applications (chat, live dashboards, collaborative editing, gaming), this model is inefficient — clients must repeatedly poll for updates. WebSocket solves this with a persistent, bidirectional channel. It starts with an HTTP/1.1 Upgrade handshake and then upgrades the connection to the WebSocket protocol (RFC 6455). Once established, either side can send a frame at any time with minimal overhead (2–10 byte header vs HTTP's 500+ bytes). The connection stays open until explicitly closed. WebSocket works over port 80 (ws://) or 443 (wss:// — WebSocket over TLS). For high-scale servers, WebSocket connections are long-lived — a 10,000 concurrent connection server must manage memory and file descriptors carefully, favoring non-blocking I/O (Spring WebFlux, Netty) over thread-per-connection models.

WebSocket Handshake and Frame Format

WebSocket upgrades an HTTP connection. The client sends an Upgrade request; the server responds with 101 Switching Protocols. From that point, the connection is no longer HTTP — it speaks the WebSocket binary frame protocol.

WebSocket HTTP upgrade and Spring Boot handler
// WebSocket Upgrade handshake:
// Client → Server:
// GET /chat HTTP/1.1
// Host: ws.example.com
// Upgrade: websocket
// Connection: Upgrade
// Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==   ← random base64 nonce
// Sec-WebSocket-Version: 13
//
// Server → Client:
// HTTP/1.1 101 Switching Protocols
// Upgrade: websocket
// Connection: Upgrade
// Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=  ← SHA-1 of key+"GUID"
//
// ═══ Connection is now WebSocket — HTTP is gone ═══

// WebSocket frame header (2–10 bytes):
// ┌────┬────────┬────────────────────────┐
// │FIN │ Opcode │ Payload Length + Mask  │
// └────┴────────┴────────────────────────┘
// Opcodes: 0x1=text, 0x2=binary, 0x8=close, 0x9=ping, 0xA=pong
// Client→Server frames are masked (4-byte XOR mask); Server→Client are not

// Java WebSocket server (Spring Boot):
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(new ChatHandler(), "/chat").setAllowedOrigins("*");
    }
}

public class ChatHandler extends TextWebSocketHandler {
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        session.sendMessage(new TextMessage("Echo: " + message.getPayload()));
    }
}

Scaling WebSockets and Comparison with SSE

WebSocket connections are stateful and long-lived — each connection holds a session object in memory. Scaling across multiple server instances requires a shared pub/sub layer (Redis, Kafka) to route messages to the correct server holding each client's connection.

Redis pub/sub for WebSocket scale-out and SSE comparison
// Scaling WebSockets — sticky sessions OR pub/sub fan-out:
//
// Problem: Client A connected to Server 1, Client B to Server 2
//          Server 1 wants to send message to Client B → doesn't have its session
//
// Solution — Redis pub/sub fan-out:
// Server 1 publishes to Redis channel "room:123"
// Server 2 subscribes to "room:123" → receives message → sends to Client B's session

// Spring Boot WebSocket + Redis pub/sub:
@Autowired StringRedisTemplate redis;

// Send to Redis when message arrives:
redis.convertAndSend("room:" + roomId, jsonMessage);

// Subscribe and forward to local WebSocket sessions:
redis.listenTo(ChannelTopic.of("room:" + roomId), (message, pattern) -> {
    localSessions.forEach(session -> session.sendMessage(new TextMessage(message)));
});

// WebSocket vs Server-Sent Events (SSE):
// ┌─────────────────┬─────────────────┬──────────────────────┐
// │ Property        │ WebSocket       │ SSE                  │
// ├─────────────────┼─────────────────┼──────────────────────┤
// │ Direction       │ Full-duplex     │ Server → Client only │
// │ Protocol        │ WebSocket (ws)  │ HTTP                 │
// │ Reconnect       │ Manual          │ Automatic            │
// │ Binary support  │ Yes             │ No (text only)       │
// │ Proxy-friendly  │ Sometimes       │ Yes (plain HTTP)     │
// │ Use case        │ Chat, gaming    │ Live feed, progress  │
// └─────────────────┴─────────────────┴──────────────────────┘

Key Points to Remember

  • 1WebSocket upgrades an HTTP/1.1 connection to a persistent, full-duplex channel via HTTP 101.
  • 2Frame header is 2–10 bytes — far lower overhead than HTTP for frequent small messages.
  • 3wss:// = WebSocket over TLS (use always in production).
  • 4Long-lived connections require careful resource management — prefer non-blocking I/O (Netty, WebFlux).
  • 5Scale-out requires sticky sessions or a pub/sub layer (Redis, Kafka) to route messages across servers.
  • 6SSE is simpler for server-push-only use cases (live feeds, progress) — uses plain HTTP.

Interview Questions

Sign in to ask Aria
1

How does a WebSocket connection get established?

MediumHotstar
2

What is the difference between WebSocket and HTTP long polling?

MediumAmazon
3

How would you scale a WebSocket-based chat application to multiple server instances?

HardFlipkart
4

When would you choose SSE over WebSocket?

MediumThoughtWorks
5

What happens to WebSocket connections behind a load balancer that terminates sessions?

HardRazorpay

Ask Aria about WebSockets

Your personal AI tutor — ask anything about this concept

Revision Status

Personal Notes

Sign in to save personal notes for this topic.

Discussion

Sign in to join the discussion.

Loading discussion…