Cloud e plataforma

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.

12/03/20269 min de leituraCloud
Arquitetura Real-time em 2026: WebSockets, SSE e WebRTC, quando usar cada um

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           P2P

WebSockets: 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ísticaWebSocketSSEWebRTC
DireçãoBidirecionalServer → ClientBidirecional
LatênciaBaixaBaixaMuito baixa
SimplicidadeMédiaAltaBaixa
EscalabilidadeMédia*Alta*N/A (P2P)
Binary dataSimNãoSim
Proxy friendlySimSimDifícil
Mobile batteryMédio impactoBaixo impactoAlto impacto
Uso principalChat, colaboraçãoNotificações, streamingVí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

Leituras relacionadas