Классы протоколов

Фактически, создание своего протокола - довольно простая задача. Обычно протокол подразумевает 2 логические операции:

  • Определение размера данных (они могут делиться на части)
  • Интерпретация данных (кодирование/декодирование)

В Localzet Server мы определили интерфейс для стандартизации класса протокола. Он включает в себя метод определения размера данных (input) и методы интерпретации (encode и decode).

Как это работает?

  1. При получении запроса сервер, запущенный с определённым протоколом, вызывает метод input для определения длинны пакета. Метод должен вернуть длину пакета или 0, в случае если длина ещё не известна (сервер будет ждать ещё данные).
  2. Сервер проверит, соответствует ли длина пакета длине из input. Если нет - сервер продолжит ожидать догрузку данных.
  3. После полной загрузки пакета сервер отправит его в метод decode для обработки.
  4. Обработанные данные отправляются в качестве аргумента в callback-метод onMessage для выполнения основной логики проекта.
  5. Когда бизнес-логике потребуется отправить данные, перед отправкой сервер вызовет метод encode для обработки ответа.

Пример протокола

К примеру, давайте напишем такой протокол, данные в котором будут представлены в формате простого JSON, а символом окончания данных (для определения размера) будет символ переноса строки \n.

Для того чтобы класс стал протоколом необходимо, чтобы он находился в namespace Protocols;. Для упрощения можно использовать интерфейс \localzet\Server\Protocols\ProtocolInterface.

<?php

namespace Protocols;

use \localzet\Server\Protocols\ProtocolInterface;

class SuperJSON
{
    /**
     * Проверка целостности пакета
     * Если можно получить длину пакета в $recv_buffer, возвращается длина всего пакета
     * В противном случае возвращается 0, что означает, что необходимо дождаться дополнительных данных.
     * Если возникает ошибка в протоколе, можно вернуть false, что приведет к разрыву соединения с текущим клиентом
     * @param string $recv_buffer
     * @return int
     */
    public static function input($recv_buffer)
    {
        // Получение позиции символа переноса строки "\n"
        $pos = strpos($buffer, "\n");

        // Если его нет - невозможно получить длину пакета, вернуть 0 и ожидать дополнительных данных
        if($pos === false)
        {
            return 0;
        }

        // Если "\n" нашёлся - вернуть длину текущего пакета (включая символ переноса строки)
        return $pos+1;
    }

    /**
     * Упаковка, автоматически вызывается при отправке данных клиенту
     * @param string $buffer
     * @return string
     */
    public static function encode($buffer)
    {
        // Формирование JSON и добавление символа переноса строки в качестве пометки окончания запроса
        return json_encode($buffer) . "\n";
    }

    /**
     * Разбор, автоматически вызывается, когда количество полученных байтов равно возвращенному значению input (значение больше 0)
     * и передается как параметр $data в обратный вызов onMessage
     * @param string $buffer
     * @return string
     */
    public static function decode($buffer)
    {
        // Удаление символа переноса строки и превращение в массив
        return json_decode(trim($buffer), true);
    }
}

Теперь протокол SuperJSON реализован и готов к использованию, например:

<?php

use localzet\Server;
use localzet\Server\Connection\TcpConnection;

require_once __DIR__ . '/vendor/autoload.php';

$server = new Server("SuperJSON://0.0.0.0:2000");

$server->onMessage = function(TcpConnection $connection, $data)
{
    // $data - это данные, отправленные клиентом, которые уже были обработаны методом SuperJSON::decode
    echo $data;

    // Данные, отправленные через $connection->send, автоматически упаковываются методом SuperJSON::encode и затем отправляются клиенту
    $connection->send(['code' => 0, 'msg' => 'ok']);
};

Server::runAll();