alpari / binary-protocol
PHP的二进制协议编解码器
Requires
- php: ~7.1
- ext-json: *
Requires (Dev)
- phpunit/phpunit: ^7.5
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] ]] ]; } }