Arquitetura Real-time em 2026: WebSockets, SSE e WebRTC, quando usar cada um
Streaming de dados, notificações push e comunicação peer-to-peer exigem escolhas arquiteturais corretas.
Resumo executivo
Streaming de dados, notificações push e comunicação peer-to-peer exigem escolhas arquiteturais corretas.
Ultima atualizacao: 12/03/2026
Introdução: O fim da era do polling
A arquitetura tradicional de polling — o cliente perguntando ao servidor "tem novidade?" a cada X segundos — morreu. É ineficiente, desperdiça largura de banda, entrega latência inconsistente e escala mal. Em 2026, qualquer aplicação moderna que exija atualizações real-time precisa de uma das três tecnologias fundamentais: WebSockets, Server-Sent Events (SSE) ou WebRTC.
Cada uma serve a um propósito distinto. Escolher a errada significa desperdiçar recursos, criar problemas de escalabilidade ou implementar funcionalidades que simplesmente não funcionam em produção.
O espectro das necessidades real-time
Antes de escolher a tecnologia, é preciso entender o que você realmente precisa:
Latenia ══════════════════════════════════════════►
Baixa Média Alta
Dupla ══════════════════════════════════════════►
direção Unidirecional Bidirecional
Escala ══════════════════════════════════════════►
1-1 1-N N-N
Conexão ══════════════════════════════════════════►
Cliente-Server P2PWebSockets: comunicação bidirecional full-duplex
WebSockets são o padrão para comunicação bidirecional persistente entre cliente e servidor. Ambas as partes podem enviar mensagens a qualquer momento, sem overhead de HTTP.
Arquitetura WebSocket
┌──────────────┐ HTTP Upgrade ┌──────────────┐
│ Browser │ ◄══════════════════════════► │ Server │
│ │ │ │
│ Client │ ◄──── JSON Message ───────► │ Handler │
│ │ │ │
│ │ ───── Acknowledgment ────► │ │
└──────────────┘ └──────────────┘
│ │
│ Event Handler Event Emitter
└─────────────────────────────────────────────────┘Quando usar WebSockets
Aplicações colaborativas:
- Editores de documentos em tempo real (Google Docs style)
- Quadros brancos colaborativos
- Ferramentas de项目管理 em equipe
Streaming de dados bidirecional:
- Chats em tempo real
- Jogos multiplayer
- Controle remoto de dispositivos
Monitoramento e dashboards:
- Dashboards de operações com atualizações contínuas
- Monitoramento de sistemas em tempo real
- Feed de notificações com confirmations
Implementação com Socket.IO
typescript// Servidor: Node.js com Socket.IO
import { Server } from 'socket.io';
import { createServer } from 'http';
const httpServer = createServer();
const io = new Server(httpServer, {
cors: {
origin: process.env.ALLOWED_ORIGINS?.split(',') || '*',
credentials: true
},
transports: ['websocket', 'polling'], // Fallback para polling
pingTimeout: 60000,
pingInterval: 25000
});
// Middleware de autenticação
io.use(async (socket, next) => {
const token = socket.handshake.auth.token;
if (!token) {
return next(new Error('Authentication error'));
}
try {
const user = await verifyToken(token);
socket.data.user = user;
next();
} catch (error) {
next(new Error('Authentication error'));
}
});
// Room management
io.on('connection', (socket) => {
const user = socket.data.user;
console.log(`User connected: ${user.id}`);
// Usuário entra na sala da organização dele
socket.join(`org:${user.organizationId}`);
// Sala pessoal para notificações diretas
socket.join(`user:${user.id}`);
// Handle incoming messages
socket.on('message:send', async (data) => {
const { roomId, content } = data;
// Salva mensagem no banco
const message = await saveMessage({
userId: user.id,
roomId,
content,
timestamp: new Date()
});
// Broadcast para todos na sala (exceto remetente)
socket.to(roomId).emit('message:received', message);
// Acknowledge para o remetente
socket.emit('message:ack', { id: message.id });
});
// Handle presence (digitando...)
socket.on('typing:start', (data) => {
const { roomId } = data;
socket.to(roomId).emit('typing:started', {
userId: user.id,
userName: user.name
});
});
socket.on('typing:stop', (data) => {
const { roomId } = data;
socket.to(roomId).emit('typing:stopped', {
userId: user.id
});
});
// Graceful disconnect
socket.on('disconnect', () => {
console.log(`User disconnected: ${user.id}`);
// Notifica saída
socket.to(`org:${user.organizationId}`).emit('user:left', {
userId: user.id,
userName: user.name
});
});
});
// Broadcast para toda organização
export function broadcastToOrganization(orgId: string, event: string, data: any) {
io.to(`org:${orgId}`).emit(event, data);
}
// Envia para usuário específico
export function sendToUser(userId: string, event: string, data: any) {
io.to(`user:${userId}`).emit(event, data);
}
httpServer.listen(3001);Cliente TypeScript/React
typescript// Cliente: React com Socket.IO Client
import { useEffect, useRef } from 'react';
import { io, Socket } from 'socket.io-client';
import { useAuth } from '@/hooks/useAuth';
export function RealtimeChat({ roomId }: { roomId: string }) {
const socketRef = useRef<Socket | null>(null);
const { token } = useAuth();
useEffect(() => {
// Conecta ao servidor WebSocket
const socket = io(process.env.NEXT_PUBLIC_WS_URL || 'ws://localhost:3001', {
auth: { token },
transports: ['websocket', 'polling'],
reconnection: true,
reconnectionDelay: 1000,
reconnectionAttempts: 5
});
socketRef.current = socket;
// Se conecta à sala
socket.emit('join:room', { roomId });
// Escuta mensagens
socket.on('message:received', (message) => {
// Atualiza estado
appendMessage(message);
});
// Escupa typing status
socket.on('typing:started', (data) => {
showTypingIndicator(data);
});
socket.on('typing:stopped', (data) => {
hideTypingIndicator(data);
});
// Cleanup
return () => {
socket.disconnect();
};
}, [roomId, token]);
const sendMessage = (content: string) => {
if (socketRef.current) {
socketRef.current.emit('message:send', { roomId, content });
}
};
return (
<div className="chat-container">
{/* UI de chat */}
</div>
);
}Considerações de produção
Gerenciamento de conexões:
typescript// Rate limiting de conexões por IP
const connectionTracker = new Map<string, number>();
io.use((socket, next) => {
const ip = socket.handshake.address;
const count = connectionTracker.get(ip) || 0;
if (count >= 10) {
return next(new Error('Too many connections'));
}
connectionTracker.set(ip, count + 1);
socket.on('disconnect', () => {
const current = connectionTracker.get(ip) || 0;
connectionTracker.set(ip, Math.max(0, current - 1));
});
next();
});Horizontal scaling com Redis:
typescript// Para múltiplos servidores WebSocket, usa Redis Adapter
import { createAdapter } from '@socket.io/redis-adapter';
import { createClient } from 'redis';
const pubClient = createClient({ url: process.env.REDIS_URL });
const subClient = pubClient.duplicate();
await Promise.all([
pubClient.connect(),
subClient.connect()
]);
io.adapter(createAdapter(pubClient, subClient));Server-Sent Events (SSE): streaming unidirecional simples
SSE é uma tecnologia muito mais simples que WebSockets: o servidor envia eventos para o cliente em um fluxo contínuo, mas o cliente não pode enviar mensagens de volta pelo mesmo canal.
Arquitetura SSE
┌──────────────┐ HTTP GET /stream ┌──────────────┐
│ Browser │ ◄════════════════════════► │ Server │
│ │ │ │
│ EventSource│ ◄────── data: {} ──────────► │ Generator │
│ │ │ │
│ │ ◄────── retry: 3000 ──────► │ │
└──────────────┘ └──────────────┘Quando usar SSE
Streaming de dados:
- Feed de preços de ações em tempo real
- Atualizações de IoT (sensores enviando dados)
- Log streaming para dashboards
Notificações push:
- Atualizações de feed social
- Alertas de sistema
- Notificações de aplicação
Implementação SSE com Node.js
typescript// Servidor SSE com Express
import express from 'express';
import { Server } from 'http';
import { verifyToken } from './auth';
const app = express();
const server = new Server(app);
interface SSEConnection {
userId: string;
response: express.Response;
lastHeartbeat: number;
}
const connections = new Map<string, SSEConnection>();
app.get('/stream', async (req, res) => {
const token = req.headers.authorization?.replace('Bearer ', '');
if (!token) {
return res.status(401).end();
}
try {
const user = await verifyToken(token);
// Configura headers SSE
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
res.setHeader('X-Accel-Buffering', 'no'); // Para nginx
// Armazena conexão
const connection: SSEConnection = {
userId: user.id,
response: res,
lastHeartbeat: Date.now()
};
connections.set(user.id, connection);
// Envia evento inicial
sendEvent(res, 'connected', { userId: user.id, timestamp: Date.now() });
// Heartbeat
const heartbeatInterval = setInterval(() => {
sendEvent(res, 'heartbeat', { timestamp: Date.now() });
}, 30000);
// Cleanup quando cliente desconecta
req.on('close', () => {
clearInterval(heartbeatInterval);
connections.delete(user.id);
});
// Cleanup se timeout
const timeout = setTimeout(() => {
clearInterval(heartbeatInterval);
connections.delete(user.id);
res.end();
}, 5 * 60 * 1000); // 5 minutos max
} catch (error) {
res.status(401).end();
}
});
// Envia evento para usuário específico
export function sendSSEEvent(userId: string, type: string, data: any) {
const connection = connections.get(userId);
if (connection) {
sendEvent(connection.response, type, data);
}
}
function sendEvent(res: express.Response, type: string, data: any) {
const event = `event: ${type}\ndata: ${JSON.stringify(data)}\n\n`;
res.write(event);
}
// Broadcast para todos
export function broadcastSSEEvent(type: string, data: any) {
for (const [userId, connection] of connections) {
sendEvent(connection.response, type, data);
}
}Cliente com EventSource
typescript// Cliente: EventSource API nativa
class SSEClient {
private eventSource: EventSource | null = null;
connect(url: string, token: string) {
this.eventSource = new EventSource(`${url}?token=${token}`);
this.eventSource.addEventListener('connected', (event) => {
console.log('SSE Connected:', JSON.parse(event.data));
});
this.eventSource.addEventListener('notification', (event) => {
const data = JSON.parse(event.data);
this.handleNotification(data);
});
this.eventSource.addEventListener('heartbeat', () => {
// Mantém conexão viva
});
this.eventSource.onerror = (error) => {
console.error('SSE Error:', error);
// EventSource tenta reconectar automaticamente
};
}
disconnect() {
if (this.eventSource) {
this.eventSource.close();
this.eventSource = null;
}
}
private handleNotification(data: any) {
// Processa notificação
}
}Vantagens e limitações
Vantagens:
- Implementação extremamente simples
- Nativas no browser (EventSource API)
- Automaticamente reconecta
- Funciona com proxies HTTP padrão
- Usa apenas uma conexão HTTP
Limitações:
- Apenas unidirecional (server → client)
- Sem suporte a binary data (só texto)
- Limitado a UTF-8
- Cada requisição cria nova conexão (não multiplexa)
WebRTC: comunicação peer-to-peer direta
WebRTC permite comunicação direta entre navegadores, sem passar pelo servidor. É a tecnologia por trás de videoconferências, compartilhamento de arquivos P2P e jogos multiplayer.
Arquitetura WebRTC
┌──────────────┐ ┌──────────────┐
│ Browser A │ │ Browser B │
│ │ │ │
│ Peer │ ←──── P2P Stream ────► │ Peer │
│ │ │ │
└──────────────┘ └──────────────┘
▲ ▲
│ │
│ ┌──────────────┐ │
└─────────────│ Signaling │──────────────┘
│ Server │
│ (WebSocket)│
└──────────────┘Quando usar WebRTC
Comunicação de baixa latência:
- Videoconferências
- Voice over IP
- Jogos multiplayer em tempo real
Transferência de grandes volumes de dados:
- Compartilhamento de arquivos P2P
- Streaming de vídeo
- Sincronização de dados grandes
Implementação WebRTC com SimplePeer
typescript// Cliente: WebRTC com SimplePeer
import SimplePeer from 'simple-peer';
import { socket } from './websocket-client';
class WebRTCClient {
private peers: Map<string, SimplePeer.Instance> = new Map();
private localStream: MediaStream | null = null;
async initialize(userId: string) {
// Obtém stream de mídia local (câmera/mic)
this.localStream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true
});
// Escuta sinais do servidor de signaling
socket.on('webrtc:signal', ({ from, signal }) => {
this.handleSignal(from, signal);
});
socket.on('webrtc:user_joined', ({ userId }) => {
this.createPeer(userId);
});
socket.on('webrtc:user_left', ({ userId }) => {
this.destroyPeer(userId);
});
// Avisa que entrou
socket.emit('webrtc:join', { userId });
}
private createPeer(userId: string) {
const peer = new SimplePeer({
initiator: true,
trickle: false,
stream: this.localStream!,
config: {
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
{ urls: 'stun:global.stun.twilio.com:3478' }
]
}
});
peer.on('signal', (signal) => {
// Envia sinal para o outro peer via servidor de signaling
socket.emit('webrtc:signal', {
to: userId,
signal
});
});
peer.on('stream', (stream) => {
// Recebe stream do outro peer
this.handleRemoteStream(userId, stream);
});
peer.on('connect', () => {
console.log(`Connected to peer ${userId}`);
});
peer.on('close', () => {
console.log(`Connection closed with peer ${userId}`);
this.peers.delete(userId);
});
this.peers.set(userId, peer);
}
private handleSignal(from: string, signal: any) {
let peer = this.peers.get(from);
if (!peer) {
// Se é o primeiro sinal, cria peer (non-initiator)
peer = new SimplePeer({
initiator: false,
trickle: false,
stream: this.localStream!
});
peer.on('signal', (signal) => {
socket.emit('webrtc:signal', {
to: from,
signal
});
});
peer.on('stream', (stream) => {
this.handleRemoteStream(from, stream);
});
peer.on('close', () => {
this.peers.delete(from);
});
this.peers.set(from, peer);
}
// Processa sinal
peer.signal(signal);
}
private handleRemoteStream(userId: string, stream: MediaStream) {
// Exibe stream remoto (por exemplo, em um elemento de vídeo)
const videoElement = document.getElementById(`video-${userId}`) as HTMLVideoElement;
if (videoElement) {
videoElement.srcObject = stream;
}
}
private destroyPeer(userId: string) {
const peer = this.peers.get(userId);
if (peer) {
peer.destroy();
this.peers.delete(userId);
}
}
cleanup() {
for (const peer of this.peers.values()) {
peer.destroy();
}
this.peers.clear();
if (this.localStream) {
this.localStream.getTracks().forEach(track => track.stop());
}
}
}Servidor de Signaling
typescript// Servidor: WebSocket para signaling WebRTC
io.on('connection', (socket) => {
socket.on('webrtc:join', ({ userId }) => {
socket.data.userId = userId;
socket.join('video-call');
// Notifica outros usuários
socket.to('video-call').emit('webrtc:user_joined', { userId });
});
socket.on('webrtc:signal', ({ to, signal }) => {
// Encaminha sinal para o peer destino
socket.to(to).emit('webrtc:signal', {
from: socket.data.userId,
signal
});
});
socket.on('disconnect', () => {
const userId = socket.data.userId;
if (userId) {
socket.to('video-call').emit('webrtc:user_left', { userId });
}
});
});Comparação final
| Característica | WebSocket | SSE | WebRTC |
|---|---|---|---|
| Direção | Bidirecional | Server → Client | Bidirecional |
| Latência | Baixa | Baixa | Muito baixa |
| Simplicidade | Média | Alta | Baixa |
| Escalabilidade | Média* | Alta* | N/A (P2P) |
| Binary data | Sim | Não | Sim |
| Proxy friendly | Sim | Sim | Difícil |
| Mobile battery | Médio impacto | Baixo impacto | Alto impacto |
| Uso principal | Chat, colaboração | Notificações, streaming | Vídeo, P2P |
\Com Redis adapter ou similar*
Sua aplicação precisa de comunicação real-time, mas você não sabe qual tecnologia escolher? Fale com especialistas da Imperialis sobre arquiteturas de comunicação real-time, de WebSockets a WebRTC, para escalar com performance e confiabilidade.
Fontes
- MDN WebSockets API — Documentação WebSockets
- MDN Server-Sent Events — Documentação SSE
- MDN WebRTC API — Documentação WebRTC
- Socket.IO documentation — Guia Socket.IO