- Em apps web em tempo real, a escolha entre Long Polling, WebSockets, SSE, WebRTC e WebTransport para entregar eventos do servidor ao cliente muda bastante a latência, a bidirecionalidade, a dificuldade de implementação e as restrições operacionais
- WebSockets oferecem comunicação bidirecional por uma única conexão de longa duração, mas em produção é comum usar bibliotecas como Socket.IO por causa de detecção de perda de conexão, reconexão e heartbeats de ping-pong
- Server-Sent Events são um fluxo unidirecional de servidor→cliente baseado em HTTP, então a implementação e o tratamento de reconexão são mais simples, mas a API padrão
EventSourcetem limitações para enviar corpo POST ou cabeçalhos customizados - WebTransport suporta múltiplos streams e transporte confiável e não confiável com base em HTTP/3 QUIC, mas em março de 2024 ainda estava em Working Draft e sem suporte nativo no Safari e no Node.js, então ainda é difícil considerá-lo uma opção universal
- Encerramento em segundo plano no mobile, limite de conexões por domínio, proxies e firewalls corporativos, e perda de eventos durante reconexões significam que apps reais também precisam de lógica de recuperação de sincronização e testes de infraestrutura
Evolução das tecnologias de comunicação em tempo real entre servidor e cliente
- Em aplicações web em tempo real, a capacidade de o servidor enviar eventos ao cliente virou um requisito central
- No início, o Long Polling, funcionando sobre HTTP, era usado como forma de mensagens entre servidor e cliente possível no navegador
- Depois, os WebSockets surgiram como uma forma mais robusta de comunicação bidirecional
- Server-Sent Events (SSE) oferecem de forma mais simples uma comunicação unidirecional apenas do servidor para o cliente
- WebTransport tem potencial para se tornar uma abordagem mais eficiente, flexível e escalável, mas hoje ainda tem suporte limitado
- WebRTC pode ser considerado para alguns casos de uso mais específicos de eventos entre servidor e cliente, mas tem um propósito diferente para ser tratado como opção principal
Long Polling
- Long Polling é uma forma de simular comunicação push do servidor com requisições XHR comuns
- Quando o cliente mantém uma requisição aberta para o servidor, o servidor segura a resposta até que haja novos dados
- Depois de enviar a nova informação, a conexão é fechada, e o cliente imediatamente inicia a próxima requisição
- Em comparação com o polling periódico tradicional, as atualizações chegam mais rápido e é possível reduzir tráfego de rede desnecessário e carga no servidor
- Ainda assim, é menos eficiente do que tecnologias em tempo real como WebSockets, e pode haver latência dependendo do momento em que os dados são enviados
- A implementação no cliente é simples, mas no backend é difícil garantir que clientes em reconexão não percam eventos
WebSockets
- WebSockets criam uma única conexão de longa duração entre cliente e servidor e oferecem comunicação full-duplex
- Depois que a conexão é estabelecida, ambos os lados podem enviar dados de forma independente sem o overhead do ciclo HTTP de requisição-resposta
- São adequados para apps como chats em tempo real, jogos e plataformas financeiras, que exigem baixa latência e atualizações frequentes
- A API básica de WebSocket é fácil de usar, mas em produção o tratamento de perda e recriação de conexão fica complexo
- Como é difícil detectar se a conexão ainda está realmente utilizável, normalmente se adicionam heartbeats de ping-and-pong
- Por essa complexidade, em muitos casos usa-se uma biblioteca como Socket.IO, que também oferece fallback para Long Polling quando necessário
Server-Sent Events
- Server-Sent Events (SSE) são uma forma padrão de enviar atualizações do servidor para o cliente sobre HTTP
- Diferentemente de WebSockets, foram projetados apenas para comunicação unidirecional servidor→cliente
- São adequados para situações como feeds de notícias ao vivo, placares esportivos e atualizações em tempo real, em que o cliente não precisa enviar mensagens ao servidor
- O SSE pode ser visto como uma única requisição HTTP que permanece aberta enquanto o backend vai transmitindo a resposta linha por linha à medida que eventos acontecem
- No cliente do navegador, recebe-se o fluxo de eventos ao inicializar uma instância de EventSource
- O EventSource, ao contrário de WebSockets, reconecta automaticamente quando a conexão cai
- O servidor deve definir o cabeçalho
Content-Typecomotext/event-streame formatar campos como tipo do evento, payload de dados, ID do evento e tempo de retry de acordo com a especificação SSE
WebTransport
- WebTransport é uma API para comunicação eficiente e de baixa latência entre clientes web e servidores
- Ela usa o protocolo HTTP/3 QUIC para enviar dados por vários streams
- Suporta tanto transporte confiável quanto não confiável, além de transmissão de dados sem ordenação
- Pode ser uma ferramenta poderosa para apps que precisam de rede de alto desempenho, como jogos em tempo real, streaming ao vivo e plataformas colaborativas
- Em março de 2024, o WebTransport estava em status de Working Draft e ainda não tinha suporte amplo
- Ainda não pode ser usado no Safari e também não tem suporte nativo no Node.js
- Mesmo quando o suporte se ampliar, a API é bastante complexa, então é provável que o uso aconteça mais por meio de bibliotecas construídas sobre WebTransport do que diretamente no código da aplicação
WebRTC
- WebRTC é um projeto open source e um padrão de API que fornece recursos de comunicação em tempo real em navegadores e apps móveis sem plugins
- Suporta conexões peer-to-peer para troca de áudio, vídeo e dados entre navegadores
- Usa protocolos como ICE, STUN e TURN para atravessar NAT e firewalls
- O WebRTC foi criado para interação cliente-cliente, mas também é possível usá-lo em comunicação servidor-cliente fazendo o servidor agir como se fosse um cliente
- Como essa abordagem só serve para casos de uso de nicho, ela fica fora da comparação principal entre opções
- Para o WebRTC funcionar, de qualquer forma é necessário um servidor de sinalização, que acabará rodando sobre WebSockets, SSE ou WebTransport
- Por isso, o argumento de usar WebRTC como substituto direto dessas tecnologias fica mais fraco
Principais restrições por tecnologia
-
Transmissão de dados bidirecional
- Apenas WebSockets e WebTransport suportam receber dados do servidor e enviar dados do cliente na mesma conexão
- Long Polling até permite isso em teoria, mas não é recomendado porque enviar novos dados durante uma conexão long-polling existente exige outra requisição HTTP
- No Long Polling, é melhor enviar dados cliente→servidor por uma requisição HTTP separada sem interferir na conexão já existente
- SSE não suporta envio de dados adicionais para o servidor
- A API nativa EventSource também não consegue enviar dados como POST no corpo HTTP nem mesmo na requisição inicial
- Os dados acabam tendo de ir em parâmetros de URL, o que não é bom do ponto de vista de segurança, já que credentials podem vazar em logs do servidor, proxies e caches
- O RxDB evita esse problema usando um polyfill de eventsource em vez da
EventSource APInativa, e essa biblioteca adiciona recursos como cabeçalhos HTTP customizados - O fetch-event-source da Microsoft permite enviar dados no body e usar requisições
POSTem vez deGET
-
Limite de conexões por domínio
- A maioria dos navegadores modernos permite 6 conexões por domínio, e esse limite restringe a usabilidade geral de abordagens estáveis de mensagens servidor→cliente
- O limite de 6 conexões também é compartilhado entre abas do navegador, então se a mesma página estiver aberta em várias abas, elas precisarão dividir o mesmo pool de conexões
- A RFC do HTTP/1.1 recomenda um número ainda menor: 2 conexões por servidor ou proxy
- Essa política faz sentido para evitar DDoS usando visitantes, mas pode causar problemas quando uma comunicação legítima entre servidor e cliente precisa de várias conexões
- Para contornar isso, é preciso usar HTTP/2 ou HTTP/3 para que o navegador abra apenas uma conexão por domínio e lide com os dados por multiplexação
- Mesmo em HTTP/2 e HTTP/3, a configuração SETTINGS_MAX_CONCURRENT_STREAMS limita a quantidade real de streams simultâneos, e o padrão da maioria das configurações é 100 concurrent streams
- O navegador até pode aumentar o limite para APIs específicas como EventSource, mas os issues relacionados no Chromium e no Firefox estão marcados como “won’t fix”
-
Reduzindo o número de conexões em apps de navegador
- Em apps de navegador, é preciso assumir que o usuário pode abrir o app em várias abas ao mesmo tempo
- Por padrão, cada aba pode abrir uma conexão própria de stream com o servidor, mas na maioria dos casos isso é desnecessário
- É possível manter apenas uma conexão mesmo com várias abas abertas e compartilhá-la entre elas
- O RxDB usa o pacote npm broadcast-channel com LeaderElection para manter apenas um replication stream entre servidor e cliente
- Esse pacote também pode ser usado isoladamente em outras aplicações, sem depender do RxDB
Restrições operacionais em mobile, proxies e firewalls
- Em sistemas operacionais móveis como Android e iOS, é difícil manter conexões abertas continuamente, incluindo WebSockets
- O sistema operacional móvel pode colocar o app em segundo plano após um tempo de inatividade e encerrar conexões abertas
- Esse comportamento faz parte da estratégia de gerenciamento de recursos para economizar bateria e otimizar desempenho
- Por isso, desenvolvedores costumam usar notificações push móveis em vez de conexões persistentes quando o servidor precisa enviar dados ao cliente
- As notificações push permitem que o servidor informe ao app sobre novos dados sem manter uma conexão aberta o tempo todo e podem induzir ações ou atualizações no app
- Em ambientes corporativos, proxies e firewalls podem bloquear conexões que não sejam HTTP, dificultando colocar servidores WebSocket na infraestrutura
- Nesses cenários, o SSE, por ser baseado em HTTP, pode ser uma forma mais fácil de integração corporativa
- Long Polling também pode ser uma opção, já que usa apenas requisições HTTP comuns
Comparação de desempenho
- Ao comparar WebSockets, SSE, Long Polling e WebTransport, é preciso considerar junto latência, throughput, carga no servidor e escalabilidade
- O repositório realtime-web, que testou tempos de mensagem em uma implementação de servidor em Go, mostra resultados em que WebSockets, WebRTC e WebTransport têm desempenho semelhante
- Como o WebTransport é uma tecnologia nova baseada em HTTP/3, mais otimizações de desempenho podem surgir após março de 2024
- O WebTransport foi otimizado para reduzir consumo de energia, mas essa métrica não foi testada
-
Latência
- WebSockets oferecem a menor latência por causa da comunicação full-duplex sobre uma única conexão persistente
- SSE também oferece baixa latência na comunicação servidor→cliente, mas o cliente precisa de uma requisição HTTP adicional para enviar mensagens ao servidor
- Long Polling tem latência maior porque cria uma nova conexão HTTP a cada envio de dados
- No Long Polling, a latência pode aumentar bastante se, no momento em que o servidor tenta enviar um evento, o cliente estiver abrindo uma nova conexão
- WebTransport deve oferecer baixa latência semelhante à de WebSockets, aproveitando a multiplexação mais eficiente e o congestion control do HTTP/3
-
Throughput
- WebSockets podem atingir alto throughput graças à conexão persistente, mas problemas de backpressure, quando o cliente não consegue processar os dados na mesma velocidade em que o servidor envia, podem afetar o desempenho
- SSE tem menos overhead que WebSockets e pode oferecer throughput potencialmente maior em broadcast unidirecional de servidor→cliente
- Long Polling em geral tem throughput baixo e consome mais recursos do servidor por causa do overhead de abrir e fechar conexões com frequência
- WebTransport deve suportar alto throughput tanto para streams unidirecionais quanto bidirecionais dentro de uma única conexão e pode superar WebSockets em cenários que exigem vários streams
-
Escalabilidade e carga no servidor
- WebSockets podem aumentar bastante a carga no servidor à medida que o número de conexões mantidas cresce, afetando a escalabilidade em apps com muitos usuários
- SSE tende a escalar melhor em cenários em que o principal requisito é enviar atualizações do servidor para o cliente
- O SSE usa requisições HTTP comuns, sem procedimentos de WebSocket como protocol upgrade, então o overhead de conexão é menor
- Long Polling é o menos escalável por causa da criação frequente de conexões e faz mais sentido apenas como mecanismo de fallback
- WebTransport foi projetado com foco em alta escalabilidade com base na eficiência de conexões e streams do HTTP/3, e pode reduzir carga no servidor em relação a WebSockets e SSE
Recomendações por caso de uso
- SSE é a opção mais direta de implementar e, por usar o protocolo HTTP/S existente, facilita evitar limitações de firewalls corporativos e outros problemas técnicos que aparecem com protocolos diferentes
- Também é fácil de integrar ao Node.js e a outros frameworks de servidor
- É adequado para apps como feeds de notícias, cotações de ações e streaming de eventos ao vivo, em que atualizações servidor→cliente acontecem com frequência
- WebSockets são fortes em cenários que exigem comunicação bidirecional contínua
- Em casos como jogos no navegador, aplicativos de chat e atualizações esportivas ao vivo com interação constante, costumam ser a principal escolha
- WebTransport tem potencial, mas ainda não tem suporte amplo em frameworks de servidor e carece de compatibilidade com Node.js e Safari
- O WebTransport depende de HTTP/3, e o suporte a HTTP/3 em muitos servidores web, como o nginx, ainda está em estado experimental
- É uma tecnologia voltada para o futuro, com suporte a transporte confiável e não confiável, mas ainda não é uma escolha viável para a maioria dos casos de uso atuais
- Long Polling é, em geral, uma abordagem ultrapassada por causa da ineficiência e do alto overhead de abrir repetidamente novas conexões HTTP
- Pode ser usado como fallback em ambientes que não suportam WebSockets ou SSE, mas não é recomendado para uso geral por suas limitações de desempenho
Problema de perda de eventos durante reconexão
- Ao construir funcionalidades sobre qualquer tecnologia de streaming em tempo real, é preciso considerar interrupções de conexão e reconexões
- Se o cliente estiver conectando, reconectando ou offline, ele pode não receber no stream eventos que ocorram no servidor
- Se o servidor transmite sempre o conteúdo completo, como cotações de ações, eventos perdidos podem não ser importantes
- Se o backend transmite apenas resultados parciais, os eventos perdidos precisam ser tratados obrigatoriamente
- Não escala bem fazer o backend lembrar quais eventos foram enviados com sucesso para cada cliente
- É melhor tratar esse problema com lógica no lado do cliente
- O RxDB Sync Engine usa dois modos de operação
- checkpoint iteration mode: consulta repetidamente os dados do backend com requisições HTTP normais até o cliente voltar a ficar sincronizado
- event observation mode: mantém o cliente sincronizado com atualizações do stream em tempo real
- Quando a conexão do cliente cai ou ocorre um erro, a replicação muda temporariamente para checkpoint iteration mode até alcançar novamente o mesmo estado do servidor
- Essa abordagem compensa eventos perdidos e permite que o cliente permaneça sempre sincronizado com o servidor no mesmo estado exato
O que verificar em infraestrutura corporativa
- Em infraestrutura corporativa, podem ocorrer problemas com tecnologias de streaming em geral
- Proxies e firewalls podem bloquear tráfego ou quebrar requisições e respostas de forma não intencional
- Ao implementar um app em tempo real nesses ambientes, o primeiro passo é testar se a tecnologia escolhida realmente funciona nessa infraestrutura
1 comentários
Opiniões no Hacker News
Sempre tive um carinho por Server-Sent Events. É simples e fácil de usar/implementar
WebSocket fica bem complicado de escalar quando o uso passa de certo nível
https://crbug.com/275955
Fico me perguntando por que não fizeram simplesmente como uma resposta de streaming multipart. Ela também suporta metadados e é um formato implementado de forma muito comum
Há algumas desvantagens adicionais a considerar
WebSocket não tem controle de fluxo (backpressure) nem multiplexação, então, se você precisar disso, tem que criar por conta própria ou usar algo como RSocket. SSE também não consegue enviar dados binários diretamente, exigindo uma codificação como base64
WebTransport trata desses problemas e também resolve o bloqueio HOL, mas me preocupa que surja um problema parecido com a transição do Python 2→3 ou do IPv6, em que as pessoas tendem a continuar usando a versão existente e os benefícios do upgrade parecem pequenos
Enquanto os navegadores continuarem funcionando via TCP, algumas redes podem simplesmente bloquear UDP e, portanto, HTTP/3/WebTransport
A preocupação de que a transição para WebTransport possa ser lenta é a mesma que se poderia ter dito antes sobre transporte TLS, HTTP/3 e XHR. Como a estrutura é dominada por alguns poucos motores de navegador importantes, a distribuição de novos recursos e protocolos de navegador é relativamente fácil
Pela lógica de que algumas redes bloqueiam UDP porque TCP está disponível, seria parecido dizer que, como HTTP 1.1 sem TLS está disponível, HTTP/2 e TLS continuariam sendo bloqueados. Não é completamente falso, mas, olhando para a ampla adoção de HTTP/2 e especialmente de TLS, não parece ser um problema tão grande quanto se imagina
Talvez em um escritório pequeno ou em um ambiente corporativo distópico digno de filme alguém possa fechar isso, mas não entendo por que o fato de algumas redes poderem proibir UDP seria tão relevante. Algumas redes também bloqueiam google.com ou wikipedia.com, mas isso não faz esses serviços fracassarem
A explicação do texto sobre WebRTC não está correta. WebRTC cliente/servidor é possível mesmo sem um “servidor de sinalização” separado; o próprio servidor pode fazer a sinalização
São necessárias apenas algumas viagens de ida e volta a mais, não um servidor separado. Canais de dados WebRTC funcionam muito bem como substitutos de WebSocket ou SSE, especialmente quando se quer evitar bloqueio HOL. Também há muitas bibliotecas, como Pion ou str0m, que fazem quase todo o trabalho
A afirmação de que a API WebTransport é complexa também parece exagerada. Se você não precisa dos recursos avançados, pode ignorá-los; se quiser usá-la como WebSocket, basta abrir um único stream bidirecional e praticamente acabou. Para evitar bloqueio HOL, abra um stream por mensagem. É um pouco mais complexo, mas não a ponto de exigir obrigatoriamente uma biblioteca, e o GitHub Copilot provavelmente consegue até escrever o código. Ainda assim, o WebTransport ainda está amadurecendo, então não há muitas bibliotecas de servidor, e o suporte no Safari ainda está sendo aguardado
Normalmente, o servidor de sinalização é implementado com WebSocket. A menos que você esteja propondo um bootstrap descentralizado dos clientes existentes, não dá para implementá-lo com o próprio WebRTC
Se você está criando para clientes com infraestrutura de TI tradicional de “enterprise” e “segurança”, é melhor adicionar um botão de atualizar e encerrar o assunto
Na minha experiência nesses ambientes, o que sempre falhou — e não dava para consertar por causa de procedimentos intermináveis — foi justamente a tentativa de criar recursos em tempo real para esse tipo de cliente
WebSocket e SSE viram uma grande dor de cabeça para gerenciar conforme a escala cresce. Especialmente no backend, é preciso ter observabilidade separada, e, se não forem implementados com muito cuidado em dispositivos móveis, a depuração no frontend vira um pesadelo
Os dispositivos desligam ou desaceleram a rede para economizar bateria, ainda mais quando você não faz I/O explicitamente por meio de uma API dedicada
Criar uma nova conexão é uma operação cara, e o servidor precisa armazenar estado em algum lugar. Se essa camada de armazenamento de estado tiver problemas, o cliente fica tentando de novo, atinge timeouts e fica preso para sempre a uma operação cara. Também não é uma forma de controlar facilmente a vazão enquanto impõe carga lentamente ao banco de dados
Em termos de confiabilidade, pela minha experiência, long polling foi o melhor. Mesmo que um fluxo orientado a eventos seja realmente importante, é melhor ter uma arquitetura em duas camadas, em que o frontend faz long polling para um backend de camada 1, e essa camada 1 assina via WebSocket um backend de camada 2; o controle de confiabilidade fica muito melhor
SSE oferece reconexão automática e também inclui o último ID visto, permitindo que o servidor continue sem interrupções
Não está no artigo, mas short polling também é relevante. Não é uma forma de enviar mensagens do servidor para o cliente, mas ainda é útil quando não há outra opção, como em hospedagem compartilhada
Pela minha experiência, mesmo com um intervalo de polling longo — por exemplo, 20 segundos — funciona bastante bem se cada resposta vier junto com uma lista de mensagens. Quando o usuário aperta um botão, o cliente envia uma requisição ao servidor, e o servidor responde com os dados e a lista de mensagens mais recente, deixando o cliente atualizado
Até hoje não entendo por que WebSocket e SSE ainda não oferecem suporte ao envio de headers como Authorization na requisição inicial. Eles acabam deixando toda a autenticação de serviços em tempo real nas mãos de quem implementa
Talvez exista uma boa forma na especificação, mas já vi tantas abordagens diferentes que, a esta altura, dá para dizer que na prática não existe
Além de lidar com headers customizados, ele também oferece suporte a todos os métodos de requisição (POST, PATCH etc.), inclusão de corpo na requisição, assinatura de eventos nomeados e definição do last event ID inicial. Também pode ser usado como iterador assíncrono
Gosto da simplicidade de Server-Sent Events, mas a API
EventSourceparece ter sido implementada às pressas e simplesmente deixada como estava[1]: https://github.com/eventsource/eventsource
Pode ser uma ideia ingênua, mas, assumindo HTTP/2 ou superior, a combinação de EventSource com
fetch()para envio de mensagens parece tão boa quanto outros protocolos que usam uma única conexão TCP. Com HTTP/3, que usa UDP, é ainda melhorIsso partindo do pressuposto de que só é necessário manter a conexão enquanto a aba está em primeiro plano. Fico curioso para saber quais problemas apareceram quando tentaram essa abordagem na prática
https://www.npmjs.com/package/@microsoft/fetch-event-source
Fiquei curioso para saber se seria possível levar SSE mais longe, reduzindo ainda mais latência, uso de memória e recursos de CPU, em vez de fazer algo completamente diferente
Acho meio curioso ver textos assim. No fim dos anos 90, projetei um sistema de leilões online, e não havia absolutamente nenhuma requisição XHR
Todas as atualizações em tempo real eram tratadas com server-push/streaming HTTP. Na época, não era fácil lidar com todas as conexões abertas, mas, com a arquitetura adequada, dava para chegar a uma escala aceitável
As vantagens de HTTP/2 ou HTTP/3 são ótimas, mas também é importante saber o que dá para aproveitar em HTTP 1.1, que é suportado praticamente em todos os lugares
Sinto um pouco de saudade do long polling. Comparado às tecnologias mais recentes, era realmente simples. Sinto isso mesmo achando que o WebRTC é o melhor
Em vez disso, ele volta a esperar por dados e envia respostas adicionais pelo mesmo stream
A rede do Second Life usa HTTPS com long polling para “canais de eventos”, e o servidor envia mensagens de evento ao cliente por esse canal. A maioria das mensagens vai por UDP, mas mensagens que precisam de criptografia ou são grandes vão pelo canal de eventos HTTPS/TCP
No lado do cliente, o cliente em C++ usa
libcurl, cujas configurações padrão de timeout não combinam com long polling. Olibcurlderruba a conexão e cria uma nova requisição, e isso pode resultar em perda ou duplicação de mensagensNo lado do servidor, o Apache fica na frente do servidor de simulação real para filtrar tentativas de conexão irrelevantes, mas o Apache também tem seus próprios timeouts, interrompendo conexões e fazendo o cliente tentar novamente
Eles tentam evitar perdas com números de sequência das mensagens, mas o servidor do Second Life ignora os números de sequência que o cliente devolve como confirmação. Alguns servidores compatíveis do Open Simulator também pulam números sequenciais
O resultado é um sistema baseado em HTTPS que pode perder ou duplicar mensagens que deveriam ser confiáveis. Se algumas mensagens forem perdidas, a atividade do usuário dentro do jogo simplesmente para
As pessoas que projetaram isso foram embora há muito tempo, e os funcionários atuais não sabiam o quanto essa confusão era grave. Usuários externos tiveram que encontrar e documentar o problema, e funcionários da empresa estão tentando corrigi-lo há meses. Parece difícil o bastante para que, no momento, estejam adiando o trabalho
Portanto, long polling não é “simples a ponto de ser idiota”. A abordagem correta provavelmente é enviar mensagens de keep-alive com frequência suficiente para que as camadas TCP e HTTPS não deem timeout. Assim, Apache e
libcurlpermanecem em um caminho que funciona bem