Real-Time Systems & Messaging

WebSockets vs SSE vs WebRTC: Choosing the Right Real-Time Protocol

MatterAI
MatterAI
5 min read·

Real-Time Applications: WebSockets vs Server-Sent Events vs WebRTC Implementation

Real-time web applications require persistent communication channels between client and server. The three primary protocols for this are WebSockets (full-duplex TCP), Server-Sent Events (unidirectional HTTP), and WebRTC (peer-to-peer UDP). Selecting the correct protocol depends on data directionality, latency requirements, and network topology.

WebSockets

WebSockets provide a full-duplex communication channel over a single TCP connection. After an initial HTTP handshake, the connection upgrades to the WebSocket protocol, allowing data to flow in both directions simultaneously with minimal overhead.

Architecture

  • Transport: TCP
  • Direction: Bidirectional
  • Connection State: Persistent until explicitly closed
  • Use Cases: Chat applications, multiplayer games, real-time trading platforms

Implementation

Client-side (Browser):

const socket = new WebSocket('wss://api.example.com/stream');

socket.onopen = () => {
  console.log('Connection established');
  socket.send(JSON.stringify({ type: 'subscribe', channel: 'updates' }));
};

socket.onmessage = (event) => {
  const data = JSON.parse(event.data);
  console.log('Received:', data);
};

socket.onclose = () => console.log('Connection closed');

Server-side (Node.js using ws):

const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });

wss.on('connection', (ws) => {
  ws.on('message', (message) => {
    console.log('Received:', message);
    // Broadcast to all clients except sender
    wss.clients.forEach((client) => {
      if (client !== ws && client.readyState === WebSocket.OPEN) {
        client.send(message);
      }
    });
  });
});

Security

  • Encryption: Always use wss:// (WebSocket Secure) over TLS to prevent data interception.
  • Authentication: Validate user identity during the handshake or immediately after connection via a token-based mechanism.

Server-Sent Events (SSE)

SSE is a unidirectional protocol allowing servers to push updates to clients over standard HTTP. It uses a persistent connection where the server keeps the response open indefinitely, streaming text-based events formatted as text/event-stream.

Architecture

  • Transport: HTTP/1.1
  • Direction: Server-to-Client only
  • Connection State: Persistent with automatic reconnection
  • Use Cases: Live news feeds, stock tickers, notification systems
  • Limitations: Browsers limit connections to a maximum of 6 concurrent SSE connections per domain.

Implementation

Client-side (Browser):

const eventSource = new EventSource('https://api.example.com/events');

eventSource.onmessage = (event) => {
  const data = JSON.parse(event.data);
  console.log('Update:', data);
};

eventSource.onerror = (err) => {
  console.error('EventSource failed:', err);
  eventSource.close();
};

Server-side (Node.js):

const express = require('express');
const app = express();

app.get('/events', (req, res) => {
  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
  res.setHeader('Connection', 'keep-alive');

  // Send an event every 2 seconds
  const interval = setInterval(() => {
    const data = { time: new Date().toISOString() };
    res.write(`data: ${JSON.stringify(data)}\n\n`);
  }, 2000);

  req.on('close', () => {
    clearInterval(interval);
    res.end();
  });
});

app.listen(3000);

Security

  • CORS: Configure Cross-Origin Resource Sharing headers strictly to allow only intended domains.
  • Authentication: Use standard HTTP authentication headers or cookies, as SSE relies on standard HTTP requests.

WebRTC

WebRTC (Web Real-Time Communication) enables peer-to-peer data, audio, and video transfer directly between browsers. It uses UDP or TCP transports selected via ICE (Interactive Connectivity Establishment), prioritizing UDP for latency while utilizing TCP candidates if UDP is blocked. It requires a signaling mechanism to exchange connection metadata.

Architecture

  • Transport: UDP or TCP (selected via ICE candidates)
  • Direction: Peer-to-Peer (Bidirectional)
  • Connection State: Managed via ICE/STUN/TURN
  • Use Cases: Video conferencing, file sharing, low-latency gaming

Implementation

Client-side (Browser - Data Channel):

const peerConnection = new RTCPeerConnection({
  iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
});

// Signaling channel (WebSocket) to exchange SDP and ICE candidates
const signaling = new WebSocket('wss://api.example.com/signaling');

// Handle incoming signaling messages
signaling.onmessage = async (event) => {
  const data = JSON.parse(event.data);

  if (data.type === 'offer') {
    // Receiver: Set remote description and create answer
    await peerConnection.setRemoteDescription(data.sdp);
    const answer = await peerConnection.createAnswer();
    await peerConnection.setLocalDescription(answer);
    signaling.send(JSON.stringify({ type: 'answer', sdp: answer }));
  } else if (data.type === 'answer') {
    // Initiator: Set remote description from answer
    await peerConnection.setRemoteDescription(data.sdp);
  } else if (data.type === 'candidate') {
    // Both: Add received ICE candidate
    await peerConnection.addIceCandidate(data.candidate);
  }
};

// Create data channel (initiator only)
const dataChannel = peerConnection.createDataChannel('chat');
dataChannel.onopen = () => dataChannel.send('Hello Peer');
dataChannel.onmessage = (event) => console.log('Peer message:', event.data);

// Handle ICE candidates
peerConnection.onicecandidate = (event) => {
  if (event.candidate) {
    signaling.send(JSON.stringify({ type: 'candidate', candidate: event.candidate }));
  }
};

// Initiator: Create offer
peerConnection.createOffer()
  .then(offer => peerConnection.setLocalDescription(offer))
  .then(() => {
    signaling.send(JSON.stringify({ type: 'offer', sdp: peerConnection.localDescription }));
  });

Security

  • Encryption: WebRTC mandates DTLS-SRTP for all media and data channels, ensuring content is encrypted.
  • Signaling: The signaling server must use secure protocols (WSS/HTTPS) to protect session descriptions during exchange.

Comparative Analysis

FeatureWebSocketsSSEWebRTC
DirectionBidirectionalUnidirectional (Server -> Client)Bidirectional (P2P)
TransportTCPHTTPUDP or TCP (ICE selected)
LatencyLowLowLowest
OverheadFraming overhead (2-14 bytes)Standard HTTP headersSTUN/TURN signaling overhead
Binary DataSupportedNo (Text only)Supported
Browser SupportUniversalUniversal (No IE)Universal
Firewall/NATGenerally goodExcellent (uses HTTP)Complex (requires TURN)

Getting Started

  1. Use WebSockets if you need bidirectional communication (e.g., a chat app where users send and receive messages).
  2. Use SSE if you only need to push updates from the server to the client (e.g., a live dashboard or news feed) and want simpler implementation with automatic reconnection.
  3. Use WebRTC if you need low-latency peer-to-peer communication (e.g., video calls or file transfers) or want to offload bandwidth from your server to the clients.
  4. Implement a signaling server (using WebSockets) before attempting WebRTC. Peers cannot discover each other directly; the signaling server acts as an intermediary to exchange SDP session descriptions and ICE network candidates, enabling the peers to establish a direct connection.

Share this Guide:

More Guides

Agentic Workflows: Building Self-Correcting Loops with LangGraph and CrewAI State Machines

Build production-ready AI agents that iteratively improve their outputs through automated feedback loops, combining LangGraph's state machine architecture with CrewAI's multi-agent orchestration for robust, self-correcting workflows.

14 min read

Bun Runtime Migration: Porting High-Traffic Node.js APIs with Native APIs and SQLite

Learn how to migrate high-traffic Node.js APIs to Bun for 4× HTTP throughput and 3.8× database performance gains using native APIs and bun:sqlite.

10 min read

Deno 2.0 Workspaces: Build Monorepos with JSR Packages and TypeScript-First Development

Learn how to configure Deno 2.0 workspaces for monorepo management, publish TypeScript packages to JSR, and automate releases with OIDC-authenticated CI/CD pipelines.

7 min read

Gleam on BEAM: Building Type-Safe, Fault-Tolerant Distributed Systems

Learn how Gleam combines Hindley-Milner type inference with Erlang's actor-based concurrency model to build systems that are both compile-time safe and runtime fault-tolerant. Covers OTP integration, supervision trees, and seamless interoperability with the BEAM ecosystem.

5 min read

Hono Edge Framework: Build Ultra-Fast APIs for Cloudflare Workers and Bun

Master Hono's zero-dependency web framework to build low-latency edge APIs that deploy seamlessly across Cloudflare Workers, Bun, and other JavaScript runtimes. Learn routing, middleware, validation, and real-time streaming patterns optimized for edge computing.

6 min read

Ship Faster. Ship Safer.

Join thousands of engineering teams using MatterAI to autonomously build, review, and deploy code with enterprise-grade precision.

No credit card requiredSOC 2 Type IISetup in 2 min