Классы протоколов
Фактически, создание своего протокола - довольно простая задача. Обычно протокол подразумевает 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
* @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();