MQTT协议简析

Posted schbook

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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 不需要

以上是关于MQTT协议简析的主要内容,如果未能解决你的问题,请参考以下文章

对netty5支持HTTP协议的代码简析

MQTT协议-MQTT协议简介及协议原理

MQTT 协议学习:002-使用MQTT示例源码构建最简单的demo在STM32上移植MQTT协议

MQTT协议的使用

MQTT协议学习笔记

基于MQTT协议实现Broker