🔌 ¿Qué es WebSocket?
📖 Definición
WebSocket es un protocolo que permite la comunicación bidireccional en tiempo real entre el cliente y el servidor. A diferencia de HTTP, una vez establecida la conexión, se mantiene de forma persistente y el servidor puede enviar datos al cliente primero (push). Se utiliza en chat, notificaciones en tiempo real, juegos, herramientas de colaboración, etc.
🎯 Comprender mediante analogía
Teléfono vs Correo
HTTP (Correo)
├─ Enviar carta (solicitud)
├─ Recibir respuesta (respuesta)
├─ Escribir nueva carta (solicitud)
└─ Necesita nueva carta cada vez (sobrecarga)
WebSocket (Teléfono)
├─ Una vez conectado al teléfono
├─ Conversación continua posible
├─ Ambos pueden hablar primero
└─ Conexión mantenida hasta que se cuelga
Entrega vs Tubería directa
HTTP
┌─────────┐ Solicitud ┌─────────┐
│ Cliente │ ──────────→ │ Servidor│
└─────────┘ └─────────┘
┌─────────┐ Respuesta ┌─────────┐
│ Cliente │ ←────────── │ Servidor│
└─────────┘ └─────────┘
¡Nueva conexión cada vez!
WebSocket
┌─────────┐ ┌─────────┐
│ Cliente │ ←─────────→ │ Servidor│
└─────────┘ Conexión └─────────┘
persistente
↕ Bidireccional
⚙️ Principio de funcionamiento
1. Proceso de conexión WebSocket
1. Solicitud de handshake HTTP
GET /chat HTTP/1.1
Upgrade: websocket
Connection: Upgrade
2. Aprobación del servidor
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
3. Conexión WebSocket establecida
↕ Inicio de comunicación bidireccional
4. Transmisión de datos
Mensaje ←→ Mensaje ←→ Mensaje
5. Cierre de conexión
Llamada a close()
2. HTTP vs WebSocket
HTTP (Unidireccional, Solicitud-Respuesta)
Cliente → Servidor: Solicitud
Cliente ← Servidor: Respuesta
[Conexión cerrada]
WebSocket (Bidireccional, Persistente)
Cliente ↔ Servidor
- El servidor puede enviar mensajes primero
- Mantenimiento de conexión
- Comunicación en tiempo real
💡 Ejemplos prácticos
WebSocket básico (Cliente)
// En el navegador web
const ws = new WebSocket('ws://localhost:8080');
// Conexión exitosa
ws.onopen = () => {
console.log('¡Conexión establecida!');
ws.send('¡Hola!'); // Envío de mensaje
};
// Recepción de mensaje
ws.onmessage = (event) => {
console.log('Mensaje recibido:', event.data);
};
// Manejo de errores
ws.onerror = (error) => {
console.error('Error:', error);
};
// Cierre de conexión
ws.onclose = () => {
console.log('Conexión cerrada');
};
// Envío de mensaje
ws.send('Hello Server!');
ws.send(JSON.stringify({ type: 'chat', message: 'Hola' }));
// Cerrar conexión
ws.close();
Servidor WebSocket básico (Node.js)
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
console.log('Servidor WebSocket en ejecución: ws://localhost:8080');
// Al conectar el cliente
wss.on('connection', (ws) => {
console.log('¡Cliente conectado!');
// Mensaje de bienvenida
ws.send('¡Bienvenido!');
// Recepción de mensaje
ws.on('message', (message) => {
console.log('Mensaje recibido:', message.toString());
// Difusión a todos los clientes
wss.clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
client.send(message.toString());
}
});
});
// Cierre de conexión
ws.on('close', () => {
console.log('Conexión del cliente cerrada');
});
// Manejo de errores
ws.on('error', (error) => {
console.error('Error:', error);
});
});
Socket.io (Aplicación de chat práctica)
// ============ Servidor (server.js) ============
const express = require('express');
const http = require('http');
const socketIO = require('socket.io');
const app = express();
const server = http.createServer(app);
const io = socketIO(server);
app.use(express.static('public'));
const users = new Map(); // Usuarios conectados
io.on('connection', (socket) => {
console.log('Usuario conectado:', socket.id);
// Entrada del usuario
socket.on('join', (username) => {
users.set(socket.id, username);
// Notificación de entrada (a todos)
io.emit('user-joined', {
username,
userCount: users.size
});
console.log(`${username} entró (total ${users.size} personas)`);
});
// Mensaje de chat
socket.on('chat-message', (message) => {
const username = users.get(socket.id);
// Difusión a todos
io.emit('chat-message', {
username,
message,
timestamp: new Date()
});
});
// Escribiendo
socket.on('typing', () => {
const username = users.get(socket.id);
socket.broadcast.emit('user-typing', username);
});
// Cierre de conexión
socket.on('disconnect', () => {
const username = users.get(socket.id);
users.delete(socket.id);
// Notificación de salida
io.emit('user-left', {
username,
userCount: users.size
});
console.log(`${username} salió (restante: ${users.size} personas)`);
});
});
server.listen(3000, () => {
console.log('Servidor en ejecución: http://localhost:3000');
});
<!-- ============ Cliente (public/index.html) ============ -->
<!DOCTYPE html>
<html>
<head>
<title>Chat en tiempo real</title>
<style>
#messages {
height: 300px;
overflow-y: auto;
border: 1px solid #ccc;
padding: 10px;
margin-bottom: 10px;
}
.message {
margin-bottom: 10px;
}
.system {
color: #999;
font-style: italic;
}
</style>
</head>
<body>
<h1>Chat en tiempo real</h1>
<div id="messages"></div>
<div id="typing"></div>
<input id="message-input" type="text" placeholder="Escribe un mensaje...">
<button id="send-btn">Enviar</button>
<script src="/socket.io/socket.io.js"></script>
<script>
const socket = io();
const messagesDiv = document.getElementById('messages');
const messageInput = document.getElementById('message-input');
const sendBtn = document.getElementById('send-btn');
const typingDiv = document.getElementById('typing');
// Ingreso de nombre de usuario
const username = prompt('Ingresa tu nombre de usuario:') || 'Anónimo';
socket.emit('join', username);
// Notificación de entrada de usuario
socket.on('user-joined', (data) => {
addMessage(`${data.username} entró. (${data.userCount} conectados)`, 'system');
});
// Notificación de salida de usuario
socket.on('user-left', (data) => {
addMessage(`${data.username} salió. (${data.userCount} conectados)`, 'system');
});
// Recepción de mensaje de chat
socket.on('chat-message', (data) => {
const time = new Date(data.timestamp).toLocaleTimeString();
addMessage(`[${time}] ${data.username}: ${data.message}`);
});
// Mostrar escribiendo
socket.on('user-typing', (username) => {
typingDiv.textContent = `${username} está escribiendo...`;
setTimeout(() => {
typingDiv.textContent = '';
}, 1000);
});
// Envío de mensaje
function sendMessage() {
const message = messageInput.value.trim();
if (message) {
socket.emit('chat-message', message);
messageInput.value = '';
}
}
sendBtn.addEventListener('click', sendMessage);
messageInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
sendMessage();
}
});
// Evento de escritura
let typingTimeout;
messageInput.addEventListener('input', () => {
clearTimeout(typingTimeout);
socket.emit('typing');
typingTimeout = setTimeout(() => {
// Dejar de escribir
}, 500);
});
// Agregar mensaje
function addMessage(text, className = '') {
const messageEl = document.createElement('div');
messageEl.className = `message ${className}`;
messageEl.textContent = text;
messagesDiv.appendChild(messageEl);
messagesDiv.scrollTop = messagesDiv.scrollHeight;
}
</script>
</body>
</html>
Función Room (Chat de grupo)
// Servidor
io.on('connection', (socket) => {
// Entrada a la sala
socket.on('join-room', (roomId) => {
socket.join(roomId);
console.log(`${socket.id} entró a la sala ${roomId}`);
// Envío solo a personas en la misma sala
socket.to(roomId).emit('user-joined-room', {
userId: socket.id,
roomId
});
});
// Envío de mensaje a sala específica
socket.on('room-message', ({ roomId, message }) => {
// Envío solo a esa sala
io.to(roomId).emit('room-message', {
userId: socket.id,
message
});
});
// Salida de la sala
socket.on('leave-room', (roomId) => {
socket.leave(roomId);
socket.to(roomId).emit('user-left-room', socket.id);
});
});
Sistema de notificaciones en tiempo real
// Servidor
const notifications = io.of('/notifications');
notifications.on('connection', (socket) => {
console.log('Conexión de notificación:', socket.id);
// Autenticación de usuario
const userId = socket.handshake.query.userId;
// Entrada a sala personal
socket.join(`user-${userId}`);
// Envío de notificación a usuario específico
socket.on('send-notification', ({ targetUserId, notification }) => {
notifications.to(`user-${targetUserId}`).emit('notification', notification);
});
});
// Envío de notificación desde cualquier lugar
function sendNotification(userId, notification) {
notifications.to(`user-${userId}`).emit('notification', {
type: notification.type,
message: notification.message,
timestamp: new Date()
});
}
// Ejemplo: Notificación de nuevo pedido
app.post('/api/orders', async (req, res) => {
const order = await createOrder(req.body);
// Notificación en tiempo real al vendedor
sendNotification(order.sellerId, {
type: 'new-order',
message: `¡Ha llegado un nuevo pedido!`,
orderId: order.id
});
res.json(order);
});
Colaboración en tiempo real (Edición colaborativa)
// Servidor
io.on('connection', (socket) => {
socket.on('join-document', (docId) => {
socket.join(docId);
// Envío del contenido actual del documento
const document = getDocument(docId);
socket.emit('document-loaded', document);
});
// Modificación del documento
socket.on('document-change', ({ docId, changes }) => {
// Actualización del documento
updateDocument(docId, changes);
// Envío de modificaciones a otros usuarios
socket.to(docId).emit('document-updated', {
userId: socket.id,
changes
});
});
// Compartir posición del cursor
socket.on('cursor-move', ({ docId, position }) => {
socket.to(docId).emit('cursor-update', {
userId: socket.id,
position
});
});
});
🤔 Preguntas frecuentes
P1. ¿WebSocket vs HTTP Polling?
R:
// HTTP Polling (Ineficiente)
// El cliente envía solicitudes regularmente al servidor
setInterval(() => {
fetch('/api/messages')
.then(res => res.json())
.then(messages => {
// Verificar nuevos mensajes
});
}, 1000); // Solicitud cada segundo
// Problemas:
// - Solicitudes innecesarias (solicitud aunque no haya nuevos mensajes)
// - Tiempo de retraso (hasta 1 segundo)
// - Carga del servidor
// WebSocket (Eficiente)
// Comunicación bidireccional en tiempo real
const socket = io();
socket.on('new-message', (message) => {
// ¡Recepción inmediata de nuevos mensajes!
});
// Ventajas:
// - Recepción inmediata (sin retraso)
// - Push del servidor
// - Eficiente
P2. ¿WebSocket es siempre mejor?
R:
// ✅ WebSocket es bueno en estos casos
1. Chat en tiempo real
2. Notificaciones en tiempo real
3. Herramientas de colaboración (edición colaborativa)
4. Juegos en tiempo real
5. Información bursátil (datos en tiempo real)
6. Control de dispositivos IoT
// ❌ WebSocket no es necesario en estos casos
1. Sitios web generales (blog, noticias)
2. REST API (CRUD)
3. Contenido estático
4. No se necesitan actualizaciones en tiempo real
// HTTP es mejor en estos casos:
// - Solicitud-respuesta simple
// - Necesidad de caché
// - Diseño RESTful
P3. ¿Socket.io vs WebSocket nativo?
R:
// WebSocket nativo
const ws = new WebSocket('ws://localhost:8080');
ws.send('Hello');
// Ventajas: Ligero y rápido
// Desventajas: Sin funciones adicionales, sin fallback
// Socket.io
const socket = io();
socket.emit('message', 'Hello');
// Ventajas:
// 1. Reconexión automática
// 2. Soporte de Room/Namespace
// 3. Fallback (Polling si WebSocket no está disponible)
// 4. Basado en eventos
// 5. Soporte binario
// Desventajas:
// 1. Pesado (tamaño de biblioteca)
// 2. No compatible con WebSocket nativo
// Criterios de selección:
// Proyecto simple → WebSocket nativo
// Proyecto complejo → Socket.io
P4. ¿Seguridad de WebSocket?
R:
// 1. Usar wss:// (WebSocket Secure)
// Como HTTP → HTTPS
// ws:// → wss://
const socket = new WebSocket('wss://example.com'); // ✅ Cifrado
const socket = new WebSocket('ws://example.com'); // ❌ Texto plano
// 2. Autenticación
// Socket.io
io.use((socket, next) => {
const token = socket.handshake.auth.token;
if (isValidToken(token)) {
next();
} else {
next(new Error('Fallo de autenticación'));
}
});
// Cliente
const socket = io({
auth: {
token: 'user-token'
}
});
// 3. Verificación de permisos
socket.on('delete-message', (messageId) => {
const userId = socket.userId;
if (canDeleteMessage(userId, messageId)) {
deleteMessage(messageId);
} else {
socket.emit('error', 'No tienes permiso');
}
});
// 4. Limitación de velocidad
const rateLimit = require('socket.io-rate-limit');
io.use(rateLimit({
max: 10, // Máximo 10
interval: 1000 // Por segundo
}));
// 5. Validación de entrada
socket.on('message', (message) => {
// Prevención de XSS
const sanitized = sanitizeHtml(message);
if (sanitized.length > 1000) {
return socket.emit('error', 'Mensaje demasiado largo');
}
broadcast(sanitized);
});
P5. ¿Gestión de conexión WebSocket?
R:
// 1. Reconexión automática (Socket.io)
const socket = io({
reconnection: true,
reconnectionAttempts: 5,
reconnectionDelay: 1000
});
socket.on('disconnect', () => {
console.log('Conexión cortada');
});
socket.on('reconnect', () => {
console.log('Reconexión exitosa');
});
// 2. Heartbeat (Verificación de conexión)
// Servidor
setInterval(() => {
wss.clients.forEach((ws) => {
if (ws.isAlive === false) {
return ws.terminate(); // Cierre si no hay respuesta
}
ws.isAlive = false;
ws.ping(); // Envío de ping
});
}, 30000);
ws.on('pong', () => {
ws.isAlive = true;
});
// 3. Gestión de memoria
const MAX_CONNECTIONS = 1000;
wss.on('connection', (ws) => {
if (wss.clients.size > MAX_CONNECTIONS) {
ws.close(1008, 'El servidor está lleno');
return;
}
// Procesamiento de conexión...
});
// 4. Cierre elegante
process.on('SIGTERM', () => {
console.log('Cerrando servidor...');
// Notificación de cierre a todas las conexiones
wss.clients.forEach((ws) => {
ws.send(JSON.stringify({ type: 'server-shutdown' }));
ws.close();
});
wss.close(() => {
console.log('Servidor WebSocket cerrado');
process.exit(0);
});
});
🎓 Próximos pasos
Si has entendido WebSocket, intenta aprender a continuación:
- ¿Qué es Node.js? - Construcción de servidor WebSocket
- ¿Qué es React? - Implementación de interfaz en tiempo real
- ¿Qué es Docker? (Documentación a crear) - Despliegue de servidor WebSocket
A practicar
# Crear aplicación de chat Socket.io
# 1. Inicialización del proyecto
mkdir chat-app
cd chat-app
npm init -y
# 2. Instalación de paquetes
npm install express socket.io
# 3. Creación del servidor (ver ejemplo anterior)
# server.js
# 4. Ejecución
node server.js
# 5. Acceso a http://localhost:3000
🎬 Resumen
WebSocket es la tecnología central de la web en tiempo real:
- Comunicación bidireccional: Servidor ↔ Cliente
- Conexión persistente: Una vez conectado, uso continuo
- Tiempo real: Transmisión de datos instantánea
- Eficiente: Mucho más eficiente que Polling
¡Si necesitas funcionalidad en tiempo real, usa WebSocket! 🔌✨