Tutorial #7: Creaci贸n del Servidor WebSocket con Ratchet
Tutorial #7: Creaci贸n del Servidor WebSocket con Ratchet
馃幆 Introducci贸n
¡Bienvenido de nuevo! En esta lecci贸n, vamos a crear nuestro servidor WebSocket usando Ratchet y probarlo con un cliente simple.
El servidor WebSocket ser谩 el encargado de:
馃敆 Gestionar conexiones de usuarios
馃摠 Enviar y recibir mensajes en tiempo real
馃攧 Manejar la se帽alizaci贸n para WebRTC
馃搳 Mantener lista de usuarios en l铆nea
馃搧 Paso 1: Estructura de Carpetas
1.1 Crear la Carpeta bin
Primero, vamos a crear la carpeta bin en la ra铆z de nuestro proyecto:
馃搧 vchat/ ├── 馃搧 bin/ ← ¡Nueva carpeta! │ └── 馃搫 server.php ← Servidor WebSocket ├── 馃搧 core/ │ └── 馃搧 classes/ │ └── 馃搫 Chat.php ← L贸gica del chat ├── 馃搫 composer.json └── 馃搫 index.php ← Cliente de prueba
1.2 Crear los Archivos
Crear carpeta bin:
# Windows mkdir C:\xampp\htdocs\vchat\bin # Linux/macOS mkdir /opt/lampp/htdocs/vchat/bin
Crear archivo server.php:
# Windows type nul > C:\xampp\htdocs\vchat\bin\server.php # Linux/macOS touch /opt/lampp/htdocs/vchat/bin/server.php
馃摑 Paso 2: Crear el Servidor (server.php)
2.1 Archivo server.php
<?php // bin/server.php - Servidor WebSocket use Ratchet\Server\IoServer; use Ratchet\Http\HttpServer; use Ratchet\WebSocket\WsServer; use MyApp\Chat; // Cargar el autoloader de Composer require dirname(__DIR__) . '/vendor/autoload.php'; // Crear el servidor $server = IoServer::factory( new HttpServer( new WsServer( new Chat() ) ), 8080 // Puerto ); echo "馃攲 Servidor WebSocket ejecut谩ndose en puerto 8080\n"; echo "馃摗 Esperando conexiones...\n"; // Ejecutar el servidor $server->run();
2.2 Explicaci贸n del C贸digo
// 1. Namespace y use - Importar las clases necesarias use Ratchet\Server\IoServer; // Servidor base use Ratchet\Http\HttpServer; // Maneja HTTP use Ratchet\WebSocket\WsServer; // Maneja WebSocket use MyApp\Chat; // Nuestra l贸gica // 2. Autoloader - Cargar todas las dependencias require dirname(__DIR__) . '/vendor/autoload.php'; // 3. Crear el servidor - Envolver capas $server = IoServer::factory( new HttpServer( // Capa HTTP (para el upgrade) new WsServer( // Capa WebSocket new Chat() // Nuestra l贸gica ) ), 8080 // Puerto ); // 4. Ejecutar - Bucle principal $server->run();
馃挰 Paso 3: Crear la L贸gica del Chat (Chat.php)
3.1 Archivo Chat.php
<?php // core/classes/Chat.php namespace MyApp; use Ratchet\MessageComponentInterface; use Ratchet\ConnectionInterface; class Chat implements MessageComponentInterface { protected $clients; public function __construct() { // Almacenar todas las conexiones $this->clients = new \SplObjectStorage; echo "馃煝 Servidor Chat inicializado\n"; } // ===== Cuando un cliente se conecta ===== public function onOpen(ConnectionInterface $conn) { // Guardar la conexi贸n $this->clients->attach($conn); echo "馃敆 Nueva conexi贸n! ID: {$conn->resourceId}\n"; echo "馃搳 Conexiones activas: " . count($this->clients) . "\n"; } // ===== Cuando un cliente env铆a un mensaje ===== public function onMessage(ConnectionInterface $from, $msg) { $numRecv = count($this->clients) - 1; echo sprintf( "馃摠 Conexi贸n %d envi贸: '%s' a %d otro(s) cliente(s)\n", $from->resourceId, $msg, $numRecv ); // Enviar a todos excepto al remitente foreach ($this->clients as $client) { if ($from !== $client) { $client->send($msg); } } } // ===== Cuando un cliente se desconecta ===== public function onClose(ConnectionInterface $conn) { // Eliminar la conexi贸n $this->clients->detach($conn); echo "馃攲 Conexi贸n {$conn->resourceId} desconectada\n"; echo "馃搳 Conexiones activas: " . count($this->clients) . "\n"; } // ===== Cuando ocurre un error ===== public function onError(ConnectionInterface $conn, \Exception $e) { echo "❌ Error: {$e->getMessage()}\n"; $conn->close(); } }
3.2 Explicaci贸n del C贸digo
Namespace y Use
namespace MyApp; // Espacio de nombres para organizar el c贸digo use Ratchet\MessageComponentInterface; // Interfaz para manejar mensajes use Ratchet\ConnectionInterface; // Interfaz para conexiones
Propiedades
protected $clients; // Almacena todas las conexiones
Constructor - __construct()
public function __construct() { // Crear un almacenamiento de objetos $this->clients = new \SplObjectStorage; }
SplObjectStoragees una clase nativa de PHP para almacenar objetos 煤nicosSe ejecuta cuando se crea el servidor
onOpen() - Cuando un cliente se conecta
public function onOpen(ConnectionInterface $conn) { $this->clients->attach($conn); // Guardar la conexi贸n echo "Nueva conexi贸n! ID: {$conn->resourceId}\n"; }
$conn->resourceIdes el ID 煤nico de la conexi贸nSe ejecuta cada vez que un cliente se conecta
onMessage() - Cuando recibe un mensaje
public function onMessage(ConnectionInterface $from, $msg) { foreach ($this->clients as $client) { if ($from !== $client) { $client->send($msg); // Reenviar a todos los dem谩s } } }
$fromes el cliente que envi贸 el mensaje$msges el contenido del mensajeReenv铆a el mensaje a todos excepto al remitente
onClose() - Cuando un cliente se desconecta
public function onClose(ConnectionInterface $conn) { $this->clients->detach($conn); // Eliminar la conexi贸n }
Elimina la conexi贸n del almacenamiento
Ya no se enviar谩n mensajes a este cliente
onError() - Cuando ocurre un error
public function onError(ConnectionInterface $conn, \Exception $e) { echo "Error: {$e->getMessage()}\n"; $conn->close(); // Cerrar la conexi贸n }
Maneja errores de conexi贸n
Cierra la conexi贸n problem谩tica
馃摝 Paso 4: Configurar Composer
4.1 Actualizar composer.json
{ "autoload": { "psr-4": { "MyApp\\": "core/classes" } }, "require": { "cboden/ratchet": "^0.4.3" } }
4.2 Ejecutar Composer Update
# Ir a la carpeta del proyecto cd C:\xampp\htdocs\vchat # Actualizar Composer composer update
Resultado esperado:
Loading composer repositories with package information Updating dependencies Package operations: 0 installs, 0 updates, 0 removals Generating autoload files
馃寪 Paso 5: Crear un Cliente de Prueba (index.php)
5.1 Archivo index.php
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Cliente WebSocket - Prueba</title> <style> body { font-family: Arial; padding: 20px; } #messages { border: 1px solid #ccc; height: 300px; overflow-y: auto; padding: 10px; margin-bottom: 10px; background: #f9f9f9; } .message { margin: 5px 0; padding: 5px 10px; border-radius: 5px; } .incoming { background: #e3f2fd; } .outgoing { background: #e8f5e9; text-align: right; } #messageInput { width: 80%; padding: 10px; border: 1px solid #ccc; border-radius: 5px; } button { padding: 10px 20px; background: #4CAF50; color: white; border: none; border-radius: 5px; cursor: pointer; } button:hover { background: #45a049; } .status { padding: 10px; margin-bottom: 10px; border-radius: 5px; } .connected { background: #d4edda; color: #155724; } .disconnected { background: #f8d7da; color: #721c24; } </style> </head> <body> <h1>馃摗 Prueba de WebSocket</h1> <div id="status" class="status disconnected">⛔ Desconectado</div> <div id="messages"></div> <input type="text" id="messageInput" placeholder="Escribe un mensaje..."> <button onclick="sendMessage()">馃摠 Enviar</button> <button onclick="sendTestMessage()">馃И Enviar prueba</button> <script> let ws = null; let isConnected = false; // ===== Conectar al servidor ===== function connect() { ws = new WebSocket('ws://localhost:8080'); ws.onopen = () => { console.log('✅ Conectado al servidor'); isConnected = true; document.getElementById('status').className = 'status connected'; document.getElementById('status').innerHTML = '✅ Conectado al servidor'; addMessage('Sistema', 'Conexi贸n establecida', 'system'); }; ws.onmessage = (event) => { console.log('馃摠 Mensaje recibido:', event.data); addMessage('Otro', event.data, 'incoming'); }; ws.onclose = () => { console.log('❌ Desconectado'); isConnected = false; document.getElementById('status').className = 'status disconnected'; document.getElementById('status').innerHTML = '⛔ Desconectado'; addMessage('Sistema', 'Conexi贸n perdida', 'system'); }; ws.onerror = (error) => { console.error('❌ Error:', error); addMessage('Sistema', 'Error en la conexi贸n', 'system'); }; } // ===== Enviar mensaje ===== function sendMessage() { const input = document.getElementById('messageInput'); const msg = input.value.trim(); if (!msg) { alert('Escribe un mensaje'); return; } if (!isConnected) { alert('No est谩s conectado al servidor'); return; } ws.send(msg); addMessage('T煤', msg, 'outgoing'); input.value = ''; } // ===== Enviar mensaje de prueba ===== function sendTestMessage() { if (!isConnected) { alert('No est谩s conectado al servidor'); return; } const msg = 'Mensaje de prueba ' + new Date().toLocaleTimeString(); ws.send(msg); addMessage('T煤', msg, 'outgoing'); } // ===== Mostrar mensaje en la interfaz ===== function addMessage(sender, text, type) { const container = document.getElementById('messages'); const div = document.createElement('div'); div.className = `message ${type}`; if (type === 'system') { div.style.background = '#fff3cd'; div.style.textAlign = 'center'; div.style.color = '#856404'; } div.innerHTML = `<strong>${sender}:</strong> ${text}`; container.appendChild(div); container.scrollTop = container.scrollHeight; } // ===== Conectar autom谩ticamente ===== connect(); // ===== Enviar con Enter ===== document.getElementById('messageInput').addEventListener('keypress', (e) => { if (e.key === 'Enter') { sendMessage(); } }); </script> </body> </html>
馃殌 Paso 6: Probar el Servidor
6.1 Iniciar el Servidor
Windows 馃獰
# Ir a la carpeta del proyecto cd C:\xampp\htdocs\vchat # Ejecutar el servidor php bin\server.php
Linux/macOS 馃惂馃崕
# Ir a la carpeta del proyecto cd /opt/lampp/htdocs/vchat # Ejecutar el servidor php bin/server.php
Resultado esperado:
馃攲 Servidor WebSocket ejecut谩ndose en puerto 8080 馃摗 Esperando conexiones... 馃煝 Servidor Chat inicializado
6.2 Probar el Cliente
Abre tu navegador en:
http://localhost/vchat/Deber铆as ver la interfaz de prueba
El estado deber铆a cambiar a "Conectado"
En el terminal del servidor ver谩s:
馃敆 Nueva conexi贸n! ID: 42 馃搳 Conexiones activas: 1
6.3 Probar con M煤ltiples Clientes
Abre Chrome en
http://localhost/vchat/Abre Firefox en
http://localhost/vchat/Env铆a un mensaje desde Chrome
Deber铆as verlo aparecer en Firefox
Salida del servidor:
馃摠 Conexi贸n 42 envi贸: 'Hola mundo' a 1 otro(s) cliente(s)
馃攳 Paso 7: Probar desde la Consola
7.1 Usar la Consola del Navegador
Abre las herramientas de desarrollo (F12) y en la consola:
// Verificar la conexi贸n console.log(ws); // Enviar un mensaje ws.send('Mensaje desde consola'); // Ver estado console.log('Conectado:', isConnected);
7.2 Usar un Cliente WebSocket en L铆nea
Puedes usar herramientas como:
WebSocket King (extensi贸n de Chrome)
wscat (CLI)
# Instalar wscat npm install -g wscat # Conectar al servidor wscat -c ws://localhost:8080 # Enviar mensaje > Hola desde wscat
馃搳 Diagrama de Flujo
1. Usuario abre p谩gina → index.php
└── JavaScript crea WebSocket
2. WebSocket se conecta → ws://localhost:8080
└── Servidor ejecuta onOpen()
└── Guarda conexi贸n en $clients
3. Usuario env铆a mensaje → ws.send()
└── Servidor ejecuta onMessage()
└── Reenv铆a a todos los dem谩s
4. Usuario cierra p谩gina → ws.close()
└── Servidor ejecuta onClose()
└── Elimina conexi贸n de $clients馃洜️ Soluci贸n de Problemas
Error: "Class 'MyApp\Chat' not found"
Soluci贸n: Regenerar el autoloader
composer dump-autoloadError: "Connection refused"
Soluci贸n: Asegurar que el servidor est茅 corriendo
# Verificar que el servidor est谩 ejecut谩ndose php bin/server.php # Verificar el puerto netstat -an | findstr 8080 # Windows netstat -an | grep 8080 # Linux/macOS
Error: "Address already in use"
Soluci贸n: Cambiar el puerto o liberarlo
// Cambiar en server.php $server = IoServer::factory( new HttpServer( new WsServer( new Chat() ) ), 8081 // Cambiar a otro puerto );
El mensaje no aparece en el cliente
Verificar:
La consola del navegador (F12) para errores
El terminal del servidor para mensajes
Que el cliente est茅 conectado
馃摎 Resumen
En esta lecci贸n hemos:
✅ Creado la estructura de carpetas (bin/)
✅ Implementado el servidor (server.php)
✅ Creado la l贸gica del chat (Chat.php)
✅ Configurado Composer para autoloading
✅ Creado un cliente de prueba (index.php)
✅ Probado el servidor con m煤ltiples clientes
✅ Verificado la comunicaci贸n en tiempo real
馃殌 Pr贸ximo Paso
En la siguiente lecci贸n vamos a:
Integrar el servidor WebSocket con la autenticaci贸n
Manejar usuarios espec铆ficos (no solo broadcast)
Preparar el sistema para WebRTC
¡Excelente trabajo! Tu servidor WebSocket est谩 funcionando. 馃帀
Comentarios
Publicar un comentario