MQTT协议简析
1 概述
MQTT(Message Queuing Telemetry Transport,消息队列遥测传输)是一个C/S架构的发布/订阅模式消息传输协议,最早在1999年由IBM的Andy Stanford-Clark博士和Arcom公司的ArlenNipper博士提出,本文的MQTT协议主要基于MQTT3.1.1。
2 报文结构
MQTT报文结构主要分为固定报头、可变报头、有效载荷3个部分,具体结构如下表所示:
说明 | 字节数 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|
固定报头 | 1 | Message Type | Dup Flag | QoS Level | Retain Flag | ||||
1~4 | 剩余长度,最小1字节最大4字节 | ||||||||
可变报头 | 1 | 消息标识符最高有效位MSB | |||||||
1 | 消息标识符最低有效位LSB | ||||||||
有效载荷 | N | Payload |
2.1 固定报头(Fixed Header)
每个MQTT报文都包含至少2个字节的固定报头,包括消息类型(Message Type)、重发标识(Dup Flag)、质量等级(QoS)、保持标识(Retain Flag)、剩余长度等字段。
2.1.1 消息类型
MQTT第1个字节的前4位表示消息类型,具体的消息类型定义如下表所示:
名字 | 值 | 报文流动方向 | 描述 |
---|---|---|---|
Reserved | 0 | 禁止 | 保留值 |
CONNECT | 1 | 客户端到服务端 | 客户端请求连接服务端 |
CONNACK | 2 | 服务端到客户端 | 连接报文确认 |
PUBLISH | 3 | 两个方向都允许 | 发布消息 |
PUBACK | 4 | 两个方向都允许 | QoS 1消息发布收到确认 |
PUBREC | 5 | 两个方向都允许 | 发布收到(保证交付第一步) |
PUBREL | 6 | 两个方向都允许 | 发布释放(保证交付第二步) |
PUBCOMP | 7 | 两个方向都允许 | QoS 2消息发布完成(保证交互第三步) |
SUBSCRIBE | 8 | 客户端到服务端 | 客户端订阅请求 |
SUBACK | 9 | 服务端到客户端 | 订阅请求报文确认 |
UNSUBSCRIBE | 10 | 客户端到服务端 | 客户端取消订阅请求 |
UNSUBACK | 11 | 服务端到客户端 | 取消订阅报文确认 |
PINGREQ | 12 | 客户端到服务端 | 心跳请求 |
PINGRESP | 13 | 服务端到客户端 | 心跳响应 |
DISCONNECT | 14 | 客户端到服务端 | 客户端断开连接 |
Reserved | 15 | 禁止 | 保留值 |
2.1.2 标识符
固定报头第一个字节的后4位为标识符,对于PUBLISH类型的消息来说,分别表示DUP、QoS、RETAIN3个标识,对于其他类型的消息来说,这4位为保留位,具体的值应该符合下表的定义,如果收到非法的标识符,协议的双方应该选择关闭网络连接。
控制报文 | 固定报头标志 | Bit 3 | Bit 2 | Bit 1 | Bit 0 |
---|---|---|---|---|---|
CONNECT | Reserved | 0 | 0 | 0 | 0 |
CONNACK | Reserved | 0 | 0 | 0 | 0 |
PUBLISH | Used in MQTT 3.1.1 | DUP1 | QoS2 | QoS2 | RETAIN3 |
PUBACK | Reserved | 0 | 0 | 0 | 0 |
PUBREC | Reserved | 0 | 0 | 0 | 0 |
PUBREL | Reserved | 0 | 0 | 1 | 0 |
PUBCOMP | Reserved | 0 | 0 | 0 | 0 |
SUBSCRIBE | Reserved | 0 | 0 | 1 | 0 |
SUBACK | Reserved | 0 | 0 | 0 | 0 |
UNSUBSCRIBE | Reserved | 0 | 0 | 1 | 0 |
UNSUBACK | Reserved | 0 | 0 | 0 | 0 |
PINGREQ | Reserved | 0 | 0 | 0 | 0 |
PINGRESP | Reserved | 0 | 0 | 0 | 0 |
DISCONNECT | Reserved | 0 | 0 | 0 | 0 |
2.1.2-1 重发标识(Dup Flag)
对PUBLISH类型的消息来说,MQTT第1个字节的第3位表示重发标识,主要用于保证消息可靠传输,默认值为0,表示第一次发送。
当QoS为大于0时,发布的消息需要回复确认,如果客户端或服务器端没有收到确认回复则尝试重发消息,此时消息的Dup Flag被置为1(需要指出的是,该标识位不能用于检测消息的重复发送)。
2.1.2-2 质量等级(QoS Level)
MQTT使用第1个字节第2、1位表示质量等级,具体的定义如下表所示:
QoS Level | 2 | 1 | 说明 |
---|---|---|---|
0 | 0 | 0 | 至多1次(At Most Once) |
1 | 0 | 1 | 至少1次(At Least Once) |
2 | 1 | 0 | 保证1次(Exactly Once) |
3 | 1 | 1 | 保留值 |
2.1.2-3 保持标识(Retain Flag)
MQTT报文第1字节第0位用于保持标识,服务端和客户端处理保持标识时需要满足以下规则:
i.在客户端发给服务端的PUBLISH报文中,如果RETAIN标识被设置为1,服务端必须保存改消息及其QoS,以使该报文可以被投递给订阅了对应主题的新订阅者;
ii.当新的订阅建立时,服务端必须把每一个主题保持的最近一条RETAIN消息(如果存在的话),投递给订阅者;
iii.如果服务端收到QoS为0,RETAIN标识为1的消息,服务端必须丢弃该主题下保存的所有RETAIN消息,同时保存这条QoS为0的消息,但是这条消息可以随时被丢弃;
iv.当一个新订阅建立时,服务端发给客户端的PUBLISH报文需设置RETAIN为1,其他情况服务端发给客户端的PUBLISH报文的RETAIN位都需要被置为0,不管服务端收到该PUBLISH报文时它的标识是什么;
v.当一个0字节内容的PUBLISH报文的RETAIN标识为1时,服务端会把这条消息投递给订阅了对应主题的订阅者,同时清空这个主题下保持的所有消息。需要指出的是,客户端收到的这条消息的RETAIN标识为0,服务端不会保存0字节内容的消息;
vi.当客户端向服务端发布一条RETAIN为0的PUBLISH报文时,服务端不会保存这条消息,也不会删除或者替换已经保存的RETAIN为1的消息;
2.1.3 剩余长度
MQTT报文的剩余长度是指可变报头和有效载荷的字节总数,从第2个字节开始,最大包括4个字节,剩余长度字段的大小及其能表示的范围如下表所示:
字节数 | 最小值 | 最大值 |
---|---|---|
1 | 0 (0x00) | 127 (0x7F) |
2 | 128 (0x80, 0x01) | 16 383 (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) |
剩余长度的编码算法伪代码如下:
do encodedByte = X MOD 128 X = X DIV 128 // if there are more data to encode, set the top bit of this byte if ( X > 0 ) encodedByte = encodedByte OR 128 endif 'output' encodedByte while ( X > 0 )
剩余长度的解码算法伪代码如下:
multiplier = 1 value = 0 do encodedByte = 'next byte from stream' value += (encodedByte AND 127) * multiplier multiplier *= 128 if (multiplier > 128*128*128) throw Error(Malformed Remaining Length) while ((encodedByte AND 128) != 0)
2.2 可变报头(Variable Header)
在某些类型的MQTT报文中存在位于固定报头和有效载荷之间的包含2个字节的可变报头。具体的报文类型包括PUBLISH (QoS > 0), PUBACK, PUBREC, PUBREL, PUBCOMP, SUBSCRIBE, SUBACK, UNSUBSCRIBE, UNSUBACK等。
2.2.1
可变报头需要满足以下规则:
2.2.1-1
SUBSCRIBE, UNSUBSCRIBE, PUBLISH (QoS > 0)等类型的报文必须包含一个16bit的报文标识符;
2.2.1-2
每次客户端发送上面几种类型的报文时,必须指定一个未使用过的标识符;
2.2.1-3
当一个客户端重发一个特定的MQTT报文时,重发的报文必须使用与被重发报文相同的报文标识符;
当客户端重发的报文被确认后,这个报文的标识符可以在下次重用;
重发报文被确认是指: QoS1的PUBLISH收到PUBACK, QoS2的PUBLISH收到PUBCOMP, SUBSCRIBE收到SUBACK, UNSUBSCRIBE收到UNSUBACK;
2.2.1-4
服务端发送QoS>0的PUBLISH消息也要遵循2.2.3相同的规则;
2.2.1-5
QoS为0的PUBLISH消息不能包含报文标识符字段;
2.2.1-6
PUBACK、PUBREC、PUBREL报文必须包含与相应的PUBLISH报文相同的报文标识符;
2.2.1-7
类似的,SUBACK和UNSUBACK消息必须包含与之对应的SUBSCRIBE和UNSUBSCRIBE报文相同的报文标识符;
2.2.2 包含报文标识符的报文
需要包含报文标识符的报文如下表所示:
控制报文 | 报文标识符字段 |
---|---|
CONNECT | 不需要 |
CONNACK | 不需要 |
PUBLISH | QoS > 0需要 |
PUBACK | 需要 |
PUBREC | 需要 |
PUBREL | 需要 |
PUBCOMP | 需要 |
SUBSCRIBE | 需要 |
SUBACK | 需要 |
UNSUBSCRIBE | 需要 |
UNSUBACK | 需要 |
PINGREQ | 不需要 |
PINGRESP | 不需要 |
DISCONNECT | 不需要 |
2.3 有效载荷Payload
有效载荷Payload即报文的正文部分,包含Payload字段的报文类型主要如下表所示:
控制报文 | 有效载荷 |
---|---|
CONNECT | 需要 |
CONNACK | 不需要 |
PUBLISH | 可选 |
PUBACK | 不需要 |
PUBREC | 不需要 |
PUBREL | 不需要 |
PUBCOMP | 不需要 |
SUBSCRIBE | 需要 |
SUBACK | 需要 |
UNSUBSCRIBE | 需要 |
UNSUBACK | 不需要 |
PINGREQ | 不需要 |
PINGRESP | 不需要 |
DISCONNECT | 不需要 |