alpari/binary-protocol

PHP的二进制协议编解码器

1.0.1 2019-08-08 14:03 UTC

This package is auto-updated.

Last update: 2024-09-09 01:48:22 UTC


README

alpari/binary-protocol 是一个PHP库,提供了处理二进制协议(如FCGI、ApacheKafka等)的API。应用程序可以使用这个库来描述二进制数据,并通过高级API进行操作,无需手动进行二进制数据的打包/解包。

安装

alpari/binary-protocol 可以通过composer安装。要下载此库,请输入以下命令:

composer require alpari/binary-protocol

类型接口API

类型接口API描述了数据在低级表示,例如Int8、Int16、String等...

<?php

namespace Alpari\BinaryProtocol;

use Alpari\BinaryProtocol\Stream\StreamInterface;

/**
 * Declares the type that can be packed/unpacked from/to binary stream
 */
interface TypeInterface
{
    /**
     * Reads a value from the stream
     *
     * @param StreamInterface $stream Instance of stream to read value from
     * @param string          $path   Path to the item to simplify debug of complex hierarchical structures
     *
     * @return mixed
     */
    public function read(StreamInterface $stream, string $path);

    /**
     * Writes the value to the given stream
     *
     * @param mixed           $value  Value to write
     * @param StreamInterface $stream Instance of stream to write to
     * @param string          $path   Path to the item to simplify debug of complex hierarchical structures
     *
     * @return void
     */
    public function write($value, StreamInterface $stream, string $path): void;

    /**
     * Calculates the size in bytes of single item for given value
     *
     * @param mixed  $value Value to write
     * @param string $path  Path to the item to simplify debug of complex hierarchical structures
     *
     * @return int
     */
    public function sizeOf($value = null, string $path =''): int;
}

每个数据类型都应该作为一个单独的类添加,实现TypeInterface合约,并包含将数据打包/解包到/从StreamInterface的逻辑。

如你所见,有两个主要方法:read()write(),用于读取和写入二进制数据到流中。

sizeOf()方法用于计算数据字节数。对于简单数据类型,它将返回一个常量值;对于更复杂的结构(如数组或对象),它应该能够计算此类字段的尺寸。

getFormat()方法在接口中声明,但目前在未使用。它可以在以后用于固定大小的数组或结构,以加快解析速度,例如Int16数组。

方案定义接口API

SchemeDefinitionInterface帮助在应用程序中使用复杂的二进制数据结构。二进制协议中的每个复杂类型都应该作为一个单独的DTO(具有属性的纯PHP对象)实现SchemeDefinitionInterface及其方法getDefinition()

<?php

/**
 * SchemeDefinitionInterface represents a class that holds definition of his scheme
 */
interface SchemeDefinitionInterface
{
    /**
     * Returns the definition of class scheme as an associative array if form of [property => type]
     */
    public static function getDefinition(): array;
}

以下是一个用于Kafka客户端的二进制包定义的示例

<?php

use Alpari\BinaryProtocol\Type\Int16BE as Int16;
use Alpari\BinaryProtocol\SchemeDefinitionInterface;

/**
 * ApiVersions response data
 */
class ApiVersionsResponseMetadata implements SchemeDefinitionInterface
{
    /**
     * Numerical code of API
     *
     * @var integer
     */
    public $apiKey;

    /**
     * Minimum supported version.
     *
     * @var integer
     */
    public $minVersion;

    /**
     * Maximum supported version.
     *
     * @var integer
     */
    public $maxVersion;

    /**
     * @inheritdoc
     */
    public static function getDefinition(): array
    {
        return [
            'apiKey'     => [Int16::class],
            'minVersion' => [Int16::class],
            'maxVersion' => [Int16::class],
        ];
    }
}

要将定义添加到现有的类中,只需将“property” => “type”映射作为方案定义添加即可。请注意,在getDefinition()方法中,每个属性类型都被声明为一个数组。此格式用于定义复杂类型的额外参数。

ArrayOf类型

ArrayOf类型用于声明重复的二进制项序列(整数、字符串或对象)。它有几个选项可以作为一个关联数组应用。

 return [
     'partitions' => [ArrayOf::class => [
        'item' => [Int32::class]
    ]]
];

ArrayOf可用的选项有

  • item (必需) 数组中使用的单个项类型的定义,例如 [Int32::class]
  • size (可选) 大小类型的定义,你可以使用 [VarInt::class][Int64::class] 或其他
  • key (可选) 对象属性名,该属性将用作数组的关联键
  • nullable (可选) 布尔标志,用于启用 null 值,编码为大小 = -1

BinaryString类型

BinaryString类是对任何字符串或包含二进制数据的原始缓冲区的通用表示。通常它被编码为缓冲区长度字段和字节序列作为数据。

BinaryString可用的选项有

  • envelope (可选) 声明存储在二进制包中的嵌套数据类型
  • size (可选) 大小类型的定义,你可以使用 [VarInt::class][Int64::class] 或其他
  • nullable (可选) 布尔标志,用于启用 null 值,编码为大小 = -1

envelope 功能在某些协议中使用,当低级协议仅定义一些缓冲区,而顶级协议从临时缓冲区中提取特定包时。例如,TCP数据包在IP上。

默认情况下,BinaryString 使用大端编码的 Int16 作为大小,如果您需要更多的数据,只需相应地配置 size 选项。

SchemeType 类型

SchemeType 类表示可以映射到 PHP 对象实例的复杂结构。它有以下选项

  • class (必填) 包含此结构的对应类的完全限定名称的字符串
  • scheme (可选) 定义每个项目类型作为键 => 定义。键用于作为此对象的属性名称。

注意:如果您的 SomeClass 类实现了 SchemeDefinitionInterface,则您可以在方案中简单引用它,如 [SomeClass::class]

BinaryProtocol 类使用方法

此库引入了 BinaryProtocol 类作为顶级二进制编码器/解码器 API。要读取数据,请准备一个合适的数据流,通过实现 StreamInterface 合同或直接利用内置的 StringStream 类将内容写入临时缓冲区。然后只需请求协议在其中读取或写入内容即可。

<?php

use Alpari\BinaryProtocol\BinaryProtocol;
use Alpari\BinaryProtocol\Type\Int32;

$protocol = new BinaryProtocol();
$protocol->write(-10000, [Int32::class], $stream);
$value = $protocol->read([Int32::class], $stream);
var_dump($value);

延迟评估字段

在某些情况下,二进制协议包含依赖于现有数据的字段,例如:数据包长度、CRC、延迟评估或编码字段。这些字段应在写入流之前进行延迟计算。要使用延迟评估,只需将此类字段的值声明为 Closure 实例。

以下是一个为 Kafka 的 Record 类计算 length 字段的示例

<?php

use Alpari\BinaryProtocol\BinaryProtocolInterface;
use Alpari\BinaryProtocol\Type\SchemeType;
use Alpari\BinaryProtocol\SchemeDefinitionInterface;

class Record implements SchemeDefinitionInterface
{
    /**
     * Record constructor
     */
    public function __construct(
        string $value,
        ?string $key = null,
        array $headers = [],
        int $attributes = 0,
        int $timestampDelta = 0,
        int $offsetDelta = 0
    ) {
        $this->value          = $value;
        $this->key            = $key;
        $this->headers        = $headers;
        $this->attributes     = $attributes;
        $this->timestampDelta = $timestampDelta;
        $this->offsetDelta    = $offsetDelta;

        // Length field uses delayed evaluation to allow size calculation
        $this->length = function (BinaryProtocolInterface $scheme, string $path) {
            // To calculate full length we use scheme without `length` field
            $recordSchemeDefinition = self::getDefinition();
            unset($recordSchemeDefinition['length']);
            $recordSchemeType = [SchemeType::class => ['class' => self::class, 'scheme' => $recordSchemeDefinition]];
            // Redefine our lazy field with calculated value
            $this->length = $size = $scheme->sizeOf($this, $recordSchemeType, $path);
            return $size;
        };
    }

    /**
     * @inheritdoc
     */
    public static function getDefinition(): array
    {
        return [
            'length'         => [VarIntZigZag::class],
            'attributes'     => [Int8::class],
            'timestampDelta' => [VarLongZigZag::class],
            'offsetDelta'    => [VarIntZigZag::class],
            'key'            => [BinaryString::class => [
                'size'     => [VarIntZigZag::class],
                'nullable' => true
            ]],
            'value'          => [BinaryString::class => ['size' => [VarIntZigZag::class]]],
            'headers'        => [ArrayOf::class => [
                'key'  => 'key',
                'item' => [Header::class],
                'size' => [VarInt::class]
            ]]
        ];
    }
}