Comparación entre WebSocket, eventos enviados por el servidor, long polling, WebRTC y WebTransport
(rxdb.info)- En las apps web en tiempo real, la elección entre Long Polling, WebSockets, SSE, WebRTC y WebTransport para entregar eventos servidor-cliente cambia mucho la latencia, la bidireccionalidad, la dificultad de implementación y las restricciones operativas
- WebSockets ofrece comunicación bidireccional con una sola conexión de larga duración, pero en operación real suele usarse junto con librerías como Socket.IO por la detección de pérdida de conexión, la reconexión y los heartbeats ping-pong
- Server-Sent Events es un stream unidireccional servidor→cliente basado en HTTP, por lo que la implementación y el manejo de reconexión son simples, pero la API EventSource básica tiene restricciones para enviar cuerpos POST o headers personalizados
- WebTransport soporta múltiples streams y transporte confiable/no confiable sobre HTTP/3 QUIC, pero a marzo de 2024 está en estado Working Draft y no cuenta con soporte nativo en Safari ni Node.js, así que todavía es difícil verlo como una opción general
- Debido al cierre en segundo plano en móviles, los límites de conexiones por dominio, los proxies y firewalls corporativos, y la pérdida de eventos durante la reconexión, las apps reales también necesitan lógica de recuperación de sincronización y pruebas de infraestructura
Evolución de las tecnologías de comunicación servidor-cliente en tiempo real
- En las aplicaciones web en tiempo real, la capacidad de que el servidor envíe eventos al cliente se volvió un requisito central
- Al principio, Long Polling, que funciona sobre HTTP, se usaba como mecanismo de mensajería servidor-cliente disponible en navegadores
- Luego WebSockets apareció como un mecanismo más sólido para la comunicación bidireccional
- Server-Sent Events(SSE) ofrece de forma más simple la comunicación unidireccional solo del servidor al cliente
- WebTransport puede llegar a ser un mecanismo más eficiente, flexible y escalable, pero actualmente su soporte es limitado
- WebRTC puede considerarse para algunos usos de nicho de eventos servidor-cliente, pero su propósito es distinto para tratarlo como una opción principal
Long Polling
- Long Polling es una forma de simular comunicación server push mediante solicitudes XHR comunes
- Cuando el cliente deja abierta una solicitud al servidor, el servidor retiene la respuesta hasta que haya datos nuevos
- Después de enviar la nueva información, la conexión se cierra y el cliente inicia de inmediato la siguiente solicitud
- Frente al polling periódico tradicional, permite actualizaciones más rápidas y puede reducir tráfico de red innecesario y carga del servidor
- Sin embargo, es menos eficiente que tecnologías en tiempo real como WebSockets y puede generar latencia según el momento de transmisión de los datos
- La implementación del cliente es simple, pero en el backend es difícil garantizar que los clientes en proceso de reconexión no pierdan eventos
WebSockets
- WebSockets crea una única conexión de larga duración entre cliente y servidor y ofrece comunicación full-duplex
- Una vez establecida la conexión, ambas partes pueden enviar datos de forma independiente sin el overhead del ciclo solicitud-respuesta de HTTP
- Es adecuado para apps que requieren baja latencia y actualizaciones frecuentes, como chats en tiempo real, juegos y plataformas de trading financiero
- La API básica de WebSocket es fácil de usar, pero en producción el manejo de pérdida y recreación de conexiones se vuelve complejo
- Como es difícil detectar si una conexión sigue siendo usable, normalmente se agregan heartbeats ping-and-pong
- Por esta complejidad, en muchos casos se usan librerías como Socket.IO, y Socket.IO también ofrece fallback a Long Polling si es necesario
Server-Sent Events
- Server-Sent Events(SSE) es un mecanismo estándar para hacer push de actualizaciones del servidor al cliente sobre HTTP
- A diferencia de WebSockets, está diseñado solo para comunicación unidireccional servidor→cliente
- Es adecuado para situaciones en las que el cliente no necesita enviar mensajes al servidor, como feeds de noticias en vivo, marcadores deportivos y actualizaciones en tiempo real
- SSE puede verse como un mecanismo donde, dentro de una sola solicitud HTTP, la conexión se mantiene abierta y el backend va enviando la respuesta línea por línea cada vez que aparece un evento
- En el cliente del navegador, se inicializa una instancia de EventSource para recibir el stream de eventos
- EventSource, a diferencia de WebSockets, se reconecta automáticamente si la conexión se corta
- El servidor debe configurar el header
Content-Typecomotext/event-streamy formatear campos como tipo de evento, payload de datos, ID de evento y retry timing según la SSE specification
WebTransport
- WebTransport es una API para comunicación eficiente y de baja latencia entre clientes web y servidores
- Usa el HTTP/3 QUIC protocol para enviar datos en múltiples streams
- Soporta conjuntamente transporte confiable y no confiable, y transmisión de datos sin orden
- Puede ser una herramienta potente para apps que necesitan networking de alto rendimiento, como juegos en tiempo real, live streaming y plataformas colaborativas
- A marzo de 2024, WebTransport está en estado Working Draft y no tiene soporte amplio
- Todavía no está disponible en Safari browser, y tampoco hay soporte nativo en Node.js
- Incluso si el soporte se amplía, la API es muy compleja, por lo que es probable que se creen librerías sobre WebTransport en lugar de usarlo directamente desde el código de aplicación
WebRTC
- WebRTC es un proyecto open source y un estándar de API que ofrece capacidades de comunicación en tiempo real dentro de navegadores y apps móviles sin plugins
- Soporta conexiones peer-to-peer para intercambio de audio, video y datos entre navegadores
- Usa protocolos como ICE, STUN y TURN para atravesar NAT y firewalls
- WebRTC fue creado para interacciones cliente-cliente, pero también puede usarse para comunicación servidor-cliente haciendo que el servidor actúe como un cliente
- Como este enfoque solo encaja en casos de uso de nicho, se excluye de la comparación de opciones principales
- Para que WebRTC funcione, de todos modos se necesita un servidor de señalización, y ese servidor funciona sobre WebSockets, SSE o WebTransport
- Por eso, el objetivo de usar WebRTC como reemplazo directo de esas tecnologías pierde fuerza
Principales restricciones por tecnología
-
Transmisión bidireccional de datos
- Solo WebSockets y WebTransport soportan recibir datos del servidor y enviar datos del cliente en la misma conexión
- Long Polling también es posible en teoría, pero no se recomienda porque enviar nuevos datos sobre una conexión long-polling existente requiere una solicitud HTTP adicional
- En Long Polling, es mejor enviar datos cliente→servidor mediante una solicitud HTTP separada sin interferir con la conexión existente
- SSE no soporta la función de enviar datos adicionales al servidor
- La EventSource API nativa básica no puede enviar datos como POST en el HTTP body ni siquiera en la solicitud inicial
- Los datos deben ponerse en parámetros de URL, lo cual no es bueno para la seguridad porque las credentials pueden filtrarse en logs del servidor, proxies y cachés
- Para evitar este problema, RxDB usa un eventsource polyfill en lugar de la
EventSource APInativa, y esta librería agrega funciones como headers HTTP personalizados - fetch-event-source de Microsoft permite enviar datos en el body y usar solicitudes
POSTen lugar deGET
-
Límite de conexiones por dominio
- La mayoría de los navegadores modernos permite 6 conexiones por dominio, y este límite restringe la usabilidad de los mecanismos confiables de mensajería servidor→cliente en general
- El límite de 6 conexiones también se comparte entre pestañas del navegador, por lo que si se abre la misma página en varias pestañas, estas deben compartir el mismo pool de conexiones
- La RFC de HTTP/1.1 recomienda una cifra aún más baja: 2 conexiones por servidor o proxy
- Esta política es razonable para prevenir DDoS usando visitantes, pero puede ser un problema cuando la comunicación servidor-cliente legítima necesita varias conexiones
- Para evitarlo, se debe usar HTTP/2 o HTTP/3, de modo que el navegador abra una sola conexión por dominio y procese los datos mediante multiplexing
- Incluso en HTTP/2·HTTP/3, la configuración SETTINGS_MAX_CONCURRENT_STREAMS limita la cantidad real de streams simultáneos, y el valor predeterminado en la mayoría de las configuraciones es 100 concurrent streams
- Los navegadores podrían aumentar el límite de conexiones para APIs específicas como EventSource, pero los issues relacionados de Chromium y Firefox están marcados como “won’t fix”
-
Reducir el número de conexiones en apps de navegador
- En una app de navegador, hay que asumir que el usuario puede abrir la app en varias pestañas al mismo tiempo
- Por defecto, cada pestaña puede abrir una conexión de stream al servidor, pero en la mayoría de los casos esto es innecesario
- Es posible abrir una sola conexión aunque haya varias pestañas y compartirla entre ellas
- RxDB usa LeaderElection del broadcast-channel npm package para mantener un solo replication stream entre servidor y cliente
- Este paquete también puede usarse de forma independiente en otras aplicaciones, sin RxDB
Restricciones operativas en móviles, proxies y firewalls
- En sistemas operativos móviles como Android e iOS, es difícil mantener abiertas conexiones como WebSockets de forma continua
- El sistema operativo móvil puede enviar la app a segundo plano y cerrar conexiones abiertas después de cierto tiempo de inactividad
- Este comportamiento forma parte de una estrategia de gestión de recursos para ahorrar batería y optimizar el rendimiento
- Los desarrolladores suelen usar notificaciones push móviles en lugar de conexiones persistentes cuando el servidor necesita enviar datos al cliente
- Las notificaciones push permiten que el servidor avise a la app de nuevos datos e impulse acciones o actualizaciones de la app sin una conexión abierta permanente
- En entornos corporativos, los proxies y firewalls pueden bloquear conexiones no HTTP, lo que dificulta incluir un servidor WebSocket en la infraestructura
- En estos entornos, SSE, al estar basado en HTTP, puede ser más fácil de integrar en empresas
- Long Polling también puede ser una opción porque usa solo solicitudes HTTP comunes
Comparación de rendimiento
- Al comparar WebSockets, SSE, Long Polling y WebTransport, hay que evaluar en conjunto latencia, throughput, carga del servidor y escalabilidad
- El realtime-web repo, que prueba tiempos de mensajes en una implementación de servidor en Go, muestra resultados en los que WebSockets, WebRTC y WebTransport tienen un rendimiento similar
- Como WebTransport es una tecnología nueva basada en HTTP/3, podrían aparecer más optimizaciones de rendimiento después de marzo de 2024
- WebTransport está optimizado para reducir el consumo de energía, pero esa métrica no fue probada
-
Latencia
- WebSockets ofrece la latencia más baja gracias a la comunicación full-duplex sobre una sola conexión persistente
- SSE también ofrece baja latencia en la comunicación servidor→cliente, pero si el cliente necesita enviar mensajes al servidor se requiere una solicitud HTTP adicional
- Long Polling tiene mayor latencia porque crea una nueva conexión HTTP por cada transmisión de datos
- En Long Polling, si el cliente está abriendo una nueva conexión justo cuando el servidor quiere enviar un evento, la latencia puede aumentar mucho
- Se espera que WebTransport ofrezca baja latencia similar a WebSockets, aprovechando el multiplexing más eficiente y el congestion control de HTTP/3
-
Throughput
- WebSockets puede lograr alto throughput gracias a la conexión persistente, pero los problemas de backpressure, cuando el cliente no puede procesar tan rápido como el servidor envía, pueden afectar el throughput
- SSE tiene menos overhead que WebSockets, por lo que podría lograr mayor throughput en broadcasts unidireccionales servidor→cliente
- Long Polling generalmente tiene menor throughput y consume más recursos del servidor por el overhead de abrir y cerrar conexiones con frecuencia
- Se espera que WebTransport soporte alto throughput tanto en streams unidireccionales como bidireccionales dentro de una sola conexión, y podría superar a WebSockets en escenarios que requieren múltiples streams
-
Escalabilidad y carga del servidor
- WebSockets puede aumentar mucho la carga del servidor al mantener muchas conexiones, lo que puede afectar la escalabilidad de apps con muchos usuarios
- SSE escala mejor principalmente en escenarios que necesitan actualizaciones servidor→cliente
- SSE usa solicitudes HTTP comunes sin procedimientos de WebSocket como protocol upgrade, por lo que el overhead de conexión es menor
- Long Polling es el menos escalable por el establecimiento frecuente de conexiones, lo que aumenta la carga del servidor, y solo es adecuado como mecanismo de fallback
- WebTransport fue diseñado con el objetivo de lograr alta escalabilidad con base en la eficiencia de manejo de conexiones y streams de HTTP/3, y podría reducir la carga del servidor frente a WebSockets y SSE
Recomendaciones por caso de uso
- SSE es la opción más directa de implementar; al usar los protocolos HTTP/S existentes, es más fácil evitar restricciones de firewalls corporativos y problemas técnicos que pueden aparecer con otros protocolos
- Puede integrarse fácilmente con Node.js y otros frameworks de servidor
- Es adecuado para apps que necesitan actualizaciones frecuentes servidor→cliente, como feeds de noticias, cotizaciones bursátiles y streaming de eventos en vivo
- WebSockets es fuerte en escenarios que requieren comunicación bidireccional persistente
- Se vuelve la opción principal para casos que necesitan interacción continua, como juegos en navegador, aplicaciones de chat y actualizaciones deportivas en vivo
- WebTransport tiene potencial, pero no cuenta con soporte amplio en frameworks de servidor y le falta compatibilidad con Node.js y Safari
- WebTransport depende de HTTP/3, y el soporte de HTTP/3 en muchos servidores web como nginx todavía está en estado experimental
- Aunque es una tecnología de futuro que soporta transmisión de datos confiable y no confiable, aún no es una opción viable para la mayoría de los casos de uso actuales
- Long Polling es en general un método anticuado por la ineficiencia y el alto overhead de establecer repetidamente nuevas conexiones HTTP
- Puede usarse como fallback en entornos que no soportan WebSockets o SSE, pero no se recomienda su uso general por sus limitaciones de rendimiento
Problema de pérdida de eventos durante la reconexión
- Al construir funciones sobre cualquier tecnología de streaming en tiempo real, hay que considerar cortes de conexión y situaciones de reconexión
- Si el cliente se está conectando, reconectando o está offline, puede no recibir por stream los eventos generados en el servidor
- Si el servidor transmite siempre el contenido completo, como cotizaciones bursátiles, los eventos perdidos podrían no importar
- Si el backend transmite solo resultados parciales, los eventos perdidos deben manejarse obligatoriamente
- Hacer que el backend recuerde qué eventos se enviaron correctamente a cada cliente no escala bien
- Es mejor manejar este problema con lógica del lado del cliente
- RxDB Sync Engine usa dos modos de operación
- checkpoint iteration mode: consulta repetidamente los datos del backend mediante solicitudes HTTP comunes hasta que el cliente se ponga al día al volver a sincronizarse
- event observation mode: mantiene al cliente sincronizado mediante actualizaciones del stream en tiempo real
- Si la conexión del cliente se corta o ocurre un error, replication cambia temporalmente a checkpoint iteration mode y sincroniza hasta volver al mismo estado que el servidor
- Este mecanismo compensa eventos perdidos y permite que el cliente siempre pueda sincronizarse exactamente al mismo estado que el servidor
Aspectos a verificar en infraestructura corporativa
- En infraestructura corporativa, pueden surgir problemas con las tecnologías de streaming en general
- Los proxies y firewalls pueden bloquear el tráfico o romper solicitudes y respuestas de forma involuntaria
- Al implementar apps en tiempo real en estos entornos, primero hay que probar si la tecnología elegida funciona en esa infraestructura
1 comentarios
Opiniones de Hacker News
Siempre le tuve cariño a Server-Sent Events. Es simple y fácil de usar/implementar
WebSocket se vuelve bastante complejo de escalar cuando el uso supera cierto nivel
https://crbug.com/275955
Me pregunto por qué no lo hicieron simplemente como una respuesta de streaming multipart. También soporta metadatos y es un formato implementado muy comúnmente
Hay más desventajas a tener en cuenta
WebSocket no tiene control de flujo (backpressure) ni multiplexación, así que si los necesitas tienes que crearlos tú mismo o usar algo como RSocket. SSE tampoco puede enviar datos binarios directamente, por lo que requiere una codificación como base64
WebTransport aborda estos problemas y también resuelve el bloqueo HOL, pero me preocupa que ocurra algo parecido a la transición de Python 2→3 o IPv6: que la gente siga usando la versión existente y perciba pocos beneficios al actualizar
Mientras los navegadores sigan funcionando sobre TCP, algunas redes podrían bloquear directamente UDP y, por lo tanto, HTTP/3/WebTransport
La preocupación de que la transición a WebTransport sea lenta es algo que también se podría haber dicho antes sobre el transporte TLS, HTTP/3 y XHR. Como la estructura está dominada por unos pocos motores de navegador importantes, desplegar nuevas funciones y protocolos en navegadores es relativamente fácil
Si la lógica es que algunas redes bloquean UDP porque TCP funciona, es parecido a decir que, como HTTP 1.1 sin TLS funciona, también seguirán bloqueando HTTP/2 y TLS. No es completamente incorrecto, pero viendo la adopción amplia de HTTP/2 y especialmente de TLS, no parece un problema tan grande como podría pensarse
En una oficina pequeña o en un entorno corporativo distópico sacado de una película quizá lo cierren, pero no entiendo por qué es tan relevante el hecho de que algunas redes puedan prohibir UDP. Algunas redes también bloquean google.com o wikipedia.com, pero eso no hace que esos servicios fracasen
La explicación del artículo sobre WebRTC no es correcta. WebRTC cliente/servidor es posible incluso sin un “servidor de señalización” separado; el servidor puede encargarse de la señalización
Solo hacen falta unas cuantas idas y vueltas más, no un servidor separado. Los canales de datos de WebRTC funcionan bastante bien como sustituto de WebSocket o SSE, especialmente cuando se quiere evitar el bloqueo HOL. También hay muchas bibliotecas como Pion o str0m que hacen casi todo el trabajo
Decir que la API de WebTransport es compleja también me parece una exageración. Si no necesitas funciones avanzadas, puedes ignorarlas; si quieres usarla como WebSocket, basta con abrir un único stream bidireccional y prácticamente ya está. Para evitar el bloqueo HOL, abre un stream por mensaje. Es un poco más complejo, pero no al punto de requerir necesariamente una biblioteca, y es muy probable que GitHub Copilot también pueda escribir el código. Eso sí, WebTransport todavía está madurando, por lo que no hay muchas bibliotecas de servidor y aún se espera el soporte de Safari
Normalmente, el servidor de señalización se implementa con WebSocket. A menos que estés proponiendo un bootstrap descentralizado de los clientes existentes, no se puede implementar con WebRTC en sí
Si estás construyendo para clientes con infraestructura de TI tradicional de “empresa” y “seguridad”, es mejor agregar un botón de actualizar y listo
En mi experiencia en esos entornos, intentar crear funciones en tiempo real para ese tipo de clientes era precisamente lo que fallaba constantemente y no se podía arreglar por procedimientos interminables
WebSocket y SSE se vuelven un gran dolor de cabeza de administrar cuando escalan. En especial en el backend se necesita observabilidad aparte, y si no se implementan con muchísimo cuidado en dispositivos móviles, depurar el frontend se vuelve una pesadilla
Los dispositivos apagan o ralentizan la red para ahorrar batería, y más aún si no se hace I/O explícitamente mediante APIs dedicadas
Crear una conexión nueva es una operación costosa, y el servidor tiene que guardar el estado en alguna parte. Si esa capa de almacenamiento de estado tiene problemas, los clientes siguen reintentando y agotando tiempos de espera, quedándose atrapados para siempre en una operación costosa. Tampoco es una forma de controlar fácilmente el throughput mientras se carga la base de datos de a poco
En términos de confiabilidad, por experiencia long polling fue lo que mejor funcionó. Incluso si el flujo basado en eventos es realmente importante, conviene más una arquitectura de 2 capas donde el frontend hace long polling al backend de capa 1, y esa capa 1 se suscribe por WebSocket al backend de capa 2; el control de confiabilidad mejora mucho
SSE admite reconexión automática e incluye el último ID visto, para que el servidor pueda continuar sin interrupciones
Aunque no aparece en el artículo, short polling también es relevante. No es una forma de enviar mensajes del servidor al cliente, pero sigue siendo útil cuando no hay otras opciones, como en hosting compartido
En mi experiencia, aunque el intervalo de polling sea largo, por ejemplo 20 segundos, funciona bastante bien si en cada respuesta incluyes también la lista de mensajes. Cuando el usuario presiona un botón, el cliente envía una solicitud al servidor, y el servidor responde con los datos junto con la lista de mensajes más reciente, dejando al cliente actualizado
Todavía no entiendo por qué WebSocket y SSE no soportan enviar headers como Authorization en la solicitud inicial. Terminan dejando toda la autenticación de servicios en tiempo real en manos de quien implementa
Puede que haya una buena forma en la especificación, pero he visto enfoques tan variados que, a estas alturas, casi se puede decir que en la práctica no existe
Además de manejar headers personalizados, soporta todos los métodos de solicitud (POST, PATCH, etc.), incluir body en la solicitud, suscribirse a eventos con nombre y configurar el last event ID inicial. También puede usarse como iterador asíncrono
Me gusta la simplicidad de Server-Sent Events, pero la API
EventSourceparece algo implementado a las apuradas y que luego simplemente quedó ahí[1]: https://github.com/eventsource/eventsource
Puede que sea una idea ingenua, pero asumiendo HTTP/2 o superior, la combinación de EventSource y
fetch()para enviar mensajes parece tan buena como otros protocolos que usan una sola conexión TCP. Y HTTP/3 usa UDP, así que mejor aúnLa premisa es que solo hace falta mantener la conexión cuando la pestaña está en primer plano. Me da curiosidad qué problemas aparecieron cuando alguien probó este enfoque en la práctica
https://www.npmjs.com/package/@microsoft/fetch-event-source
Me preguntaba si, en lugar de hacer algo completamente distinto, se podría llevar SSE más lejos reduciendo aún más la latencia, el uso de memoria y los recursos de CPU
Me divierte un poco leer artículos así. A fines de los 90 diseñé un sistema de subastas en línea, y no había ninguna solicitud XHR
Todas las actualizaciones en tiempo real se manejaban con server-push/streaming HTTP. En esa época no era fácil manejar todas las conexiones abiertas, pero con la arquitectura adecuada se podía llegar a una escala aceptable
Las ventajas de HTTP/2 o HTTP/3 son excelentes, pero también conviene saber qué se puede aprovechar en HTTP 1.1, que en la práctica está soportado en todas partes
Extraño un poco el long polling. Comparado con las tecnologías modernas, era realmente simple. Lo siento incluso pensando que WebRTC es lo mejor
En cambio, vuelve a esperar datos y envía respuestas adicionales por el mismo stream
La red de Second Life usa HTTPS con long polling para los “canales de eventos”, y el servidor envía mensajes de eventos al cliente por ese canal. La mayoría de los mensajes van por UDP, pero los que necesitan cifrado o son grandes van por el canal de eventos HTTPS/TCP
Del lado del cliente, el cliente en C++ usa
libcurl, pero la configuración predeterminada de timeout no encaja con el long polling.libcurlcorta la conexión y crea una nueva solicitud, lo que puede provocar pérdida o duplicación de mensajesDel lado del servidor, Apache está delante del servidor de simulación real y filtra intentos de conexión irrelevantes, pero Apache también tiene sus propios timeouts, así que interrumpe la conexión y hace que el cliente reintente
Se intenta evitar pérdidas con números de secuencia en los mensajes, pero el servidor de Second Life ignora los números de secuencia que el cliente devuelve como confirmación. Algunos servidores compatibles de Open Simulator incluso se saltan números secuenciales
El resultado es un sistema basado en HTTPS que puede perder o duplicar mensajes que deberían ser confiables. Si se pierden algunos mensajes, la actividad del usuario dentro del juego se detiene
Quienes diseñaron esto se fueron hace mucho, y el personal actual no sabía qué tan grave era este caos. Usuarios externos tuvieron que encontrar y documentar el problema, y los empleados de la empresa llevan meses intentando arreglarlo. Es lo suficientemente difícil de solucionar como para que, por ahora, parezca que están postergando el trabajo
Así que el long polling no es “tan simple que es tonto”. La forma correcta probablemente sea enviar mensajes keep-alive con la frecuencia suficiente para que las capas TCP y HTTPS no expiren por timeout. Así Apache y
libcurlse mantienen en una ruta donde funcionan bien