Comment personnaliser un protocole

En réalité, établir son propre protocole est une tâche relativement simple. Un protocole simple comprend généralement deux parties :

  • Un identifiant pour délimiter les frontières des données
  • Une définition du format des données

Un exemple

Définition du protocole

Supposons que l'identifiant pour délimiter les frontières des données soit le caractère de nouvelle ligne "\n" (notez que les données de la requête elles-mêmes ne peuvent pas contenir le caractère de nouvelle ligne), et que le format des données soit en JSON. Par exemple, voici un paquet de requête qui respecte cette règle.

{"type":"message","content":"hello"}

Notez qu'il y a un caractère de nouvelle ligne à la fin des données de la requête (indiqué dans PHP par la chaîne de double guillemets "\n"), ce qui représente la fin d'une requête.

Étapes de mise en œuvre

Dans Workerman, pour mettre en œuvre le protocole ci-dessus, supposons que le nom du protocole soit JsonNL et que le projet soit MyApp. Les étapes à suivre sont les suivantes :

  1. Placez le fichier de protocole dans le dossier Protocols du projet, par exemple le fichier MyApp/Protocols/JsonNL.php.

  2. Implémentez la classe JsonNL avec namespace Protocols; comme espace de noms, et vous devez implémenter trois méthodes statiques nommées respectivement input, encode et decode.

Notez que Workerman appellera automatiquement ces trois méthodes statiques pour gérer les opérations de déballage, d'emballage et de découpage. Consultez le diagramme de flux pour plus de détails sur le processus d'exécution ci-dessous.

Flux d'interaction entre Workerman et la classe de protocole

  1. Supposons qu'un client envoie un paquet de données au serveur. Après avoir reçu des données (qui peuvent être des données partielles), le serveur appellera immédiatement la méthode input du protocole pour vérifier la longueur de ce paquet. La méthode input retourne la valeur de longueur $length au cadre Workerman.
  2. Une fois que le cadre Workerman reçoit ce $length, il détermine si des données de longueur $length ont déjà été reçues dans le tampon de données. Si ce n'est pas le cas, il continuera à attendre les données jusqu'à ce que la longueur des données dans le tampon ne soit pas inférieure à $length.
  3. Une fois que la longueur des données dans le tampon est suffisante, Workerman extraira les données de longueur $length du tampon (c'est-à-dire découpage) et appellera la méthode decode du protocole pour déballer, les données déballées seront $data.
  4. Après le déballage, Workerman passera les données $data à la fonction de rappel onMessage($connection, $data), permettant au service d'utiliser la variable $data pour obtenir les données complètes et déballées envoyées par le client.
  5. Lorsque le service dans onMessage a besoin d'envoyer des données au client en appelant la méthode $connection->send($buffer), Workerman utilisera automatiquement la méthode encode du protocole pour emballer $buffer avant de l'envoyer au client.

Mise en œuvre concrète

Implémentation de MyApp/Protocols/JsonNL.php

namespace Protocols;
class JsonNL
{
    /**
     * Vérifie l'intégrité du paquet
     * Si la longueur du paquet peut être déterminée, elle retourne la longueur totale du paquet dans le tampon, sinon retourne 0 pour continuer à attendre les données
     * Si le protocole présente des problèmes, retourner -1 provoquera la déconnexion de la connexion client actuelle
     * @param string $buffer
     * @return int
     */
    public static function input($buffer)
    {
        // Obtenir la position du caractère de nouvelle ligne "\n"
        $pos = strpos($buffer, "\n");
        // Pas de caractère de nouvelle ligne, impossible de déterminer la longueur du paquet, retourne 0 pour continuer à attendre les données
        if($pos === false)
        {
            return 0;
        }
        // Caractère de nouvelle ligne présent, retourner la longueur actuelle du paquet (incluant le caractère de nouvelle ligne)
        return $pos+1;
    }

    /**
     * Emballage, appelé automatiquement lors de l'envoi de données au client
     * @param string $buffer
     * @return string
     */
    public static function encode($buffer)
    {
        // Sérialiser en json et ajouter un caractère de nouvelle ligne comme marqueur de fin de requête
        return json_encode($buffer)."\n";
    }

    /**
     * Déballage, appelé automatiquement lorsque le nombre de bytes des données reçues est égal à la valeur retournée par input (valeur supérieure à 0)
     * Et transmis au paramètre $data de la fonction de rappel onMessage
     * @param string $buffer
     * @return string
     */
    public static function decode($buffer)
    {
        // Retirer la nouvelle ligne, restaurer en tableau
        return json_decode(trim($buffer), true);
    }
}

Ainsi, le protocole JsonNL est implémenté et peut être utilisé dans le projet MyApp. Voici comment l'utiliser :

Fichier : MyApp\start.php

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

$json_worker = new Worker('JsonNL://0.0.0.0:1234');
$json_worker->onMessage = function(TcpConnection $connection, $data) {

    // $data est les données envoyées par le client, les données ont déjà été traitées par JsonNL::decode
    echo $data;

    // Les données envoyées par $connection->send seront automatiquement emballées par JsonNL::encode, puis envoyées au client
    $connection->send(array('code'=>0, 'msg'=>'ok'));

};
Worker::runAll();
...

Astuce
Workerman tentera de charger le protocole sous le namespace Protocols, par exemple new Worker('JsonNL://0.0.0.0:1234') tentera de charger le protocole Protocols\JsonNL.
Si une erreur Class 'Protocols\JsonNL' not found apparaît, veuillez consulter l'autoload pour implémenter l'autoloading.

Description de l'interface de protocole

Les classes de protocole développées dans Workerman doivent implémenter trois méthodes statiques : input, encode et decode. La description de l'interface de protocole est disponible dans Workerman/Protocols/ProtocolInterface.php et est définie comme suit :

namespace Workerman\Protocols;

use \Workerman\Connection\ConnectionInterface;

/**
 * Interface de protocole
 * @author walkor <walkor@workerman.net>
 */
interface ProtocolInterface
{
    /**
     * Utilisé pour découper les paquets dans le recv_buffer reçu
     *
     * Si la longueur du paquet peut être déterminée dans $recv_buffer, il retourne la longueur totale du paquet
     * Sinon, retourne 0, ce qui indique qu'il faut plus de données pour obtenir la longueur actuelle du paquet de requête
     * Si -1 est retourné, cela indique une requête erronée, alors la connexion sera fermée
     *
     * @param ConnectionInterface $connection
     * @param string $recv_buffer
     * @return int
     */
    public static function input($recv_buffer, ConnectionInterface $connection);

    /**
     * Utilisé pour déballer les requêtes
     *
     * La valeur de retour de input est supérieure à 0 et Workerman a reçu suffisamment de données, decode est automatiquement appelé
     * Ensuite, elle déclenche la fonction de rappel onMessage et passe les données déchiffrées à la seconde paramètre de onMessage
     * Cela signifie que lorsque la requête complète du client est reçue, decode est automatiquement appelée sans que le code métier ait besoin d'appeler manuellement
     * @param ConnectionInterface $connection
     * @param string $recv_buffer
     */
    public static function decode($recv_buffer, ConnectionInterface $connection);

    /**
     * Utilisé pour emballer les requêtes
     *
     * Lorsqu'il est nécessaire d'envoyer des données au client et d'appeler $connection->send($data); 
     * $data sera automatiquement emballé une fois avec encode, ce qui le transformera en format de données conforme au protocole, puis envoyé au client
     * Cela signifie que les données envoyées au client seront automatiquement emballées par encode, sans appel manuel requis dans le code métier
     * @param ConnectionInterface $connection
     * @param mixed $data
     */
    public static function encode($data, ConnectionInterface $connection);
}

Remarque :

Il n'est pas strictement requis que les classes de protocole dans Workerman soient basées sur l'interface ProtocolInterface. En réalité, une classe de protocole a juste besoin de contenir les trois méthodes statiques input, encode et decode.