Como Personalizar Protocolos

Na verdade, criar seu próprio protocolo é uma tarefa relativamente simples. Protocolos simples geralmente contêm duas partes:

  • Identificadores que diferenciam os limites dos dados
  • Definição do formato dos dados

Um Exemplo

Definição do Protocolo

Aqui, assumimos que o identificador que diferencia os limites dos dados é o caractere de nova linha "\n" (observe que os dados da solicitação em si não podem conter o caractere de nova linha), e o formato dos dados é Json. Por exemplo, abaixo está um pacote de solicitação que cumpre esta regra.

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

Observe que há um caractere de nova linha no final dos dados da solicitação (representado em PHP como a string entre aspas duplas "\n"), que representa o fim de uma solicitação.

Passos para Implementação

Em Workerman, para implementar o protocolo acima, supondo que o nome do protocolo seja JsonNL e o projeto seja MyApp, você precisará seguir os seguintes passos:

  1. Coloque o arquivo do protocolo na pasta Protocols do projeto, por exemplo, o arquivo MyApp/Protocols/JsonNL.php.

  2. Implemente a classe JsonNL usando namespace Protocols; como espaço de nome e deve implementar três métodos estáticos: input, encode e decode.

Observe que: o workerman chamará automaticamente esses três métodos estáticos para realizar a fragmentação, a desfragmentação e o empacotamento. Consulte a descrição do fluxo de execução abaixo para mais detalhes.

Fluxo de Interação entre Workerman e a Classe de Protocolo

  1. Suponha que o cliente envie um pacote de dados para o servidor. Depois que o servidor recebe os dados (que podem ser dados parciais), ele chamará imediatamente o método input do protocolo para verificar o comprimento do pacote, e o método input retornará o valor de comprimento $length para o framework workerman.
  2. Depois de receber o valor $length, o framework workerman verificará se já recebeu dados com o comprimento $length no buffer de dados atuais. Se não, ele continuará esperando por dados até que o comprimento dos dados no buffer seja, no mínimo, $length.
  3. Quando o comprimento dos dados no buffer for suficiente, o workerman irá cortar os dados de comprimento $length do buffer (ou seja, fragmentação) e chamará o método decode do protocolo para desfragmentar. Os dados desfragmentados serão $data.
  4. Após a desfragmentação, o workerman passará os dados $data para a função de retorno onMessage($connection, $data), permitindo ao negócio usar a variável $data para acessar os dados completos que foram enviados pelo cliente e já desfragmentados.
  5. Quando o negócio em onMessage precisar enviar dados ao cliente através do método $connection->send($buffer), o workerman chamará automaticamente o método encode do protocolo para empacotar o $buffer antes de enviá-lo ao cliente.

Implementação Específica

Implementação de MyApp/Protocols/JsonNL.php

namespace Protocols;
class JsonNL
{
    /**
     * Verifica a integridade do pacote
     * Se for possível obter o comprimento do pacote, retorna o comprimento do pacote no buffer; 
     * caso contrário, retorna 0 e continua esperando por mais dados.
     * Se houver um problema com o protocolo, pode retornar -1, e a conexão atual do cliente será encerrada.
     * @param string $buffer
     * @return int
     */
    public static function input($buffer)
    {
        // Obtém a posição do caractere de nova linha "\n"
        $pos = strpos($buffer, "\n");
        // Sem caractere de nova linha, não é possível determinar o comprimento do pacote, retorna 0 e continua esperando por dados.
        if($pos === false)
        {
            return 0;
        }
        // Com caractere de nova linha, retorna o comprimento do pacote atual (incluindo o caractere de nova linha)
        return $pos+1;
    }

    /**
     * Empacotamento, será chamado automaticamente ao enviar dados ao cliente.
     * @param string $buffer
     * @return string
     */
    public static function encode($buffer)
    {
        // Serializa em json e adiciona o caractere de nova linha como um indicativo de fim da solicitação.
        return json_encode($buffer)."\n";
    }

    /**
     * Desempacotamento, será chamado automaticamente quando o número de bytes recebidos for igual ao valor retornado por input (valor maior que 0).
     * E passa para o parâmetro $data da função de retorno onMessage.
     * @param string $buffer
     * @return string
     */
    public static function decode($buffer)
    {
        // Remove o caractere de nova linha e converte de volta para um array.
        return json_decode(trim($buffer), true);
    }
}

Assim, o protocolo JsonNL está implementado e pode ser utilizado no projeto MyApp. O método de uso é como a seguir.

Arquivo: 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 é os dados enviados pelo cliente, que já foram processados por JsonNL::decode
    echo $data;

    // Dados enviados por $connection->send serão automaticamente empacotados pelo método JsonNL::encode e enviados ao cliente.
    $connection->send(array('code'=>0, 'msg'=>'ok'));

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

Nota
O workerman tentará carregar os protocolos no espaço de nome Protocols. Por exemplo, new Worker('JsonNL://0.0.0.0:1234') tentará carregar o protocolo Protocols\JsonNL.
Se ocorrer o erro Class 'Protocols\JsonNL' not found, consulte Autoload para implementar o carregamento automático.

Descrição da Interface de Protocolo

As classes de protocolo desenvolvidas em Workerman devem implementar três métodos estáticos: input, encode, decode. A descrição da interface do protocolo pode ser encontrada em Workerman/Protocols/ProtocolInterface.php, definida da seguinte forma:

namespace Workerman\Protocols;

use \Workerman\Connection\ConnectionInterface;

/**
 * Interface do Protocolo
* @author walkor <walkor@workerman.net>
 */
interface ProtocolInterface
{
    /**
     * Utilizado para fragmentar no recv_buffer recebido.
     *
     * Se for possível obter o comprimento do pacote em $recv_buffer, retorna o comprimento total do pacote.
     * Caso contrário, retorna 0, indicando que mais dados são necessários para obter o comprimento do pacote atual.
     * Se retornar -1, representa um erro na solicitação, a conexão será encerrada.
     *
     * @param ConnectionInterface $connection
     * @param string $recv_buffer
     * @return int
     */
    public static function input($recv_buffer, ConnectionInterface $connection);

    /**
     * Utilizado para desempacotamento de solicitação.
     *
     * Se o valor retornado por input for maior que 0 e o Workerman recebeu dados suficientes, então decode será chamado automaticamente.
     * Em seguida, a função de retorno onMessage será acionada e os dados decodificados serão passados como segundo parâmetro para a função de retorno onMessage.
     * Isso significa que quando a solicitação completa do cliente for recebida, o decode será chamado automaticamente, sem necessidade de chamada manual no código do negócio.
     * @param ConnectionInterface $connection
     * @param string $recv_buffer
     */
    public static function decode($recv_buffer, ConnectionInterface $connection);

    /**
     * Utilizado para empacotamento de solicitação.
     *
     * Quando é necessário enviar dados ao cliente, ou seja, ao chamar $connection->send($data); 
     * o $data será automaticamente empacotado com encode, transformando-se no formato de dados que cumpre o protocolo, e enviado ao cliente.
     * Portanto, os dados enviados ao cliente serão automaticamente empacotados com encode, sem necessidade de chamada manual no código do negócio.
     * @param ConnectionInterface $connection
     * @param mixed $data
     */
    public static function encode($data, ConnectionInterface $connection);
}

Atenção:

Não há uma exigência rigorosa em Workerman de que a classe de protocolo deva ser baseada em ProtocolInterface. Na verdade, a classe de protocolo apenas precisa conter os três métodos estáticos: input, encode e decode.