WebSocket vs Server-Sent Events: Escolhendo a Tecnologia Certa para Tempo Real em Produção
WebSocket e SSE resolvem comunicação em tempo real, mas não são intercambiáveis. Entender as diferenças arquiteturais previne gargalos de performance e complexidade operacional.
Resumo executivo
WebSocket e SSE resolvem comunicação em tempo real, mas não são intercambiáveis. Entender as diferenças arquiteturais previne gargalos de performance e complexidade operacional.
Ultima atualizacao: 10/03/2026
Resumo executivo
Funcionalidade em tempo real—atualizações ao vivo, notificações push, edição colaborativa—evoluiu de "bom de se ter" para pré-requisito para aplicações web modernas. As duas abordagens dominantes para comunicação em tempo real de navegador para servidor são WebSocket e Server-Sent Events (SSE).
A decisão de engenharia não é apenas técnica; é arquitetural. WebSocket fornece comunicação full-duplex, enquanto SSE é push unidirecional de servidor para cliente. Ambos têm características distintas de escala, requisitos operacionais e modos de falha. Escolher a tecnologia errada leva a complexidade de infraestrutura desnecessária, vulnerabilidades de segurança ou má experiência do usuário.
Modelos de conexão: Entendendo a diferença fundamental
WebSocket: Comunicação bidirecional full-duplex
WebSocket atualiza uma conexão HTTP para um socket TCP persistente que permite que os dados fluam em ambas as direções simultaneamente.
typescript// Implementação de cliente WebSocket
class RealtimeChatClient {
private ws: WebSocket;
private tentativasReconexao = 0;
private readonly MAX_TENTATIVAS_RECONEXAO = 5;
private readonly DELAY_RECONEXAO_MS = 3000;
constructor(private url: string) {
this.ws = this.conectar();
}
private conectar(): WebSocket {
const ws = new WebSocket(this.url);
ws.onopen = () => {
console.log('WebSocket conectado');
this.tentativasReconexao = 0;
// Enviar autenticação
this.enviarMensagem({
tipo: 'auth',
token: this.obterTokenAutenticacao()
});
};
ws.onmessage = (event) => {
const mensagem = JSON.parse(event.data);
this.tratarMensagem(mensagem);
};
ws.onerror = (erro) => {
console.error('Erro no WebSocket:', erro);
};
ws.onclose = (event) => {
console.log('WebSocket fechado:', event.code, event.reason);
if (!event.wasClean && this.tentativasReconexao < this.MAX_TENTATIVAS_RECONEXAO) {
this.tentativasReconexao++;
setTimeout(() => {
this.ws = this.conectar();
}, this.DELAY_RECONEXAO_MS);
}
};
return ws;
}
enviarMensagem(mensagem: any) {
if (this.ws.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify(mensagem));
} else {
console.error('WebSocket não está aberto');
}
}
private tratarMensagem(mensagem: any) {
switch (mensagem.tipo) {
case 'chat_message':
this.exibirMensagem(mensagem.dados);
break;
case 'presence_update':
this.atualizarPresenca(mensagem.dados);
break;
case 'typing_indicator':
this.mostrarIndicadorDigitacao(mensagem.dados);
break;
}
}
private obterTokenAutenticacao(): string {
return localStorage.getItem('auth_token') || '';
}
private exibirMensagem(dados: any) {
// Atualizar UI com nova mensagem
}
private atualizarPresenca(dados: any) {
// Atualizar indicadores de presença
}
private mostrarIndicadorDigitacao(dados: any) {
// Mostrar/ocultar indicador de digitação
}
}Server-Sent Events: Push unidirecional de servidor para cliente
SSE usa uma conexão HTTP de longa duração onde o servidor envia eventos para o cliente. O cliente não pode enviar dados de volta através da conexão SSE.
typescript// Implementação de cliente SSE
class LiveUpdatesClient {
private eventSource: EventSource;
private tentativasReconexao = 0;
private readonly MAX_TENTATIVAS_RECONEXAO = 5;
constructor(private url: string) {
this.eventSource = this.conectar();
}
private conectar(): EventSource {
const eventSource = new EventSource(this.url);
eventSource.onopen = () => {
console.log('Conexão SSE estabelecida');
this.tentativasReconexao = 0;
};
eventSource.onmessage = (event) => {
const dados = JSON.parse(event.data);
this.tratarAtualizacao(dados);
};
eventSource.onerror = (erro) => {
console.error('Erro no SSE:', erro);
this.eventSource.close();
if (this.tentativasReconexao < this.MAX_TENTATIVAS_RECONEXAO) {
this.tentativasReconexao++;
setTimeout(() => {
this.eventSource = this.conectar();
}, 3000);
}
};
return eventSource;
}
private tratarAtualizacao(dados: any) {
switch (dados.tipo) {
case 'price_update':
this.atualizarPreco(dados);
break;
case 'notification':
this.mostrarNotificacao(dados);
break;
case 'system_status':
this.atualizarStatus(dados);
break;
}
}
private atualizarPreco(dados: any) {
// Atualizar preço na UI
}
private mostrarNotificacao(dados: any) {
// Exibir notificação toast
}
private atualizarStatus(dados: any) {
// Atualizar indicador de status do sistema
}
// Nota: Não é possível enviar dados de volta através da conexão SSE
// Use fetch/HTTP para comunicação cliente-servidor
}Matriz de comparação: Quando usar qual
| Critério | WebSocket | SSE (Server-Sent Events) |
|---|---|---|
| Direcionalidade | Bidirecional (servidor ↔ cliente) | Unidirecional (servidor → cliente apenas) |
| Formato de dados | Binário ou texto | Texto apenas (tipicamente JSON) |
| Suporte de navegador | Excelente (todos os navegadores modernos) | Excelente (todos os navegadores modernos) |
| Compatibilidade de proxy | Pode requerer configuração especial | Funciona através da maioria dos proxies |
| Reconexão | Implementação manual necessária | Automática com backoff exponencial |
| Tipos de evento | Tipos de mensagem customizados | Canais de evento nomeados |
| Overhead de conexão | Handshake HTTP inicial + upgrade | Conexão HTTP persistente |
| Complexidade de escala | Alta (pool de conexões, gerenciamento de estado) | Moderada (stateless mais fácil) |
| Adequação de caso de uso | Chat, jogos, edição colaborativa | Atualizações ao vivo, notificações, tickers de ações |
Análise de casos de uso
Quando WebSocket é a escolha certa
1. Aplicações de chat em tempo real
Chat requer comunicação bidirecional: usuários enviam mensagens e recebem mensagens instantaneamente. A natureza full-duplex do WebSocket é ideal.
typescript// Aplicação de chat usando WebSocket
class AplicacaoChat {
private ws: WebSocket;
private usuarioAtual: Usuario;
async enviarMensagem(conteudo: string) {
this.ws.send(JSON.stringify({
tipo: 'send_message',
dados: {
userId: this.usuarioAtual.id,
conteudo,
timestamp: Date.now()
}
}));
}
private tratarMensagemRecebida(mensagem: ChatMessage) {
// Exibir mensagem recebida
// Atualizar contador de não lidas
// Disparar notificação se necessário
}
async definirStatusDigitacao(estaDigitando: boolean) {
this.ws.send(JSON.stringify({
tipo: 'typing_status',
dados: {
userId: this.usuarioAtual.id,
estaDigitando,
timestamp: Date.now()
}
}));
}
private tratarStatusDigitacao(status: TypingStatus) {
// Mostrar/ocultar indicador de digitação
}
}2. Jogos multiplayer
Jogos requerem comunicação bidirecional frequente, com baixa latência e múltiplos eventos simultâneos.
3. Edição colaborativa
Ferramentas como Google Docs ou Figma requerem sincronização em tempo real das ações do usuário em todos os participantes.
Quando SSE é a escolha certa
1. Atualizações de dados financeiros ao vivo
Preços de ações, valores de criptomoedas ou quaisquer tickers financeiros precisam apenas de push de servidor para cliente.
typescript// Ticker de ações usando SSE
class StockTickerClient {
private eventSource: EventSource;
private simbolosInscritos: Set<string> = new Set();
async inscrever(simbolos: string[]) {
// Use HTTP POST para se inscrever (não SSE)
await fetch('/api/acoes/inscrever', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ simbolos })
});
simbolos.forEach(simbolo => this.simbolosInscritos.add(simbolo));
}
async desinscrever(simbolos: string[]) {
await fetch('/api/acoes/desinscrever', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ simbolos })
});
simbolos.forEach(simbolo => this.simbolosInscritos.delete(simbolo));
}
private tratarAtualizacaoPreco(atualizacao: PriceUpdate) {
if (this.simbolosInscritos.has(atualizacao.simbolo)) {
this.atualizarDisplay(atualizacao);
}
}
private atualizarDisplay(atualizacao: PriceUpdate) {
// Atualizar preço na UI
// Destacar mudança de preço (verde/vermelho)
// Atualizar gráfico
}
}2. Notificações em tempo real
Notificações push, alertas ou atualizações de status do sistema fluem apenas do servidor para o cliente.
3. Monitoramento de dashboard ao vivo
Dashboards operacionais exibindo métricas, logs ou status do sistema precisam apenas de atualizações de servidor para cliente.
Considerações de escala
Desafios de escala de WebSocket
Conexões WebSocket são com estado e persistentes, o que cria complexidade de escala:
1. Pool de conexões e gerenciamento de estado
typescript// Servidor WebSocket com pool de conexões
class WebSocketServer {
private conexoes: Map<string, WebSocket> = new Map();
private usuarioSalas: Map<string, Set<string>> = new Map(); // userId -> roomIds
handleConnection(userId: string, ws: WebSocket) {
this.conexoes.set(userId, ws);
ws.on('message', (message) => {
const dados = JSON.parse(message);
this.tratarMensagem(userId, dados);
});
ws.on('close', () => {
this.conexoes.delete(userId);
this.removerUsuarioDeTodasSalas(userId);
});
}
broadcastParaSala(salaId: string, mensagem: any) {
const usuariosNaSala = this.obterUserIdsNaSala(salaId);
usuariosNaSala.forEach(userId => {
const ws = this.conexoes.get(userId);
if (ws && ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify(mensagem));
}
});
}
private obterUserIdsNaSala(salaId: string): string[] {
// Retornar todos os user IDs na sala
return Array.from(this.usuarioSalas.entries())
.filter(([_, salas]) => salas.has(salaId))
.map(([userId, _]) => userId);
}
private removerUsuarioDeTodasSalas(userId: string) {
const salas = this.usuarioSalas.get(userId);
if (salas) {
salas.forEach(salaId => this.sairDaSala(userId, salaId));
}
this.usuarioSalas.delete(userId);
}
}2. Compatibilidade de balanceador de carga
Conexões WebSocket requerem balanceadores de carga que suportam sessões sticky ou implementam roteamento consciente de WebSocket.
3. Escala horizontal através de múltiplos servidores
Quando conexões WebSocket são distribuídas através de múltiplos servidores, comunicação entre servidores é necessária para broadcast:
typescript// Pub/sub Redis para broadcast WebSocket cross-server
class DistributedWebSocketServer {
private conexoes: Map<string, WebSocket> = new Map();
private redisPublisher: Redis;
private redisSubscriber: Redis;
constructor(private serverId: string) {
this.redisSubscriber.subscribe('websocket:broadcast');
this.redisSubscriber.on('message', (channel, message) => {
this.tratarMensagemBroadcast(JSON.parse(message));
});
}
async broadcastParaTodos(mensagem: any) {
// Publicar no Redis para todos os servidores receberem
await this.redisPublisher.publish('websocket:broadcast', JSON.stringify({
serverId: this.serverId,
mensagem
}));
}
private tratarMensagemBroadcast(dados: any) {
// Não rebroadcast se mensagem originou deste servidor
if (dados.serverId === this.serverId) {
return;
}
// Enviar para todos os clientes conectados neste servidor
this.conexoes.forEach(ws => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify(dados.mensagem));
}
});
}
}Vantagens de escala de SSE
SSE é mais fácil de escalar porque:
1. Design de servidor sem estado
Servidores SSE podem ser sem estado e escalados horizontalmente atrás de balanceadores de carga padrão.
2. Reconexão integrada
A API EventSource gerencia automaticamente reconexão com backoff exponencial, reduzindo complexidade do lado do servidor.
3. Escala horizontal simples
Múltiplos servidores SSE podem servir clientes diferentes sem necessidade de comunicação entre servidores para broadcast.
typescript// Servidor SSE sem estado
class SSEServer {
private clientes: Map<http.ServerResponse, Set<string>> = new Map();
handleSSEConnection(req: http.IncomingMessage, res: http.ServerResponse) {
// Definir headers SSE
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'X-Accel-Buffering': 'no' // Desabilitar buffering do nginx
});
// Rastrear cliente e suas inscrições
this.clientes.set(res, new Set());
// Enviar mensagem de conexão inicial
this.enviarEvento(res, { tipo: 'connected', timestamp: Date.now() });
req.on('close', () => {
this.clientes.delete(res);
});
}
enviarEvento(res: http.ServerResponse, dados: any) {
const evento = `data: ${JSON.stringify(dados)}\n\n`;
res.write(evento);
}
broadcastParaTodos(mensagem: any) {
this.clientes.forEach((_, res) => {
this.enviarEvento(res, mensagem);
});
}
}Considerações de segurança
Segurança de WebSocket
1. Autenticação durante handshake
typescript// Middleware de autenticação WebSocket
const authenticateWebSocket = async (req: http.IncomingRequest): Promise<User | null> => {
const token = req.headers.authorization?.replace('Bearer ', '');
if (!token) {
return null;
}
try {
const decoded = await verifyJWT(token);
return await User.findById(decoded.userId);
} catch (error) {
return null;
}
};
// Servidor WebSocket com autenticação
wss.on('connection', async (ws: WebSocket, req: http.IncomingRequest) => {
const usuario = await authenticateWebSocket(req);
if (!usuario) {
ws.close(4008, 'Unauthorized');
return;
}
// Armazenar conexão de usuário
connections.set(usuario.id, ws);
ws.on('message', (message) => {
tratarMensagemUsuario(usuario.id, message);
});
});2. Rate limiting e prevenção de abuso
Conexões WebSocket podem ser abusadas para ataques DoS ou exaustão de recursos.
typescript// Rate limiting para mensagens WebSocket
class WebSocketRateLimiter {
private contagemMensagens: Map<string, number[]> = new Map();
private readonly JANELA_MS = 60000; // 1 minuto
private readonly MAX_MENSAGENS = 100;
verificarRateLimit(userId: string): boolean {
const agora = Date.now();
const mensagensUsuario = this.contagemMensagens.get(userId) || [];
// Remover mensagens fora da janela de tempo
const mensagensRecentes = mensagensUsuario.filter(timestamp => agora - timestamp < this.JANELA_MS);
if (mensagensRecentes.length >= this.MAX_MENSAGENS) {
return false;
}
mensagensRecentes.push(agora);
this.contagemMensagens.set(userId, mensagensRecentes);
return true;
}
}Segurança de SSE
1. Autenticação através de parâmetros de query ou cookies
typescript// Servidor SSE com autenticação
const handleSSEConnection = async (req: http.IncomingRequest, res: http.ServerResponse) => {
// Extrair token de cookie ou parâmetro de query
const token = req.headers.cookie?.match(/auth_token=([^;]+)/)?.[1]
|| new URL(req.url || '', 'http://localhost').searchParams.get('token');
if (!token) {
res.writeHead(401);
res.end();
return;
}
const usuario = await autenticarToken(token);
if (!usuario) {
res.writeHead(401);
res.end();
return;
}
// Estabelecer conexão SSE
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
});
// Enviar evento inicial
res.write(`data: ${JSON.stringify({ tipo: 'connected', userId: usuario.id })}\n\n`);
// Continuar enviando eventos...
};2. Autorização e filtragem baseada em escopo
Clientes devem receber apenas eventos que são autorizados a ver.
Considerações de performance e recursos
Uso de recursos de WebSocket
Overhead de conexão:
- Cada conexão WebSocket mantém um socket TCP aberto
- Memória do servidor: ~1-2KB por conexão (estado de conexão)
- CPU do servidor: mínima para conexões ociosas
- Rede: pacotes keep-alive (tipicamente a cada 30-60 segundos)
Exemplo de benchmark:
typescript// Teste de estresse de conexões WebSocket
async function benchmarkWebSocketConnections() {
const CONEXOES_SIMULTANEAS = 10000;
const clientes: WebSocket[] = [];
console.time('connect');
for (let i = 0; i < CONEXOES_SIMULTANEAS; i++) {
const ws = new WebSocket('ws://localhost:8080');
clientes.push(ws);
await new Promise(resolve => ws.onopen = resolve);
}
console.timeEnd('connect');
// Medir uso de memória
const usoMemoria = process.memoryUsage();
console.log('Uso de memória:', {
heapUsed: `${Math.round(usoMemoria.heapUsed / 1024 / 1024)} MB`,
heapTotal: `${Math.round(usoMemoria.heapTotal / 1024 / 1024)} MB`
});
}Uso de recursos de SSE
Overhead de conexão:
- Cada conexão SSE mantém uma conexão HTTP aberta
- Memória do servidor: ~500B-1KB por conexão (menos que WebSocket)
- CPU do servidor: mínima para conexões ociosas
- Rede: pacotes keep-alive (tipicamente a cada 30-60 segundos)
Vantagem de eficiência de memória: Conexões SSE são tipicamente mais leves em memória porque não mantêm tanto estado quanto conexões WebSocket.
Abordagens híbridas
Em muitos sistemas de produção, a solução ótima combina ambas as tecnologias baseadas no caso de uso:
typescript// Cliente tempo real híbrido
class HybridRealtimeClient {
private clienteSSE: LiveUpdatesClient;
private clienteWS: RealtimeChatClient;
private wsConectado = false;
constructor(private config: HybridConfig) {
// Inicializar SSE para atualizações servidor-para-cliente
this.clienteSSE = new LiveUpdatesClient(config.sseUrl);
// Inicializar WebSocket para comunicação bidirecional
this.clienteWS = new RealtimeChatClient(config.wsUrl);
this.clienteWS.onConnectionChange = (conectado: boolean) => {
this.wsConectado = conectado;
};
}
enviarMensagemChat(mensagem: string) {
// Usar WebSocket para enviar mensagens
if (this.wsConectado) {
this.clienteWS.enviarMensagem(mensagem);
} else {
console.error('WebSocket não conectado');
// Fallback: usar HTTP POST
this.postarMensagemViaHTTP(mensagem);
}
}
private async postarMensagemViaHTTP(mensagem: string) {
await fetch('/api/chat/mensagens', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ conteudo: mensagem })
});
}
inscreverAtualizacoesAoVivo() {
// Usar SSE para receber atualizações
this.clienteSSE.conectar();
}
}Framework de decisão
Use este framework para escolher a tecnologia certa para seu caso de uso:
Perguntas a fazer
- O cliente precisa enviar dados para o servidor em tempo real?
- Sim → WebSocket
- Não → Considere SSE
- Baixa latência é crítica para ambas as direções?
- Sim → WebSocket
- Não → SSE pode ser suficiente
- Quantas conexões simultâneas você espera?
- < 10.000 → Ambas opções viáveis
- 10.000-100.000 → SSE pode escalar mais fácil
- > 100.000 → Requer infraestrutura WebSocket especializada
- Qual é a expertise operacional da sua equipe?
- Experienciada com infraestrutura WebSocket → WebSocket
- Preferindo arquitetura sem estado mais simples → SSE
- Você precisa de transferência de dados binários?
- Sim → WebSocket
- Não → SSE suficiente
Checklist de implantação em produção
Para WebSocket:
- [ ] Implementar pool de conexões e gerenciamento de estado
- [ ] Configurar balanceadores de carga para sessões sticky
- [ ] Implementar comunicação cross-server para broadcast
- [ ] Adicionar rate limiting e prevenção de abuso
- [ ] Configurar monitoramento de saúde de conexão
- [ ] Implementar reconexão graceful do lado do cliente
- [ ] Adicionar tratamento de timeout de conexão
Para SSE:
- [ ] Implementar autenticação via cookies ou parâmetros de query
- [ ] Definir headers SSE apropriados (Content-Type, Cache-Control)
- [ ] Configurar proxies reversos para desabilitar buffering
- [ ] Adicionar autorização e filtragem baseada em escopo
- [ ] Implementar replay de eventos para clientes desconectados
- [ ] Configurar monitoramento de saúde de conexão
Conclusão
WebSocket e SSE são tecnologias poderosas para comunicação em tempo real, mas resolvem problemas diferentes. WebSocket se destaca em comunicação bidirecional, com baixa latência onde cliente e servidor precisam trocar dados frequentemente. SSE brilha em cenários unidirecionais servidor-para-cliente onde simplicidade e escala sem estado são prioridades.
A decisão não é apenas sobre capacidades técnicas—é sobre complexidade operacional, expertise da equipe e manutenibilidade a longo prazo. Escolha a tecnologia que corresponde ao seu caso de uso, requisitos de escala e capacidades operacionais.
Construindo uma aplicação em tempo real e precisa de orientação sobre decisões arquiteturais? Fale com a Imperialis sobre projetar e implementar a solução tempo real certa para seu sistema de produção.
Fontes
- RFC 6455: The WebSocket Protocol — especificação WebSocket
- HTML Living Standard: Server-Sent Events — especificação SSE
- MDN Web Docs: WebSocket API — documentação da API WebSocket
- MDN Web Docs: EventSource — documentação da API EventSource