MQTT 数据包格式简介(V5.0)
MQTT 数据包格式简介(V5.0)
前言
作为一种IOT领域的网络协议,MQTT需要在实现功能的前提下尽可能的精炼协议的长度,因此它的数据包格式设计的很巧妙。我根据MQTT规范 MQTT Specifications MQTT Version 5.0,做了一些翻译和总结来进行学习和归纳。
概览
MQTT的数据包格式可以划分为以下三部分。
数据类型 | 出现频率 |
---|---|
固定头(Fixed Header) | 所有类型 |
可变头(Variable Header) | 部分类型 |
载荷(Payload) | 部分类型 |
固定头(Fixed Header)
bit | 7-4 | 3-0 |
---|---|---|
Byte1 | 包类型 | 每种包类型的特殊标志位 |
Byte2... | 剩余长度(第7-4bit) | 剩余长度(第3-0bit) |
- 包类型由4个bit组成,有16个值,各值含义如下
名字 | 值 | 传输方向 | 描述 |
---|---|---|---|
Reserved | 0 | 禁止 | 保留 |
CONNECT | 1 | 客户端->服务端 | 连接请求 |
CONNACK | 2 | 服务端->客户端 | 连接确认 |
PUBLISH | 3 | 客户端->服务端 或 服务端->客户端 | 发布消息 |
PUBACK | 4 | 客户端->服务端 或 服务端->客户端 | 发布确认(QoS 1) |
PUBREC | 5 | 客户端->服务端 或 服务端->客户端 | 发布接收确认(QoS 2 part1) |
PUBREL | 6 | 客户端->服务端 或 服务端->客户端 | 发布释放(QoS 2 part2) |
PUBCOMP | 7 | 客户端->服务端 或 服务端->客户端 | 发布完成(QoS 2 part3) |
SUBSCRIBE | 8 | 客户端->服务端 | 请求订阅 |
SUBACK | 9 | 服务端->客户端 | 订阅确认 |
UNSUBSCRIBE | 10 | 客户端->服务端 | 取消订阅请求 |
UNSUBACK | 11 | 服务端->客户端 | 取消订阅确认 |
PINGREQ | 12 | 客户端->服务端 | ping请求 |
PINGRESP | 13 | 服务端->客户端 | ping响应 |
DISCONNECT | 14 | 客户端->服务端 或 服务端->客户端 | 连接断开通知 |
AUTH | 15 | 客户端->服务端 或 服务端->客户端 | 认证交换 |
- 每种包类型的特殊标志位由4个bit组成,有16个值,下面列出涉及的包类型的以及保留值特殊的包类型,未提及的包类型所有特殊标志位均要设置为0。
包类型 | 是否使用 | bit3 | bit2 | bit1 | bit0 |
---|---|---|---|---|---|
PUBLISH | 在v5.0版本中使用 | DUP | Qos(bit1) | Qos(bit0) | RETAIN |
PUBREL | 保留 | 0 | 0 | 1 | 0 |
SUBSCRIBE | 保留 | 0 | 0 | 1 | 0 |
UNSUBSCRIBE | 保留 | 0 | 0 | 1 | 0 |
DUP = PUBLISH包是否重复
QoS = PUBLISH服务质量
RETAIN = PUBLISH是否在服务器上保存
- 从Byte2开始为剩余长度,它是一个可变字节数,用于表示这个MQTT数据包中还有多少个字节(不包括它本身),即可变头加载荷的长度。通过这个定义可以得出,MQTT数据包的总长度 = 固定头的长度 + 剩余长度。
可变头(Variable Header)
可变头具体含义取决于包类型,但在MQTT 5.0协议中,大部分包类型都含有两部分,包序号(Packet Identifier)和特性(Properties)。
包序号(Packet Identifier)
包序号为双字节数,使用了包序号的包类型有。
包类型 | 是否含有包序号 |
---|---|
CONNECT | 否 |
CONNACK | 否 |
PUBLISH | 是(当 QoS > 0 时) |
PUBACK | 是 |
PUBREC | 是 |
PUBCOMP | 是 |
SUBSCRIBE | 是 |
UNSUBSCRIBE | 是 |
PINGREQ | 否 |
PINGRESP | 否 |
DISCONNECT | 否 |
AUTH | 否 |
包序号的使用有如下规则:
- 如果QoS为0的PUBLISH包不允许包含包序号
- 每当客户端发送一个新的SUBSCRIBE、UNSUBSCRIBE或PUBLISH(当 QoS > 0 时)包,它的包序号必须是一个未被使用的非0序号,同样地,服务端发送PUBLISH(当 QoS > 0 时)包时也要遵循此规则。
- 如果发送方接受到了一个包相关联的确认包(包序号一致),那么这个包的包序号可以被重用。这里的相关联指 PUBLISH(QoS1)<->PUBACK、PUBLISH(QoS2)<->PUBCOMP(返回码大于等于128)和PUBREC(返回码大于等于128)、SUBSCRIBE<->SUBACK、SUBSCRIBE<->UNSUBACK。
- 一次会话的包序号集合由发起方来维护,并且客户端和服务端分开维护的,这意味着客户端发送的包序号有可能与服务端发送的包序号重复。但一个发起方多条指令任何时候不能使用同一个包序号。
特性(Properties)
特性是在MQTT V5.0协议中新增的重要特性。主要是为了解决V3版本协议扩展性不足的问题。这个特性在协议中扮演的角色类似HTTP协议中的HTTP头。特性用途的介绍可以看这篇博客 What are MQTT User Properties? – MQTT 5 Essentials Part 6
目前几乎所有的包类型(除了PINGREQ、PINGRESP)都包含此区域,额外的,CONNECT还有一些可选的特性位于载荷的遗言特性(Will Properties)区域。
特性区域起始字节为可变字节数的特性长度(Property Length),它表示它之后特性区域的长度,即不包括它本身,如果这个包不携带特性,那么它需要被设置成0。
特性长度后就是有效特性区域,这个区域可以多个特性,每个特性都是由一个为可变字节数的特性序号和特性值组成,多个特性之间的排列顺序并没有特别的要求。
V5.0协议预设了许多特性在各种包类型中,其中38(0x26)允许用户自定义自己需要的内容
序号 | 名词 (用途) | 数据类型 | 包类型 / 遗言特性 |
---|---|---|---|
1 0x01 | Payload Format Indicator | Byte | PUBLISH, Will Properties |
2 0x02 | Message Expiry Interval | Four Byte Integer | PUBLISH, Will Properties |
3 0x03 | Content Type | UTF-8 Encoded String | PUBLISH, Will Properties |
8 0x08 | Response Topic | UTF-8 Encoded String | PUBLISH, Will Properties |
9 0x09 | Correlation Data | Binary Data | PUBLISH, Will Properties |
11 0x0B | Subscription Identifier | Variable Byte Integer | PUBLISH, SUBSCRIBE |
17 0x11 | Session Expiry Interval | Four Byte Integer | CONNECT, CONNACK, DISCONNECT |
18 0x12 | Assigned Client Identifier | UTF-8 Encoded String | CONNACK |
19 0x13 | Server Keep Alive | Two Byte Integer | CONNACK |
21 0x15 | Authentication Method | UTF-8 Encoded String | CONNECT, CONNACK, AUTH |
22 0x16 | Authentication Data | Binary Data | CONNECT, CONNACK, AUTH |
23 0x17 | Request Problem Information | Byte | CONNECT |
24 0x18 | Will Delay Interval | Four Byte Integer | Will Properties |
25 0x19 | Request Response Information | Byte | CONNECT |
26 0x1A | Response Information | UTF-8 Encoded String | CONNACK |
28 0x1C | Server Reference | UTF-8 Encoded String | CONNACK, DISCONNECT |
31 0x1F | Reason String | UTF-8 Encoded String | CONNACK, PUBACK, PUBREC, PUBREL, PUBCOMP, SUBACK, UNSUBACK, DISCONNECT, AUTH |
33 0x21 | Receive Maximum | Two Byte Integer | CONNECT, CONNACK |
34 0x22 | Topic Alias Maximum | Two Byte Integer | CONNECT, CONNACK |
35 0x23 | Topic Alias | Two Byte Integer | PUBLISH |
36 0x24 | Maximum QoS | Byte | CONNACK |
37 0x25 | Retain Available | Byte | CONNACK |
38 0x26 | User Property | UTF-8 String Pair | CONNECT, CONNACK, PUBLISH, Will Properties, PUBACK, PUBREC, PUBREL, PUBCOMP, SUBSCRIBE, SUBACK, UNSUBSCRIBE, UNSUBACK, DISCONNECT, AUTH |
39 0x27 | Maximum Packet Size | Four Byte Integer | CONNECT, CONNACK |
40 0x28 | Wildcard Subscription Available | Byte | CONNACK |
41 0x29 | Subscription Identifier Available | Byte | CONNACK |
42 0x2A | Shared Subscription Available | Byte | CONNACK |
载荷(Payload)
部分MQTT数据包的最后一部分就是载荷,在PUBLISH包中,这个是应用信息。
包类型 | 是否需要 |
---|---|
CONNECT | 需要 |
CONNACK | 不需要 |
PUBLISH | 可选 |
PUBACK | 不需要 |
PUBREC | 不需要 |
PUBREL | 不需要 |
PUBCOMP | 不需要 |
SUBSCRIBE | 需要 |
SUBACK | 需要 |
UNSUBSCRIBE | 需要 |
UNSUBACK | 需要 |
PINGREQ | 不需要 |
PINGRESP | 不需要 |
DISCONNECT | 不需要 |
AUTH | 不需要 |
原因码(Reason Code)
原因码是一个字节的无符号值用于表示操作的结果。一般表示成功的原因码为0,不大于0x80也表示成功,但会表示操作成功的不同结果,而大于等于0x80表示失败。
CONNACK,PUBACK,PUBREC,PUBREL,PUBCOMP,DISCONNECT,AUTH包中有一个单独的原因码在可变头中,SUBACK和UNSUBACK包中则是在载荷中有一连串的1个或多个原因码。
原因码 | 名称 | 包类型 |
---|---|---|
0 0x00 | Success | CONNACK, PUBACK, PUBREC, PUBREL, PUBCOMP, UNSUBACK, AUTH |
0 0x00 | Normal disconnection | DISCONNECT |
0 0x00 | Granted QoS 0 | SUBACK |
1 0x01 | Granted QoS 1 | SUBACK |
2 0x02 | Granted QoS 2 | SUBACK |
4 0x04 | Disconnect with Will Message | DISCONNECT |
16 0x10 | No matching subscribers | PUBACK, PUBREC |
17 0x11 | No subscription existed | UNSUBACK |
24 0x18 | Continue authentication | AUTH |
25 0x19 | Re-authenticate | AUTH |
128 0x80 | Unspecified error | CONNACK, PUBACK, PUBREC, SUBACK, UNSUBACK, DISCONNECT |
129 0x81 | Malformed Packet | CONNACK, DISCONNECT |
130 0x82 | Protocol Error | CONNACK, DISCONNECT |
131 0x83 | Implementation specific error | CONNACK, PUBACK, PUBREC, SUBACK, UNSUBACK, DISCONNECT |
132 0x84 | Unsupported Protocol Version | CONNACK |
133 0x85 | Client Identifier not valid | CONNACK |
134 0x86 | Bad User Name or Password | CONNACK |
135 0x87 | Not authorized | CONNACK, PUBACK, PUBREC, SUBACK, UNSUBACK, DISCONNECT |
136 0x88 | Server unavailable | CONNACK |
137 0x89 | Server busy | CONNACK, DISCONNECT |
138 0x8A | Banned | CONNACK |
139 0x8B | Server shutting down | DISCONNECT |
140 0x8C | Bad authentication method | CONNACK, DISCONNECT |
141 0x8D | Keep Alive timeout | DISCONNECT |
142 0x8E | Session taken over | DISCONNECT |
143 0x8F | Topic Filter invalid | SUBACK, UNSUBACK, DISCONNECT |
144 0x90 | Topic Name invalid | CONNACK, PUBACK, PUBREC, DISCONNECT |
145 0x91 | Packet Identifier in use | PUBACK, PUBREC, SUBACK, UNSUBACK |
146 0x92 | Packet Identifier not found | PUBREL, PUBCOMP |
147 0x93 | Receive Maximum exceeded | DISCONNECT |
148 0x94 | Topic Alias invalid | DISCONNECT |
149 0x95 | Packet too large | CONNACK, DISCONNECT |
150 0x96 | Message rate too high | DISCONNECT |
151 0x97 | Quota exceeded | CONNACK, PUBACK, PUBREC, SUBACK, DISCONNECT |
152 0x98 | Administrative action | DISCONNECT |
153 0x99 | Payload format invalid | CONNACK, PUBACK, PUBREC, DISCONNECT |
154 0x9A | Retain not supported | CONNACK, DISCONNECT |
155 0x9B | QoS not supported | CONNACK, DISCONNECT |
156 0x9C | Use another server | CONNACK, DISCONNECT |
157 0x9D | Server moved | CONNACK, DISCONNECT |
158 0x9E | Shared Subscriptions not supported | SUBACK, DISCONNECT |
159 0x9F | Connection rate exceeded | CONNACK, DISCONNECT |
160 0xA0 | Maximum connect time | DISCONNECT |
161 0xA1 | Subscription Identifiers not supported | SUBACK, DISCONNECT |
162 0xA2 | Wildcard Subscriptions not supported | SUBACK, DISCONNECT |
名词解释
双字节(Two Byte Integer)、四字节数(Four Byte Integer)
MQTT中使用的多字节数均为大端序
可变字节数 (Variable Byte Integer)
可变字节是一种编码方式,将每个字节的最高位定义为“延续位”(continuation bit),剩下7位存储数据。“延续位”为1说明后一个字节也属于这个可变字节数。
例如,127可以用一个字节表示为0x7F。而128由于超过了7个位能表达的最大值,因此它需要两个字节表示。第1个字节的最高位“延续位”为1,剩下的7位存储128的0-7位,即全部为0,因此第一个字节为0x80,而第二个字节后不需要更多字节了,所以它的最高位“延续位”为0,剩下的7位存储128的8-14位,也就是1,所以第二个字节位0x01。综上所述,128使用可变字节数来表示,就是0x80,0x01。
目前,可变字节数最多可以由4个字节组成,那么根据规则,可以给出1-4个字节数的取值范围。
字节数 | 起始 | 终止 |
---|---|---|
1 | 0 (0x00) | 127 (0x7F) |
2 | 128 (0x80,0x01) | 16383 (0xFF,0x7F) |
3 | 16,384 (0x80, 0x80, 0x01) | 2,097,151 (0xFF, 0xFF, 0x7F) |
4 | 2,097,152 (0x80, 0x80, 0x80, 0x01) | 268,435,455 (0xFF, 0xFF, 0xFF, 0x7F) |
MQTT协议中给出了非规范性建议的编解码实现的伪代码如下。
// 编码
// X 为待编码数
// encodeByte 为当前正在编码的字节
do
encodeByte = X MOD 128
X = X DIV 128
if (X > 0)
encodeByte = encodeByte OR 128
endif
'output' encodeByte
while (X > 0)
// 解码
// value 为解码后得到的数
multiplier = 1
value = 0
do
encodedByte = 'next byte from stream'
value += (encodedByte AND 127) * multiplier
if (multiplier > 128*128*128)
throw Error(Malformed Variable Byte Integer)
multiplier *= 128
while ((encodedByte AND 128) != 0)
二进制数据 (Binary Data)
二进制数据起始为双字节数表示长度(0 ~ 65536),后面跟着这个长度的字节。
UTF-8 编码字符串 (UTF-8 Encoded String)
在有效数据前有一个双字节数用于表示字符串长度(0 ~ 65535),有效数据按照UTF-8规则编码,协议中要求不能包含控制字符(U+0001..U+001F、U+007F..U+009F)、不能包含空字符(U+0000)等,这里不在赘述,具体请查看这里。
以字符串“A𪛔(U+2A6D4)”举例,
字节 | 值 | 描述 |
---|---|---|
Byte1 | 0 | 字符串长度高字节(MSB) |
Byte2 | 5 | 字符串长度低字节(LSB) |
Byte3 | 'A'(0x41) | |
Byte4 | 0xF0 | |
Byte5 | 0xAA | |
Byte6 | 0x9B | |
Byte7 | 0x94 |
UTF-8 编码字符串对 (UTF-8 String Pair)
两个UTF-8 编码字符串组成的键-值对。
参考来源
[1] MQTT Specifications MQTT Version 5.0: https://docs.oasis-open.org/mqtt/mqtt/v5.0/mqtt-v5.0.html