API WebSocket de JavaScript
Aprende la API WebSocket de JavaScript: ciclo de vida de la conexión, readyState, envío de texto, JSON y datos binarios, reconexión y seguridad, con un ejemplo de Echo Chat ejecutable.
Introducción a la tecnología WebSocket
WebSocket es un protocolo que proporciona un canal de comunicación full-duplex (bidireccional) entre el navegador y un servidor a través de una única conexión TCP de larga duración. Una vez abierta la conexión, cualquiera de los dos extremos puede enviar un mensaje en cualquier momento sin esperar a que el otro lo solicite — algo que el HTTP convencional no puede hacer.
Esta página cubre qué problemas resuelven los WebSockets, el ciclo de vida de la conexión, el envío de texto y datos binarios, el trabajo con mensajes JSON, la reconexión, la seguridad y cuándo usar una biblioteca en lugar de la API nativa. Todo se construye en torno a un ejemplo de Echo Chat ejecutable.
¿Por qué WebSockets en lugar de HTTP?
Con HTTP convencional (incluidos la Fetch API y XMLHttpRequest), el cliente debe iniciar cada intercambio. Para obtener actualizaciones en tiempo real hay que hacer polling — preguntar repetidamente "¿hay algo nuevo?" — lo que desperdicia ancho de banda y añade latencia. Los WebSockets invierten esto: el servidor puede enviar datos en el instante en que los tiene, con prácticamente ninguna sobrecarga por mensaje.
| Enfoque | Dirección | Sobrecarga | Ideal para |
|---|---|---|---|
| Petición/respuesta HTTP | El cliente pregunta, el servidor responde | Cabeceras completas por petición | Solicitudes puntuales, APIs REST |
| Polling | El cliente pregunta según un temporizador | Muchas peticiones innecesarias | Actualizaciones simples y poco frecuentes |
| WebSocket | Ambos envían libremente | Un handshake, frames pequeños | Chat, feeds en vivo, juegos, dashboards |
Usa WebSockets cuando el servidor necesite hablar primero o los mensajes fluyan constantemente: chat, juegos multijugador, editores colaborativos, tickers de bolsa y dashboards en tiempo real. Para actualizaciones ocasionales unidireccionales del servidor al cliente únicamente, los Server-Sent Events o la Push API pueden ser más sencillos.
El ciclo de vida de la conexión WebSocket
Abres una conexión pasando una URL ws:// (sin cifrar) o wss:// (cifrada) al constructor WebSocket. La conexión pasa entonces por cuatro estados expuestos por socket.readyState:
| Constante | Valor | Significado |
|---|---|---|
WebSocket.CONNECTING | 0 | Handshake en progreso (estado inicial) |
WebSocket.OPEN | 1 | Listo para enviar y recibir |
WebSocket.CLOSING | 2 | Se ha solicitado un cierre |
WebSocket.CLOSED | 3 | Conexión cerrada o no se pudo abrir |
Reaccionas a las transiciones con cuatro eventos: open, message, error y close. La regla clave es que solo puedes llamar a socket.send() mientras readyState sea OPEN — enviar antes de que se dispare open lanza un error.
const socket = new WebSocket("wss://echo.websocket.events");
socket.addEventListener("open", () => {
console.log("open, readyState =", socket.readyState); // 1
socket.send("hello");
});
socket.addEventListener("message", (event) => {
console.log("received:", event.data); // "hello" (echoed back)
});
socket.addEventListener("close", (event) => {
console.log("closed, code =", event.code); // e.g. 1000
});Configuración del Echo Chat con WebSocket
Estructura HTML básica
Primero, establecemos la interfaz de usuario con HTML que incluye un área de visualización de mensajes, un campo de entrada y botones de control.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>WebSocket Echo Chat</title>
</head>
<body>
<textarea id="chatBox" readonly style="width: 100%; height: 300px"></textarea><br />
<input type="text" id="messageInput" placeholder="Type a message..." style="width: 75%" />
<button onclick="sendMessage()">Send</button>
<button onclick="closeConnection()">Close Connection</button>
</body>
</html>Integración de WebSocket con JavaScript
El JavaScript para gestionar la comunicación WebSocket es clave para habilitar las interacciones en tiempo real.
<script>
// Accessing the chat box and message input elements from the HTML.
const chatBox = document.getElementById("chatBox");
const messageInput = document.getElementById("messageInput");
// Establishing a WebSocket connection to the echo server.
const socket = new WebSocket("wss://echo.websocket.events");
// When the connection is open, display a connected message in the chat box.
socket.addEventListener("open", function (event) {
chatBox.value += "Connected to the echo server\n";
});
// Handle incoming messages by adding them to the chat box.
socket.addEventListener("message", function (event) {
chatBox.value += "Echoed back: " + event.data + "\n";
});
// Handle connection errors.
socket.addEventListener("error", function (event) {
chatBox.value += "Connection error occurred.\n";
});
// Handle connection closure.
socket.addEventListener("close", function (event) {
chatBox.value += "Connection closed. Code: " + event.code + "\n";
});
// Function to send a message when the send button is clicked.
function sendMessage() {
const message = messageInput.value; // Get the message from the input field.
if (!message) return; // If there's no message, don't do anything.
socket.send(message); // Send the message to the server.
chatBox.value += "You: " + message + "\n"; // Show the message in the chat box.
messageInput.value = ""; // Clear the message input field.
}
// Function to close the WebSocket connection.
function closeConnection() {
if (socket.readyState === WebSocket.OPEN) {
socket.close(1000, "The user closed the connection"); // Close the connection normally.
chatBox.value += "Connection closed by user\n"; // Inform the user in the chat box.
} else {
alert("Connection is not open or already closed."); // Alert if the connection can't be closed.
}
}
// Ensure the WebSocket is closed properly when the webpage is closed or reloaded.
window.addEventListener("beforeunload", function () {
if (socket.readyState === WebSocket.OPEN) {
socket.close(1000, "The page is unloading"); // Close the connection normally.
}
});
</script>Este script configura una función de chat en una página web que se conecta a un servidor mediante WebSockets. A continuación, una explicación sencilla de sus partes:
- Acceso a los elementos HTML: El script obtiene el cuadro de chat y el campo de entrada de mensajes de la página web para poder interactuar con ellos.
- Conexión WebSocket: Abre una conexión a un servidor que repite los mensajes de vuelta. Esto significa que cualquier cosa que envíes a este servidor te será devuelta inmediatamente.
- Mostrar el estado de la conexión: Cuando la conexión se establece con éxito, muestra un mensaje en el cuadro de chat indicando que la conexión al servidor de eco está establecida.
- Gestión de mensajes entrantes: Los mensajes que llegan del servidor se añaden al cuadro de chat, mostrando que el servidor ha devuelto los mensajes.
- Envío de mensajes: Hay una función para enviar los mensajes escritos en el campo de entrada. Si hay texto, lo envía al servidor y lo muestra en el cuadro de chat como tu mensaje.
- Cierre de la conexión: También hay una función para cerrar la conexión WebSocket cuando sea necesario, como cuando el usuario decide cerrarla o cuando la página web se está cerrando.
Esta configuración permite la comunicación en tiempo real con el servidor y ayuda a probar y demostrar cómo funcionan las aplicaciones de mensajería.
Ahora juntemos todo y veámoslo en acción:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>WebSocket Echo Chat</title>
</head>
<body>
<textarea id="chatBox" readonly style="width: 100%; height: 300px">
</textarea>
<br />
<input
type="text"
id="messageInput"
placeholder="Type a message..."
style="width: 75%"
/>
<button onclick="sendMessage()">Send</button>
<button onclick="closeConnection()">Close Connection</button>
</body>
<script>
const chatBox = document.getElementById("chatBox");
const messageInput = document.getElementById("messageInput");
const socket = new WebSocket("wss://echo.websocket.events");
socket.addEventListener("open", function (event) {
chatBox.value += "Connected to the echo server\n";
});
socket.addEventListener("message", function (event) {
chatBox.value += "Echoed back: " + event.data + "\n";
});
socket.addEventListener("error", function (event) {
chatBox.value += "Connection error occurred.\n";
});
socket.addEventListener("close", function (event) {
chatBox.value += "Connection closed. Code: " + event.code + "\n";
});
function sendMessage() {
const message = messageInput.value;
if (!message) return;
socket.send(message);
chatBox.value += "You: " + message + "\n";
messageInput.value = "";
}
function closeConnection() {
if (socket.readyState === WebSocket.OPEN) {
socket.close(1000, "The user closed the connection");
chatBox.value += "Connection closed by user\n";
} else {
alert("Connection not open or already closed.");
}
}
window.addEventListener("beforeunload", function () {
if (socket.readyState === WebSocket.OPEN) {
socket.close(1000, "The page is unloading");
}
});
</script>
</html>En el ejemplo anterior, en cuanto te conectas al servidor, puedes empezar a enviar mensajes y recibirás exactamente lo que has escrito, devuelto en eco. Si haces clic en el botón 'Close Connection', la conexión WebSocket se terminará y ya no recibirás mensajes de eco.
Características y técnicas avanzadas de WebSocket
Envío de datos estructurados como JSON
Las aplicaciones reales rara vez envían cadenas simples — envían objetos. Como el canal solo transporta texto o datos binarios, serializas los objetos con JSON.stringify() antes de enviarlos y los vuelves a analizar con JSON.parse() al recibirlos. Consulta Trabajar con JSON para más información sobre este formato.
// Sending an object
const payload = { type: "chat", user: "Ann", text: "Hi!" };
socket.send(JSON.stringify(payload));
// Receiving and parsing it
socket.addEventListener("message", (event) => {
const msg = JSON.parse(event.data);
console.log(msg.user + ": " + msg.text); // "Ann: Hi!"
});Un patrón común es un campo type que permite que una conexión multiplexa muchos tipos de mensajes — chat, presence, typing, etc. — cada uno gestionado por su propia rama.
Gestión de datos binarios
Los WebSockets no se limitan al texto. También pueden transportar frames binarios, útiles para audio, imágenes o estado de juego. Establece socket.binaryType para controlar cómo llegan los datos binarios entrantes — "blob" (el valor predeterminado) o "arraybuffer".
socket.binaryType = "arraybuffer";
// Send raw bytes
const bytes = new Uint8Array([72, 73]); // "HI"
socket.send(bytes);
socket.addEventListener("message", (event) => {
if (typeof event.data === "string") {
console.log("text frame:", event.data);
} else {
const view = new Uint8Array(event.data);
console.log("binary frame, length:", view.length);
}
});Reconexión automática
Las redes fallan. El WebSocket nativo no se reconecta por sí solo — cuando el evento close se dispara de forma inesperada, debes reabrir la conexión manualmente, idealmente con un retroceso creciente (exponencial) para no saturar el servidor.
let delay = 1000; // start at 1 second
function connect() {
const socket = new WebSocket("wss://echo.websocket.events");
socket.addEventListener("open", () => {
delay = 1000; // reset back-off after a successful connection
});
socket.addEventListener("close", () => {
setTimeout(connect, delay);
delay = Math.min(delay * 2, 30000); // cap at 30 seconds
});
}
connect();Implementación de la seguridad en WebSocket
La seguridad es fundamental cuando se trabaja con WebSockets:
- Usa WSS: Utiliza siempre WebSocket Secure (WSS), que cifra los datos transmitidos entre el cliente y el servidor.
- Autenticación: Implementa autenticación basada en tokens para garantizar que solo los usuarios autorizados puedan establecer conexiones WebSocket.
- Validación: Valida correctamente todos los datos enviados al servidor para prevenir vulnerabilidades web comunes como XSS o inyección SQL.
WebSocket y el patrón Pub/Sub
El patrón publicación/suscripción es un modelo popular en los servicios de datos en tiempo real donde los mensajes se difunden a través de un canal. Los servicios WebSocket como PubNub ofrecen APIs que soportan el modelo pub/sub, mejorando las capacidades de WebSocket mediante la gestión de conexiones, el cifrado de datos y la difusión basada en canales.
Bibliotecas y frameworks de WebSocket
Varias bibliotecas JavaScript facilitan el trabajo con WebSockets y lo hacen más robusto:
- Socket.IO: Proporciona características adicionales como reconexión automática, gestión de eventos y manejo de salas.
- WebSocket-Node: Una implementación de servidor WebSocket para Node.js.
- ReconnectingWebSocket: Una pequeña biblioteca que añade funciones de reconexión a los WebSockets nativos.
Conclusión
La tecnología WebSocket es un bloque fundamental para desarrollar aplicaciones web interactivas y en tiempo real. Al integrar WebSockets en tus aplicaciones, habilitas la interacción directa y bidireccional entre clientes y servidores. Esta guía recorrió el ciclo de vida de la conexión, el ejemplo de Echo Chat, los mensajes JSON y binarios, la reconexión y la seguridad — suficiente para construir características robustas en tiempo real.
Ver también
- Fetch API — para peticiones HTTP puntuales cuando no necesitas un canal en vivo.
- XMLHttpRequest — la API de peticiones anterior sobre la que WebSockets y Fetch se basan conceptualmente.
- Push API — mensajes del servidor al cliente incluso cuando la página está cerrada.
- Trabajar con JSON — el formato estándar para mensajes WebSocket estructurados.