Long Polling vs WebSocket en Java: duelo de tecnologías en tiempo real
\n\nImagina: estás desarrollando un chat, un terminal bursátil o un juego en línea en Java. El usuario espera actualizaciones, pero el servidor guarda silencio. ¿Cómo hacer que los datos lleguen al instante? Antes solo había una opción: Long Polling. Hoy en el ring está WebSocket. Pero, ¿quién gana en una pelea real?
\n\nEn este artículo no solo compararemos conceptos. Escribiremos código vivo en Java, veremos el interior de ambas tecnologías y decidiremos qué usar en tu próximo proyecto. ¡Bienvenidos al ring!
\n\n¿Qué es Long Polling (y por qué no es una «consulta»)?
\n\nEl HTTP clásico es como una carta: envías una solicitud → recibes una respuesta. Si el servidor no tiene nada que decir, devuelve una respuesta vacía. El cliente se ve obligado a preguntar de nuevo cada N segundos. Esto es Short Polling: un camino sin salida.
\n\nLong Polling funciona de manera más astuta. El cliente envía una solicitud y el servidor… se duerme. Mantiene la conexión abierta hasta que aparece un nuevo evento (o hasta que expire el tiempo de espera). Tan pronto como ocurre el evento, el servidor se despierta y envía la respuesta. El cliente crea inmediatamente una nueva solicitud Long Polling. El ciclo se cierra.
\n\nLong Polling en Java: implementación simple
\n\nImaginemos que escribimos un servidor de notificaciones con Servlet API puro (sin Spring, para que sea más claro). Para Long Polling necesitaremos procesamiento asíncrono (AsyncContext), de lo contrario el servidor bloqueará todos los hilos.
\n\nimport javax.servlet.AsyncContext;\nimport javax.servlet.annotation.WebServlet;\nimport javax.servlet.http.HttpServlet;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\nimport java.util.Queue;\nimport java.util.concurrent.ConcurrentLinkedQueue;\n\n@WebServlet(urlPatterns = "/long-poll", asyncSupported = true)\npublic class LongPollingServlet extends HttpServlet {\n\n // Cola para almacenar las conexiones "dormidas"\n private static final Queue<AsyncContext> WAITING_CLIENTS = new ConcurrentLinkedQueue<>();\n\n @Override\n protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {\n AsyncContext asyncContext = req.startAsync();\n asyncContext.setTimeout(30_000); // Tiempo de espera de 30 segundos\n\n // Guardamos el contexto\n WAITING_CLIENTS.add(asyncContext);\n\n // Si el tiempo de espera expira, devolvemos una respuesta vacía\n asyncContext.addListener(new javax.servlet.AsyncListener() {\n public void onTimeout(javax.servlet.AsyncEvent event) {\n WAITING_CLIENTS.remove(asyncContext);\n try {\n HttpServletResponse response = (HttpServletResponse) event.getAsyncContext().getResponse();\n response.getWriter().write("timeout");\n response.setStatus(HttpServletResponse.SC_OK);\n asyncContext.complete();\n } catch (IOException e) { e.printStackTrace(); }\n }\n // Otros métodos del listener se omiten por brevedad\n });\n }\n\n // Este método se llama cuando aparece un nuevo evento\n public static void sendEventToClients(String message) {\n for (AsyncContext ctx : WAITING_CLIENTS) {\n try {\n HttpServletResponse response = (HttpServletResponse) ctx.getResponse();\n response.getWriter().write(message);\n response.setStatus(HttpServletResponse.SC_OK);\n ctx.complete();\n WAITING_CLIENTS.remove(ctx);\n } catch (IOException e) { e.printStackTrace(); }\n }\n }\n}\n\nCómo funciona:
\n- \n
- El cliente realiza una solicitud GET a
/long-poll. \n - El servidor cambia la solicitud a modo asíncrono y se «duerme» (espera). \n
- Cuando aparece un mensaje (método
sendEventToClien\n