écouter

void Worker::listen(void)

Utilisé pour exécuter l'écoute après l'instanciation du Worker.

Cette méthode est principalement utilisée pour créer dynamiquement de nouvelles instances de Worker après le démarrage du processus Worker, permettant ainsi à un même processus d'écouter plusieurs ports et de prendre en charge plusieurs protocoles. Il est à noter qu'utiliser cette méthode n'augmente l'écoute que dans le processus actuel, cela ne crée pas de nouveaux processus dynamiquement et ne déclenchera pas la méthode onWorkerStart.

Par exemple, après le démarrage d'un Worker http, si un Worker websocket est instancié, alors ce processus pourra être accessible à la fois via le protocole http et via le protocole websocket. Étant donné que le Worker websocket et le Worker http sont dans le même processus, ils peuvent accéder à des variables mémoire partagées et partager toutes les connexions socket. Cela permet de recevoir des requêtes http et ensuite d'interagir avec le client websocket pour effectuer une opération de poussée de données vers le client de manière similaire.

Note :

Si la version de PHP est <=7.0, il n'est pas possible d'instancier le même port avec plusieurs processus fils. Par exemple, si le processus A crée un Worker écoutant le port 2016, alors le processus B ne pourra pas créer un Worker écoutant également le port 2016, sinon une erreur Address already in use sera générée. Par exemple, le code suivant est impossible à exécuter.

use Workerman\Worker;
use Workerman\Connection\TcpConnection;
require_once __DIR__ . '/vendor/autoload.php';

$worker = new Worker();
// 4 processus
$worker->count = 4;
// Après le démarrage de chaque processus, ajouter un Worker pour écouter dans le processus actuel
$worker->onWorkerStart = function($worker)
{
    /**
     * Lorsque les 4 processus démarrent, chacun crée un Worker sur le port 2016
     * Lors de l'exécution de worker->listen(), une erreur Address already in use sera générée
     * Si worker->count=1, aucune erreur ne se produira
     */
    $inner_worker = new Worker('http://0.0.0.0:2016');
    $inner_worker->onMessage = 'on_message';
    // Exécuter l'écoute. Ici, une erreur Address already in use sera générée
    $inner_worker->listen();
};

$worker->onMessage = 'on_message';

function on_message(TcpConnection $connection, $data)
{
    $connection->send("hello\n");
}

// Exécuter le worker
Worker::runAll();

Si votre version de PHP est >=7.0, vous pouvez définir Worker->reusePort=true, ce qui permet à plusieurs processus fils de créer des Workers avec le même port. Voir l'exemple ci-dessous :

use Workerman\Worker;
use Workerman\Connection\TcpConnection;
require_once __DIR__ . '/vendor/autoload.php';

$worker = new Worker('text://0.0.0.0:2015');
// 4 processus
$worker->count = 4;
// Après le démarrage de chaque processus, ajouter un Worker pour écouter dans le processus actuel
$worker->onWorkerStart = function($worker)
{
    $inner_worker = new Worker('http://0.0.0.0:2016');
    // Activer le réemploi de port, permettant de créer des Workers écoutant le même port (nécessite PHP>=7.0)
    $inner_worker->reusePort = true;
    $inner_worker->onMessage = 'on_message';
    // Exécuter l'écoute. L'écoute normale ne génèrera pas d'erreur
    $inner_worker->listen();
};

$worker->onMessage = 'on_message';

function on_message(TcpConnection $connection, $data)
{
    $connection->send("hello\n");
}

// Exécuter le worker
Worker::runAll();

Exemple de backend PHP pour pousser des messages en temps réel aux clients

Principe :

  1. Établir un Worker websocket pour maintenir une connexion longue avec le client.

  2. Créer un Worker text à l'intérieur du Worker websocket.

  3. Le Worker websocket et le Worker text étant dans le même processus, il est facile de partager les connexions des clients.

  4. Un système PHP autonome communique avec le Worker text via le protocole text.

  5. Le Worker text opère la connexion websocket pour effectuer le push des données.

Code et étapes

push.php

<?php
use Workerman\Worker;
use Workerman\Connection\TcpConnection;
require_once __DIR__ . '/vendor/autoload.php';

// Initialiser un conteneur worker, écoutant le port 1234
$worker = new Worker('websocket://0.0.0.0:1234');

/*
 * Notez que le nombre de processus doit être réglé sur 1 ici
 */
$worker->count = 1;
// Créer un Worker text après le démarrage du processus pour ouvrir un port de communication interne
$worker->onWorkerStart = function($worker)
{
    // Ouvrir un port interne pour faciliter l'envoi de données à partir du système interne, format du protocole Text : texte + saut de ligne
    $inner_text_worker = new Worker('text://0.0.0.0:5678');
    $inner_text_worker->onMessage = function(TcpConnection $connection, $buffer)
    {
        // Format du tableau $data, contenant uid, indiquant à quelle page avec quel uid envoyer des données
        $data = json_decode($buffer, true);
        $uid = $data['uid'];
        // Envoyer des données à la page uid via workerman
        $ret = sendMessageByUid($uid, $buffer);
        // Retourner le résultat du push
        $connection->send($ret ? 'ok' : 'fail');
    };
    // ## Exécuter l'écoute ##
    $inner_text_worker->listen();
};
// Ajouter une nouvelle propriété pour garder la correspondance entre uid et connection
$worker->uidConnections = array();
// Fonction de rappel exécutée lors de la réception d'un message d'un client
$worker->onMessage = function(TcpConnection $connection, $data)
{
    global $worker;
    // Vérifier si le client courant est validé, à savoir s'il a défini un uid
    if(!isset($connection->uid))
    {
       // Si non validé, considérer le premier paquet comme uid (ici pour faciliter la démonstration, aucune validation réelle n'a été effectuée)
       $connection->uid = $data;
       /* Enregistrer la correspondance uid-connection, ce qui permet de retrouver facilement la connection via uid,
        * permettant de pousser des données ciblées vers des uid spécifiques
        */
       $worker->uidConnections[$connection->uid] = $connection;
       return;
    }
};

// Lorsqu'un client se déconnecte
$worker->onClose = function(TcpConnection $connection)
{
    global $worker;
    if(isset($connection->uid))
    {
        // Supprimer la correspondance lors de la déconnexion
        unset($worker->uidConnections[$connection->uid]);
    }
};

// Pousser des données vers tous les utilisateurs validés
function broadcast($message)
{
   global $worker;
   foreach($worker->uidConnections as $connection)
   {
        $connection->send($message);
   }
}

// Pousser des données vers un uid spécifique
function sendMessageByUid($uid, $message)
{
    global $worker;
    if(isset($worker->uidConnections[$uid]))
    {
        $connection = $worker->uidConnections[$uid];
        $connection->send($message);
        return true;
    }
    return false;
}

// Exécuter tous les workers
Worker::runAll();

Démarrer le service backend
php push.php start -d

Code JS pour que le frontend reçoive le push

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);
};

Code pour pousser des messages depuis le backend

// Établir une connexion socket au port interne de push
$client = stream_socket_client('tcp://127.0.0.1:5678', $errno, $errmsg, 1);
// Données à pousser, contenant le champ uid, indiquant que c'est pour ce uid
$data = array('uid'=>'uid1', 'percent'=>'88%');
// Envoyer des données, notez que le port 5678 est le port du protocole Text, ce qui nécessite d'ajouter un saut de ligne à la fin des données
fwrite($client, json_encode($data)."\n");
// Lire le résultat du push
echo fread($client, 8192);