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:

text
馃搧 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:

bash
# Windows
mkdir C:\xampp\htdocs\vchat\bin

# Linux/macOS
mkdir /opt/lampp/htdocs/vchat/bin

Crear archivo server.php:

bash
# 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
<?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

php
// 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
<?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

php
namespace MyApp;  // Espacio de nombres para organizar el c贸digo

use Ratchet\MessageComponentInterface;  // Interfaz para manejar mensajes
use Ratchet\ConnectionInterface;        // Interfaz para conexiones

Propiedades

php
protected $clients;  // Almacena todas las conexiones

Constructor - __construct()

php
public function __construct() {
    // Crear un almacenamiento de objetos
    $this->clients = new \SplObjectStorage;
}
  • SplObjectStorage es una clase nativa de PHP para almacenar objetos 煤nicos

  • Se ejecuta cuando se crea el servidor

onOpen() - Cuando un cliente se conecta

php
public function onOpen(ConnectionInterface $conn) {
    $this->clients->attach($conn);  // Guardar la conexi贸n
    echo "Nueva conexi贸n! ID: {$conn->resourceId}\n";
}
  • $conn->resourceId es el ID 煤nico de la conexi贸n

  • Se ejecuta cada vez que un cliente se conecta

onMessage() - Cuando recibe un mensaje

php
public function onMessage(ConnectionInterface $from, $msg) {
    foreach ($this->clients as $client) {
        if ($from !== $client) {
            $client->send($msg);  // Reenviar a todos los dem谩s
        }
    }
}
  • $from es el cliente que envi贸 el mensaje

  • $msg es el contenido del mensaje

  • Reenv铆a el mensaje a todos excepto al remitente

onClose() - Cuando un cliente se desconecta

php
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

php
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

json
{
    "autoload": {
        "psr-4": {
            "MyApp\\": "core/classes"
        }
    },
    "require": {
        "cboden/ratchet": "^0.4.3"
    }
}

4.2 Ejecutar Composer Update

bash
# Ir a la carpeta del proyecto
cd C:\xampp\htdocs\vchat

# Actualizar Composer
composer update

Resultado esperado:

text
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

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 馃獰

cmd
# Ir a la carpeta del proyecto
cd C:\xampp\htdocs\vchat

# Ejecutar el servidor
php bin\server.php

Linux/macOS 馃惂馃崕

bash
# Ir a la carpeta del proyecto
cd /opt/lampp/htdocs/vchat

# Ejecutar el servidor
php bin/server.php

Resultado esperado:

text
馃攲 Servidor WebSocket ejecut谩ndose en puerto 8080
馃摗 Esperando conexiones...
馃煝 Servidor Chat inicializado

6.2 Probar el Cliente

  1. Abre tu navegador en: http://localhost/vchat/

  2. Deber铆as ver la interfaz de prueba

  3. El estado deber铆a cambiar a "Conectado"

En el terminal del servidor ver谩s:

text
馃敆 Nueva conexi贸n! ID: 42
馃搳 Conexiones activas: 1

6.3 Probar con M煤ltiples Clientes

  1. Abre Chrome en http://localhost/vchat/

  2. Abre Firefox en http://localhost/vchat/

  3. Env铆a un mensaje desde Chrome

  4. Deber铆as verlo aparecer en Firefox

Salida del servidor:

text
馃摠 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:

javascript
// 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)

bash
# Instalar wscat
npm install -g wscat

# Conectar al servidor
wscat -c ws://localhost:8080

# Enviar mensaje
> Hola desde wscat

馃搳 Diagrama de Flujo

text
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

bash
composer dump-autoload

Error: "Connection refused"

Soluci贸n: Asegurar que el servidor est茅 corriendo

bash
# 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

php
// 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:

  1. La consola del navegador (F12) para errores

  2. El terminal del servidor para mensajes

  3. Que el cliente est茅 conectado


馃摎 Resumen

En esta lecci贸n hemos:

  1. ✅ Creado la estructura de carpetas (bin/)

  2. ✅ Implementado el servidor (server.php)

  3. ✅ Creado la l贸gica del chat (Chat.php)

  4. ✅ Configurado Composer para autoloading

  5. ✅ Creado un cliente de prueba (index.php)

  6. ✅ Probado el servidor con m煤ltiples clientes

  7. ✅ 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

Entradas m谩s populares de este blog

Tutorial Completo: Chat de Video en Vivo con PHP + WebRTC