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:
-
Coloque o arquivo do protocolo na pasta Protocols do projeto, por exemplo, o arquivo MyApp/Protocols/JsonNL.php.
-
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
- 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
inputdo protocolo para verificar o comprimento do pacote, e o métodoinputretornará o valor de comprimento$lengthpara o framework workerman. - Depois de receber o valor
$length, o framework workerman verificará se já recebeu dados com o comprimento$lengthno 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. - Quando o comprimento dos dados no buffer for suficiente, o workerman irá cortar os dados de comprimento
$lengthdo buffer (ou seja, fragmentação) e chamará o métododecodedo protocolo para desfragmentar. Os dados desfragmentados serão$data. - Após a desfragmentação, o workerman passará os dados
$datapara a função de retornoonMessage($connection, $data), permitindo ao negócio usar a variável$datapara acessar os dados completos que foram enviados pelo cliente e já desfragmentados. - Quando o negócio em
onMessageprecisar enviar dados ao cliente através do método$connection->send($buffer), o workerman chamará automaticamente o métodoencodedo protocolo para empacotar o$bufferantes 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 nomeProtocols. Por exemplo,new Worker('JsonNL://0.0.0.0:1234')tentará carregar o protocoloProtocols\JsonNL.
Se ocorrer o erroClass '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.