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