listen
void Worker::listen(void)
Se utiliza para ejecutar la escucha después de instanciar Worker.
Este método se utiliza principalmente para crear dinámicamente nuevas instancias de Worker después de que los procesos de Worker se hayan iniciado, lo que permite que un mismo proceso escuche varios puertos y soporte múltiples protocolos. Es importante notar que utilizar este método solo agrega escucha en el proceso actual, no creará dinámicamente nuevos procesos y no activará el método onWorkerStart.
Por ejemplo, si un Worker de http se inicia y luego se instancia un Worker de websocket, entonces este proceso podrá ser accesible tanto a través del protocolo http como del protocolo websocket. Dado que el Worker de websocket y el Worker de http están en el mismo proceso, pueden acceder a variables de memoria compartidas y compartir todas las conexiones de socket. Esto permite recibir solicitudes http y luego manipular el cliente de websocket para completar la tarea de enviar datos al cliente.
Nota:
Si la versión de PHP es <= 7.0, no se permite instanciar un Worker en el mismo puerto en múltiples subprocesos. Por ejemplo, si el proceso A crea un Worker que escucha el puerto 2016, entonces el proceso B no puede crear otro Worker que escuche el puerto 2016, de lo contrario se generará un error Address already in use. Por ejemplo, el siguiente código es inviable de ejecutar.
use Workerman\Worker;
use Workerman\Connection\TcpConnection;
require_once __DIR__ . '/vendor/autoload.php';
$worker = new Worker();
// 4 procesos
$worker->count = 4;
// Cada proceso, después de ser iniciado, agregará un nuevo Worker para escuchar
$worker->onWorkerStart = function($worker)
{
/**
* Cuando se inician los 4 procesos, se crean Workers que escuchan en el puerto 2016.
* Al ejecutar worker->listen() se generará un error Address already in use
* Si worker->count=1, no se generará un error
*/
$inner_worker = new Worker('http://0.0.0.0:2016');
$inner_worker->onMessage = 'on_message';
// Ejecutar la escucha. Aquí se generará el error Address already in use
$inner_worker->listen();
};
$worker->onMessage = 'on_message';
function on_message(TcpConnection $connection, $data)
{
$connection->send("hello\n");
}
// Ejecutar el worker
Worker::runAll();
Si su versión de PHP es >= 7.0, puede configurar Worker->reusePort=true, lo que permite que varios subprocesos creen Workers en el mismo puerto. Véase el siguiente ejemplo:
use Workerman\Worker;
use Workerman\Connection\TcpConnection;
require_once __DIR__ . '/vendor/autoload.php';
$worker = new Worker('text://0.0.0.0:2015');
// 4 procesos
$worker->count = 4;
// Cada proceso, después de ser iniciado, agregará un nuevo Worker para escuchar
$worker->onWorkerStart = function($worker)
{
$inner_worker = new Worker('http://0.0.0.0:2016');
// Configurar reutilización de puerto, permitiendo crear Workers que escuchen el mismo puerto (se requiere PHP >= 7.0)
$inner_worker->reusePort = true;
$inner_worker->onMessage = 'on_message';
// Ejecutar la escucha. La escucha debería funcionar sin errores
$inner_worker->listen();
};
$worker->onMessage = 'on_message';
function on_message(TcpConnection $connection, $data)
{
$connection->send("hello\n");
}
// Ejecutar el worker
Worker::runAll();
Ejemplo de backend PHP para enviar mensajes al cliente en tiempo real
Principio:
-
Establecer un Worker de websocket para mantener una conexión de largo plazo con el cliente.
-
Dentro de este Worker de websocket, crear un Worker de texto.
-
El Worker de websocket y el Worker de texto están en el mismo proceso, lo que permite compartir fácilmente las conexiones de los clientes.
-
Un sistema independiente de PHP en el backend se comunica con el Worker de texto a través del protocolo de texto.
-
El Worker de texto manipula la conexión de websocket para completar la tarea de enviar datos.
Código y pasos
push.php
<?php
use Workerman\Worker;
use Workerman\Connection\TcpConnection;
require_once __DIR__ . '/vendor/autoload.php';
// Inicializar un contenedor de worker que escuche en el puerto 1234
$worker = new Worker('websocket://0.0.0.0:1234');
/*
* Nota: aquí el número de procesos debe ser configurado a 1
*/
$worker->count = 1;
// Al iniciar el proceso worker, crear un Worker de texto para abrir un puerto de comunicación interno
$worker->onWorkerStart = function($worker)
{
// Abrir un puerto interno para facilitar que el sistema interno envíe datos, formato de protocolo de texto.
$inner_text_worker = new Worker('text://0.0.0.0:5678');
$inner_text_worker->onMessage = function(TcpConnection $connection, $buffer)
{
// El formato del arreglo $data, que contiene uid, indica a qué uid se le envían los datos
$data = json_decode($buffer, true);
$uid = $data['uid'];
// Enviar datos a la página del uid a través de workerman
$ret = sendMessageByUid($uid, $buffer);
// Devolver el resultado de la transmisión
$connection->send($ret ? 'ok' : 'fail');
};
// ## Ejecutar la escucha ##
$inner_text_worker->listen();
};
// Nueva propiedad para guardar el mapeo de uid a conexión
$worker->uidConnections = array();
// Función de callback que se ejecuta cuando un cliente envía un mensaje
$worker->onMessage = function(TcpConnection $connection, $data)
{
global $worker;
// Verificar si el cliente actual ha sido autenticado, es decir, si se ha configurado el uid
if(!isset($connection->uid))
{
// Si no está autenticado, tomar el primer paquete como uid (esto es solo para facilitar la demostración, no se realiza una verificación real)
$connection->uid = $data;
/* Guardar el mapeo de uid a conexión, permitiendo que se busque la conexión fácilmente a través del uid,
* para enviar datos específicos a un uid determinado
*/
$worker->uidConnections[$connection->uid] = $connection;
return;
}
};
// Cuando un cliente se desconecta
$worker->onClose = function(TcpConnection $connection)
{
global $worker;
if(isset($connection->uid))
{
// Eliminar el mapeo al desconectar
unset($worker->uidConnections[$connection->uid]);
}
};
// Enviar datos a todos los usuarios autenticados
function broadcast($message)
{
global $worker;
foreach($worker->uidConnections as $connection)
{
$connection->send($message);
}
}
// Enviar datos específicos a un uid
function sendMessageByUid($uid, $message)
{
global $worker;
if(isset($worker->uidConnections[$uid]))
{
$connection = $worker->uidConnections[$uid];
$connection->send($message);
return true;
}
return false;
}
// Ejecutar todos los workers
Worker::runAll();
Iniciar el servicio del backend
php push.php start -d
Código JS del frontend para recibir notificaciones
var ws = new WebSocket('ws://127.0.0.1:1234');
ws.onopen = function(){
var uid = 'uid1';
ws.send(uid);
};
ws.onmessage = function(e){
alert(e.data);
};
Código para que el backend envíe los mensajes
// Establecer conexión de socket al puerto interno de envío
$client = stream_socket_client('tcp://127.0.0.1:5678', $errno, $errmsg, 1);
// Los datos a enviar, que incluyen el campo uid, indicando que se enviarán a este uid
$data = array('uid'=>'uid1', 'percent'=>'88%');
// Enviar datos, observar que el puerto 5678 es para el protocolo de texto, por lo que se necesita agregar un salto de línea al final de los datos
fwrite($client, json_encode($data)."\n");
// Leer el resultado de la transmisión
echo fread($client, 8192);