Back to blogs
Engineering3 min readSep 6, 2026

WebSockets, WebRTC, and Real-Time Video: A Quick Tour

A short tour of how real-time apps actually work under the hood, from WebSockets and Socket.IO to WebRTC signaling, STUN/TURN, data channels, and video streaming.

WebRTCWebSocketsSocket.IOvideo streamingreal-time

Real-time features all look the same from the outside (a chat that updates instantly, a video call, a live cursor) but under the hood they split into two very different mechanisms: relayed (WebSockets) and peer-to-peer (WebRTC).

WebSockets: a persistent, two-way pipe

A normal HTTP request is one-shot: ask, get an answer, connection closes. A WebSocket upgrades that into a single long-lived TCP connection both sides can push through at any time: no polling, no re-handshaking per message. Socket.IO wraps this in rooms, automatic reconnection, and a fallback transport, which is why it's the default choice for chat, notifications, and live dashboards.

The catch: every message still round-trips through your server. Fine for small JSON events. Not fine for continuous audio/video: that's a lot of bandwidth to relay for no reason when the two participants could just talk to each other directly.

WebRTC: browser-native peer-to-peer

WebRTC lets two browsers exchange audio, video, and arbitrary data directly, with no server in the media path. It has three moving parts:

1. Signaling

WebRTC deliberately doesn't define how two peers find each other: that's left to you, and in practice it's almost always your existing WebSocket/Socket.IO server. Its only job is ferrying two messages back and forth: an SDP offer from the caller and an SDP answer from the callee, describing codecs and media capabilities each side supports.

2. NAT traversal: STUN and TURN

Most devices sit behind a router doing NAT, so they don't know their own public address. A STUN server answers "what does the internet see me as?", which is enough for a direct connection most of the time. When both sides are behind strict/symmetric NAT (common on corporate networks), no direct path exists, and a TURN server relays the media instead, slower, but guaranteed to work.

3. Media & data channels

navigator.mediaDevices.getUserMedia() grabs camera/mic as a MediaStream. Its tracks get added to an RTCPeerConnection, and the matching tracks show up in a <video> element on the other end once the connection is up. Alongside that, RTCDataChannel opens a second peer-to-peer pipe for plain data (game state, file chunks, cursor positions) with the same low latency as the media path.

Wiring up a minimal video call

  1. Both browsers connect to a signaling server (a Socket.IO namespace works fine).
  2. The caller creates an RTCPeerConnection, attaches its local stream, and sends an offer through signaling.
  3. The callee creates its own connection, sets the offer as remote description, and sends back an answer.
  4. Both sides trickle ICE candidates to each other as they're discovered.
  5. Once ICE finishes negotiating a path (direct or via TURN), media starts flowing directly between the two browsers. The signaling server is no longer involved.

Where peer-to-peer stops scaling

A mesh topology (every participant connects to every other participant) works for a 1:1 call and holds up reasonably to 4 to 6 people. Past that, each participant's upload bandwidth and CPU cost multiply per additional peer, and it falls over. That's the point where you'd introduce an SFU (Selective Forwarding Unit): each peer uploads its stream once, and the server fans it out to everyone else, trading a bit of latency for a topology that actually scales.


Draft placeholder. Written as a sample while wiring up the blog. Replace with your own version.