chore: 重写初始提交(清空历史,整理后全量提交)

This commit is contained in:
ywxapp
2026-08-16 16:54:14 +08:00
commit 6c1a106bc1
1808 changed files with 238144 additions and 0 deletions
+176
View File
@@ -0,0 +1,176 @@
<?php
// +----------------------------------------------------------------------
// | YwxApp [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2026-2036 http://ywxapp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Author: ywxapp<admin@ywxapp.cn>
// +----------------------------------------------------------------------
namespace addon\mqttbroker\protocol;
/**
* MQTT 协议编解码器(Workerman 标准协议类)
*
* 遵循 Workerman 自定义协议规范(https://www.workerman.net/doc/workerman/protocols/how-protocols.html):
* - 必须实现 input() / decode() / encode() 三个静态方法;
* - 无需强制继承 ProtocolInterface,只要类包含这三个静态方法即可;
* - 通过 $worker->protocol = Mqtt::class 绑定,Workerman 在收到数据时自动:
* input() 判定一个完整包的长度(>0 包长 / 0 继续等待 / -1 协议错误断开)
* decode() 解析完整包,结果作为 $data 传入 onMessage
* 业务侧 $connection->send($data) 时自动调用 encode() 打包。
*
* 解码后数据结构(供 Broker 使用):
* ['cmd' => int, 'flags' => int, 'body' => string]
* - cmd :MQTT 控制报文类型(高 4 位固定头)
* - flags:固定头低 4 位(PUBLISH 时为 dup/qos/retain
* - body :变长头 + 负载(已剔除固定头与剩余长度字段)
*
* 编码时同样接收上述数组,也可直接传原始字符串(透传)。
*/
class Mqtt
{
/* ---------- MQTT 控制报文类型 ---------- */
const CMD_CONNECT = 1;
const CMD_CONNACK = 2;
const CMD_PUBLISH = 3;
const CMD_PUBACK = 4;
const CMD_PUBREC = 5;
const CMD_PUBREL = 6;
const CMD_PUBCOMP = 7;
const CMD_SUBSCRIBE = 8;
const CMD_SUBACK = 9;
const CMD_UNSUBSCRIBE = 10;
const CMD_UNSUBACK = 11;
const CMD_PINGREQ = 12;
const CMD_PINGRESP = 13;
const CMD_DISCONNECT = 14;
const CMD_AUTH = 15;
/**
* 检查包的完整性,返回当前包在 buffer 中的总长度
* @param string $buffer 当前收到的数据缓冲
* @param mixed $connection Workerman 传入的连接对象(此处未使用)
* @return int >0 完整包长度 | 0 数据不足需等待 | -1 协议错误(断开连接)
*/
public static function input(string $buffer, $connection = null): int
{
$len = strlen($buffer);
if ($len < 2) {
return 0; // 至少 1 字节固定头 + 1 字节长度
}
$first = ord($buffer[0]);
$cmd = ($first >> 4) & 0x0F;
if ($cmd < 1 || $cmd > 15) {
return -1; // 非法控制报文类型
}
// 解码剩余长度(变长整数,最多 4 字节)
$value = 0;
$multiplier = 1;
$i = 0;
$pos = 1;
do {
if ($pos + $i >= $len) {
return 0; // 长度字节尚未收全
}
$byte = ord($buffer[$pos + $i]);
$value += ($byte & 0x7F) * $multiplier;
$multiplier *= 128;
$i++;
if ($i > 4) {
return -1; // 剩余长度字段超过 4 字节,协议错误
}
} while (($byte & 0x80) !== 0);
$headerLen = 1 + $i; // 固定头(1) + 剩余长度字段(i)
if ($len - $headerLen < $value) {
return 0; // 包体尚未收全
}
return $headerLen + $value;
}
/**
* 解包:把完整包解析为结构化数组,作为 onMessage 的 $data
* @param string $buffer 一个完整 MQTT 报文(含固定头)
* @param mixed $connection
* @return array ['cmd'=>int,'flags'=>int,'body'=>string]
*/
public static function decode(string $buffer, $connection = null): array
{
if ($buffer === '') {
return ['cmd' => 0, 'flags' => 0, 'body' => ''];
}
$first = ord($buffer[0]);
$cmd = ($first >> 4) & 0x0F;
$flags = $first & 0x0F;
// 解码剩余长度,定位包体起始
$value = 0;
$multiplier = 1;
$i = 0;
$pos = 1;
do {
$byte = ord($buffer[$pos + $i]);
$value += ($byte & 0x7F) * $multiplier;
$multiplier *= 128;
$i++;
} while (($byte & 0x80) !== 0);
$body = substr($buffer, $pos + $i, $value);
return ['cmd' => $cmd, 'flags' => $flags, 'body' => $body];
}
/**
* 打包:把结构化数组编码为可发送字节流
* @param array|string $data 编码结构 ['cmd'=>int,'flags'=>int,'body'=>string] 或原始字符串
* @param mixed $connection
* @return string
*/
public static function encode($data, $connection = null): string
{
if (is_string($data)) {
return $data; // 兼容直接发送原始字节
}
$cmd = (int) ($data['cmd'] ?? 0);
$flags = (int) ($data['flags'] ?? 0);
$body = (string) ($data['body'] ?? '');
$fh = chr(($cmd << 4) | ($flags & 0x0F));
return $fh . self::encodeRemainingLength(strlen($body)) . $body;
}
/**
* 编码 MQTT 剩余长度(变长整数,最大 4 字节)
*/
public static function encodeRemainingLength(int $len): string
{
$bytes = '';
do {
$byte = $len % 128;
$len = intdiv($len, 128);
if ($len > 0) {
$byte |= 0x80;
}
$bytes .= chr($byte);
} while ($len > 0);
return $bytes;
}
/**
* 解码 MQTT 剩余长度,返回 [值, 占用字节数]
*/
public static function decodeRemainingLength(string $buffer, int $pos = 0): array
{
$value = 0;
$multiplier = 1;
$i = 0;
do {
$byte = ord($buffer[$pos + $i]);
$value += ($byte & 0x7F) * $multiplier;
$multiplier *= 128;
$i++;
if ($i > 4) {
break;
}
} while (($byte & 0x80) !== 0);
return [$value, $pos + $i];
}
}