Как настроить протокол
На самом деле создание собственного протокола — это довольно простая задача. Простой протокол обычно состоит из двух частей:
- Идентификатор, который разделяет границы данных
- Определение формата данных
Пример
Определение протокола
Предположим, что идентификатор, разделяющий границы данных, — это символ новой строки "\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. - После получения значения
$lengthфреймворк Workerman проверяет, получены ли данные длины$lengthв текущем буфере данных. Если нет, он будет продолжать ждать данные, пока длина данных в буфере не станет не меньше$length. - Как только длина данных в буфере достаточна, workerman извлечет из буфера данные длины
$length(т.е. разобьет на пакеты) и вызовет метод протоколаdecodeдля распаковки, в результате чего полученные данные будут$data. - После распаковки workerman передаст данные
$dataв виде обратного вызоваonMessage($connection, $data)бизнес-логике, которая в onMessage сможет использовать переменную$dataдля доступа к полностью распакованным данным от клиента. - Когда в бизнес-логике необходимо отправить данные клиенту с помощью вызова метода
$connection->send($buffer), workerman автоматически использует метод протоколаencodeдля упаковки$bufferперед отправкой клиенту.
Конкретная реализация
Реализация MyApp/Protocols/JsonNL.php
namespace Protocols;
class JsonNL
{
/**
* Проверка целостности пакета
* Если можно получить длину пакета, возвращает длину пакета в буфере, иначе возвращает 0 и продолжает ждать данных
* Если с протоколом возникли проблемы, можно вернуть -1, в этом случае текущее подключение клиента будет разорвано
* @param string $buffer
* @return int
*/
public static function input($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();
...
Подсказка
Workerman попытается загрузить протоколы из пространства именProtocols, например,new Worker('JsonNL://0.0.0.0:1234')попытается загрузить протоколProtocols\JsonNL.
Если появляется ошибкаClass 'Protocols\JsonNL' not found, обратитесь к Автоматической загрузке для реализации автоматической загрузки.
Описание интерфейса протокола
Протоколы, разработанные в Workerman, должны реализовывать три статических метода: input, encode и decode. Описание интерфейса протокола приведено в Workerman/Protocols/ProtocolInterface.php и определено следующим образом:
namespace Workerman\Protocols;
use \Workerman\Connection\ConnectionInterface;
/**
* Протокол интерфейс
* @author walkor <walkor@workerman.net>
*/
interface ProtocolInterface
{
/**
* Используется для разбивки пакета в полученном recv_buffer
*
* Если можно получить длину запрашиваемого пакета в $recv_buffer, вернуть общую длину пакета
* В противном случае вернуть 0, что означает, что требуется больше данных для получения текущей длины запрашиваемого пакета
* Если вернуть -1, это будет означать ошибочный запрос, в этом случае соединение будет разорвано
*
* @param ConnectionInterface $connection
* @param string $recv_buffer
* @return int
*/
public static function input($recv_buffer, ConnectionInterface $connection);
/**
* Используется для распаковки запроса
*
* Если значение, возвращенное input, больше 0, и Workerman получил достаточно данных, автоматически вызывается decode
* Затем вызывается обратный вызов onMessage, и декодированные данные передаются второму параметру обратного вызова onMessage
* Это означает, что при получении полного запроса клиента decode будет вызываться автоматически, без необходимости вручную вызывать в коде сам бизнес
* @param ConnectionInterface $connection
* @param string $recv_buffer
*/
public static function decode($recv_buffer, ConnectionInterface $connection);
/**
* Используется для упаковки запроса
*
* Когда необходимо отправить данные клиенту, т.е. вызывается $connection->send($data);
* $data автоматически упаковывается с помощью encode, изменяя его на формат данных, соответствующий протоколу, а затем отправляется клиенту
* Это означает, что данные, отправляемые клиенту, автоматически упаковываются в encode, без необходимости вручную вызывать в коде сам бизнес
* @param ConnectionInterface $connection
* @param mixed $data
*/
public static function encode($data, ConnectionInterface $connection);
}
Примечание:
В Workerman нет строгого требования, чтобы классы протоколов обязательно реализовывали ProtocolInterface. На самом деле, классы протоколов могут быть реализованы, если они просто содержат три статических метода: input, encode и decode.