MQTT协议简析

1 概述

MQTT(Message Queuing Telemetry Transport,消息队列遥测传输)是一个C/S架构的发布/订阅模式消息传输协议,最早在1999年由IBM的Andy Stanford-Clark博士和Arcom公司的ArlenNipper博士提出,本文的MQTT协议主要基于MQTT3.1.1

2 报文结构

MQTT报文结构主要分为固定报头、可变报头、有效载荷3个部分,具体结构如下表所示:

说明字节数76543210
固定报头 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 3Bit 2Bit 1Bit 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 Level21说明
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 不需要
原文地址:https://www.cnblogs.com/schbook/p/7576919.html