Как настроить протокол
Фактически, создание собственного протокола - довольно простая задача. Простой протокол обычно состоит из двух частей:
- Идентификатор, разделяющий границы данных
- Определение формата данных
Пример
Определение протокола
Допустим, идентификатором, разделяющим границы данных, является символ переноса строки "\n" (заметьте, что внутри самих данных запроса не может содержаться символ переноса строки), и формат данных представлен в виде Json. Ниже приведен запрос, соответствующий этим правилам.
{"type":"message","content":"hello"}
Обратите внимание, что в конце данных запроса находится символ переноса строки (в PHP он представлен как строка в двойных кавычках "\n"), который обозначает завершение запроса.
Шаги реализации
В Workerman, если вы хотите реализовать описанный выше протокол, предположим, что его название - JsonNL, а проект называется MyApp, вам потребуется выполнить следующие шаги:
-
Разместите файл протокола в папке проекта Protocols, например, файл MyApp/Protocols/JsonNL.php.
-
Реализуйте класс JsonNL с использованием пространства имен
namespace Protocols;
, который должен содержать три статических метода: input, encode и decode.
Примечание: Workerman автоматически вызывает эти три статических метода для реализации фрагментации, сборки и упаковки данных. Конкретный процесс описан в разделе "Работа с классом протокола".
Взаимодействие между Workerman и классом протокола
- Предположим, что клиент отправляет пакет данных серверу. После получения данных (возможно, только части данных) сервер сразу вызывает метод
input
протокола для определения длины этого пакета. Методinput
возвращает значение длины$length
фрейма для Workerman. - После того, как Workerman получает значение
$length
, он проверяет, содержит ли текущий буфер данных уже длину$length
. Если нет, он продолжает ожидать данных до тех пор, пока длина буфера данных не станет не меньше$length
. - После достаточной длины буфера данных, Workerman извлекает фрагмент данных длиной
$length
(т.е. фрагментация) и вызывает методdecode
протокола для разбора фрагмента. Результат разбора сохраняется в переменной$data
. - После разбора данных, Workerman передает данные
$data
в качестве второго аргумента обратного вызоваonMessage($connection, $data)
для обработки в бизнес-логике. Теперь бизнес-логика может использовать переменную$data
, чтобы получить полные и разобранные данные, отправленные клиентом. - Когда бизнес-логике требуется отправить данные клиенту, вызов
$connection->send($buffer)
будет автоматически использовать методencode
протокола для упаковки$buffer
перед отправкой клиенту.
Конкретная реализация
Реализация в файле MyApp/Protocols/JsonNL.php
namespace Protocols;
class JsonNL
{
/**
* Проверка целостности пакета
* Если можно получить длину пакета в $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;
}
// Если символ переноса строки имеется, вернуть длину текущего пакета (включая символ переноса строки)
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);
}
}
Теперь протокол JsonNL реализован и может быть использован в проекте MyApp. Пример использования приведен ниже.
Файл: 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 - это данные, отправленные клиентом, которые уже были обработаны методом JsonNL::decode
echo $data;
// Данные, отправленные через $connection->send, автоматически упаковываются методом JsonNL::encode и затем отправляются клиенту
$connection->send(array('code'=>0, 'msg'=>'ok'));
};
Worker::runAll();