MQTT协议分析
- 网络
- 2024-11-23
- 234热度
- 0评论
什么是MQTT
MQTT是基于TCP/IP网络协议栈构建的异步通信消息协议,基于发布-订阅模式进行传输。实现MQTT协议需要客户端和服务器端通讯完成,在通讯过程中, MQTT协议中有三种身份: 发布者(Publish)、代理(Broker)(服务器)、订阅者(Subscribe)。 其中消息的发布者和订阅者都是客户端,消息代理是服务器,消息发布者可以同时是订阅者,客户端之间的通信完全是空间解耦的。如上图,客户端C如果要接收客户端A的信息,先向broker subscibe想要接收的主题,当Client A发布消息是发送给broker,broker接收到后判断此前Client C订阅了该主题信息,那么就会转发给Client C。因此发布消息/订阅消息与发布者和订阅者是完全解耦的,这种耦合度有下面三个维度:
- 空间解耦:发布者与订阅者并不知道对方的存在。
- 时间解藕:发布者与订阅者并不一定需要同时运行。
- 同步 Synchronization 解藕:两个组件的操作比如发布和订阅都不会在发布或者接收过程中产生中断。
MQTT传输的消息分为: 主题(Topic) 和 负载(payload)两部分:
- Topic: 为消息的类型,订阅者订阅(Subscribe)主题后,就会收到目标主题的消息内容(payload)
- payload: 消息的内容,发布者要发布的数据内容和订阅者要接收的数据内容。
MQTT协议传输
主题
MQTT传输中,topic是broker为每个client过滤消息用的,主要用字符串来标识。使用目录分层的结构来进行表示,broker收到消息后会将该主题下的消息转发给所有订阅该主题(Topic)的设备。以“/”为分隔符区分不同的层级,包含通配符“+” 或 “#”的主题又称为主题过滤器(Topic Filters);,不含通配符的成为主题名(Topic Names) ,示例如下:
sensor/10/temperature
sensor/+/temperature
SYS/broker/metrics/packets/receivedSYS/broker/metrics/#
'+' : 表示通配一个层级, 例如a/+,匹配a/x, a/y
MQTT数据包格式
如上图,MQTT的包格式有3部分组成,fixed header,variable header,playload,下面依次进行展开。
fixed header
fixed header又分为MQTT control packet type 和每种packet type对应的flag组成,Packet type主要有以下,其决定了mqtt实际传输的方法。
packet types
flag
在fixed header的bit[3]~bit[0]主要存储的是各个packet type对应的标志,如上图只有PUBLISH type才有意义,其他的都是保留。
- DUP:设置为1,表明这个数据包是一条重复的消息;否则该数据包就是第一次发布的消息。
- QOS:用于发布的传输质量,bit[2]bit[1]=00,只发一次不保证成功;01最少发一次,收不到ACK会一直重传,接收者可能会重复接受到数据;10只有一次,接收者不会重复收到数据。
PUBLISH数据包不得将两个QoS位都设置为1。如果服务器或客户端收到PUBLISH两个QoS位都设置为1 的数据包必须关闭网络连接。 - RETAIN:如果RETAIN 标志设置为1,则在客户端向服务器发送的PUBLISH数据包中,服务器必须存储应用程序消息及其QoS,以便可以将其传递给订阅与其主题名称匹配的未来订阅者。
下面针对QOS质量再进行说明。使用QoS 0可能丢失消息,使用QoS 1可以保证收到消息,但消息可能重复,使用 QoS 2 可以保证消息既不丢失也不重复。
- QOS 0 : QoS 0是最低的QoS 等级,消息即发即弃,不需要等待确认,不需要存储和重传,因此对于接收方来说,永远都不需要担心收到重复的消息。当我们使用 QoS 0 传递消息时,消息的可靠性完全依赖于底层的TCP协议。而TCP只能保证在连接稳定不关闭的情况下消息的可靠到达,一旦出现连接关闭、重置,仍有可能丢失当前处于网络链路或操作系统底层缓冲区中的消息。这也是 QoS 0 消息最主要的丢失场景。
- QOS 1: 是加入了应答与重传机制,发送方只有在收到接收方的PUBACK报文以后,才能认为消息投递成功。在此之前,发送方需要存储该PUBLISH 报文以便下次重传。缺点就是Broker若从发布方收到了重复的PUBLISH报文,而在将这些报文转发给订阅方的过程中,再次发生重传,这将导致订阅方最终收到更多的重复消息。
- QOS 2:解决QoS 0、1 消息可能丢失或者重复的问题,但它也带来了最复杂的交互流程和最高的开销。每一次的QoS 2 消息投递,都要求发送方与接收方进行至少两次请求/响应流程。QOS2是针对接受者不能同时收到重复报的场景需求。
步骤1:发送方(发布者或broker)存储并发布QoS2的报文,然后需要接收方(broker或订阅者)回复PUBREC报文。这里与QoS1流程基本一直,只是回复报文从PUBACK 变成了PUBREC。
步骤2:当发送方收到PUBREC报文后,表示对端已经收到了发送的报文,发送方将不能再重传这个报文,发送方所以此时可以删除本地存储的PUBLISH报文;接着发送一个PUBREL报文通知对端自己准备将本次使用的Packet ID标记为可用了。
步骤3:当接收方收到PUBREL报文后,回复PUBCOMP报文表示自己也准备好将当前的Packet ID用于新的消息了。
4)当发送方收到PUBCOMP报文,这一次的QoS 2 消息传输就算正式完成了。在这之后,发送方可以再次使用当前的 Packet ID 发送新的消息,而接收方再次收到使用这个 Packet ID 的PUBLISH报文时,也会将它视为一个全新的消息。
variable header
可变长度头部有些报文包含,有些报文则不包含。实际上可变长度的头部可以理解为实际的具体数据,因为不同的type对应的payload不同,相同的type也会有不同的数据可以区分,比如connect类型需要包含不少的格式信息。
- 协议版本 :号值表示客户端的版本。
- clean sesion:MQTT客户端向服务器发起CONNECT请求时,可以通过’Clean Session’标志设置会话。‘Clean Session’设置为0,表示创建一个持久会话,在客户端断开连接时,会话仍然保持并保存离线消息,直到会话超时注销。‘Clean Session’设置为1,表示创建一个新的临时会话,在客户端断开时,会话自动销毁
- Will Flag : 遗言标志位
- Will QoS: 遗言的消息质量
- Will Retain: 遗言的保持状态
- Keep Alive timer(心跳时长):client与broker交互PINGREQ和PINGRESP。
payload
一些MQTT控制数据包包含有效载荷作为数据包的最后部分。对于 PUBLISH 数据包,这是应用程序消息。上图列出了需要有效载荷的控制数据包。
MQTT基本交互
CONNECT
- ClientId:每个客户端连接到broker的标识,具有唯一性。如果客户端与服务端不需要保持长连接状态,可以为空。
- CleanSession:client告诉broker是否需要建立持久会话,在持久会话 (CleanSession = false)中,broker会存储client的所有订阅。如果会话不是持久的(CleanSession = true),那么 broker则不会为client存储任何内容并且会清除先前持久会话中的所有信息。
- Username/Password :MQTT会发送username和password进行client认证和授权。如果此信息没有经过加密或者hash,那么密码将会以纯文本的形式发送。因此建议username 和 password 要经过加密安全传输。
- LastWillxxx :表示遗愿,client在连接 broker的时候将会设立一个遗愿,这个遗愿会保存在 broker中,当 client 因为非正常原因与 broker断开连接时,broker会将遗愿发送给订阅了这个 topic(订阅遗愿的 topic)的client。
- keepAlive:client在连接建立时与 broker 通信保活的时间间隔,通常以秒为单位。这个时间指的是client 与 broker 在不发送消息下所能承受的最大时长。client与broker交互PINGREQ和PINGRESP来保持连接。
CONNACK
当broker收到CONNECT消息后,需要发送CONNACK消息进行响应。CONNACK消息包括两部分内容。
PUBLISH
当MQTT client在连接上broker以后,就可以发布信息了。MQTT使用的是基于topic主题的过滤,每条消息都应该包含一个topic,broke 可以使用topic将消息发送给感兴趣 client。除此之外每条消息还会包含一个负载(Payload),Payload 中包含要以字节形式发送的数据。MQTT 是数据无关性的,也就是说数据是由发布者 - publisher 决定要发送的是 XML 、JSON 还是二进制数据、文本数据。
MQTT 中的 PUBLISH 消息结构如下。
- Packet Identifier:这个PacketId标识在 client 和 broker 之间唯一的消息标识。packetId 仅与大于零的 Qos 级别相关。
- TopicName:主题名称是一个简单的字符串,/ 代表着分层结构。
- Qos:这个数字表示的是服务质量水平,服务质量水平有三个等级:0、1 和 2,服务级别决定了消息到达 client 或者 broker 的保证类型,来决定消息是否丢失。
- RetainFlag:这个标志表示 broker 将最近收到的一条 RETAIN 标志位为true的消息保存在服务器端(内存或者文件)。
MQTT 服务器只会为每一个 Topic保存最近收到的一条RETAIN标志位为true的消息。如果MQTT 服务器上已经为某个 Topic 保存了一条 Retained 消息,当客户端再次发布一条新的 Retained 消息时,那么服务器上原来的那条消息会被覆盖。 -
Payload:这个是每条消息的实际内容。MQTT 是数据无关性的。可以发送任何文本、图像、加密数据以及二进制数据。
- Dupflag:这个标志表示该消息是重复的并且由于预期的 client 或者 broker 没有确认所以重新发送了一次。这个标志仅仅与 Qos 大于 0 相关。
当 client 向 broker 发送消息时,broker 会读取消息,根据 Qos 的级别进行消息确认,然后处理消息。处理消息其实就是确定哪些 subscriber 订阅了 topic 并将消息发送给他们。
最初发布消息的 client 只关心将 PUBLISH 消息发送给 broker,一旦 broker 收到 PUBLISH 消息,broker 就有责任将其传递给所有 subscriber。发布消息的 client 不会知道是否有人对发布的消息感兴趣,同时也不知道多少 client 从 broker 收到了消息。
SUBSCRIBE
client 会向 broker 发送 SUBSCRIBE 消息来接收有关感兴趣的 topic,这个 SUBSCRIBE 消息非常简单,它包含了一个唯一的数据包标识和一个订阅列表。
- Packet Identifier:这个 PacketId 和上面的 PacketId 一样,都表示消息的唯一标识符。
- ListOfSubscriptions:SUBSCRIBE 消息可以包含一个client的多个订阅,每个订阅都会由一个 topic和一个 Qos构成。
client在向broker发送 SUBSCRIBE 消息后,broker会向 client发送 SUBACK 确认消息。
- Packet Identifier :这个数据包标识符和SUBSCRIBE中的相同。
- ReturnCode:broker 为每个接收到的 SUBSCRIBE 消息的 topic/Qos 对发送一个返回码。例如,如果 SUBSCRIBE 消息有五个订阅消息,则SUBACK消息包含五个返回码作为响应。
到现在我们已经探讨过了三种消息类型,发布 - 订阅 - 确认消息,这三种消息的示意图如下。
参考文献: