Классы протоколов
Фактически, создание своего протокола - довольно простая задача. Обычно протокол подразумевает 2 логические операции:
- Определение размера данных (они могут делиться на части)
- Интерпретация данных (кодирование/декодирование)
В Localzet Server мы определили интерфейс для стандартизации класса протокола. Он включает в себя метод определения
размера данных (input) и методы интерпретации (encode и decode).
Как это работает?
- При получении запроса сервер, запущенный с определённым протоколом, вызывает метод
inputдля определения длинны пакета. Метод должен вернуть длину пакета или0, в случае если длина ещё не известна (сервер будет ждать ещё данные). - Сервер проверит, соответствует ли длина пакета длине из
input. Если нет - сервер продолжит ожидать догрузку данных. - После полной загрузки пакета сервер отправит его в метод
decodeдля обработки. - Обработанные данные отправляются в качестве аргумента в callback-метод
onMessageдля выполнения основной логики проекта. - Когда бизнес-логике потребуется отправить данные, перед отправкой сервер вызовет метод
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 Буфер полученных данных
* @param \localzet\Server\Connection\ConnectionInterface $connection Соединение с клиентом
* @return int|false Длина пакета, 0 если данных недостаточно, false при ошибке
*/
public static function input(string $recv_buffer, \localzet\Server\Connection\ConnectionInterface $connection): int|false
{
// Получение позиции символа переноса строки "\n"
$pos = strpos($recv_buffer, "\n");
// Если его нет - невозможно получить длину пакета, вернуть 0 и ожидать дополнительных данных
if($pos === false)
{
return 0;
}
// Если "\n" нашёлся - вернуть длину текущего пакета (включая символ переноса строки)
return $pos+1;
}
/**
* Упаковка, автоматически вызывается при отправке данных клиенту
*
* @param mixed $data Данные для отправки
* @param \localzet\Server\Connection\ConnectionInterface $connection Соединение с клиентом
* @return string Закодированные данные для отправки
*/
public static function encode(mixed $data, \localzet\Server\Connection\ConnectionInterface $connection): string
{
// Формирование JSON и добавление символа переноса строки в качестве пометки окончания запроса
return json_encode($data, JSON_THROW_ON_ERROR) . "\n";
}
/**
* Разбор, автоматически вызывается, когда количество полученных байтов равно возвращенному значению input (значение больше 0)
* и передается как параметр $data в обратный вызов onMessage
*
* @param string $buffer Буфер данных для декодирования
* @param \localzet\Server\Connection\ConnectionInterface $connection Соединение с клиентом
* @return mixed Декодированные данные
*/
public static function decode(string $buffer, \localzet\Server\Connection\ConnectionInterface $connection): mixed
{
// Удаление символа переноса строки и превращение в массив
$decoded = json_decode(trim($buffer), true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new \RuntimeException('Ошибка декодирования JSON: ' . json_last_error_msg());
}
return $decoded;
}
}
Теперь протокол 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();

