Hi, I am watershade. This blog will record some of my technical articles. Wish it is valueable to you. Thanks!
My areas of focus include robotics and ROS, embedded SW/HW, linux, RTOS, IoT and computer languages (C/C++, Python, RUST).
MQTT(Message Queuing Telemetry Transport), 中文名叫消息队列遥测传输协议. MQTT是一个基于发布/订阅(publish/subscribe)模式的“轻量级”消息传输协议。它是一种基于发布/订阅的消息协议,它是为低带宽和低功耗设备设计的。它旨在为物联网(IoT)设备提供可靠的消息传输,并支持多平台、多协议。
MQTT协议由客户端和服务器组成,客户端可以订阅主题(topic)并接收发布到这些主题的消息。服务器可以向客户端发送消息,并将消息发布到主题。
MQTT协议支持三种消息质量(QoS):
图1:ISO七层网络模型
在讲解MQTT之前,先来看一下ISO七层网络模型。ISO七层网络模型是一种网络模型,它将网络分为七层,分别为物理层(Physical Layer)、数据链路层(Data Link Layer)、网络层(Network Layer)、传输层(Transport Layer)、会话层(Session Layer)、表示层(Presentation Layer)、应用层(Application Layer)。
其中物理层、数据链路层和网络层一般统称为媒体层(Media Layer)。MQTT和http协议一样位于应用层之上,依赖于TCP/UDP协议,很多时候还需要TLS/SSL协议。
MQTT是一种发布/订阅协议,旨在连接物联网设备。与HTTP的请求/响应范例不同,MQTT以事件驱动的方式运行,允许将消息推送到客户端。这种架构方法通过解耦数据生产者和数据消费者并消除它们之间的依赖关系来实现高度可扩展的解决方案。建立MQTT连接以发布和订阅消息的两个关键组件是 MQTT客户端和MQTT代理,如下图所示。 !mqtt-publish-subscribe
图2:MQTT发布/订阅架构
在进行正文介绍之前,需要先了解MQTT的几个概念:
还需要了解对于一个Topic来说MQTT设备担任的几个角色:
但是真实的MQTT应用中一个设备可能同时担任发布者和订阅者的角色,所以还可以认为MQTT具有以下两个角色:
当然下面的几个基本概念也需要明确一下:
MQTT控制报文由三部分组成:固定报头(Fixed Header)、可变报头(Variable Header)、有效载荷(Payload)。
固定报头:固定报头由控制报文类型(MQTT Control Packet type)、报文标志(Flags specific to each MQTT Control Packet type)、剩余长度(Remaining Length)三个字段组成。如下表:
Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
Byte 1 | MQTT Control Packet type | Flags specific to each MQTT Control Packet type | ||||||
Byte 2 | Remaining Length |
控制报文类型(MQTT Control Packet type):第一个字节的高半字节,表示控制报文的类型。控制报文的具体含义如下:
名字 | 值 | 报文流动方向 | 描述 |
---|---|---|---|
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 | 禁止 | 保留 |
为了便于记忆,可以将上面14个有效标签(不含Reserved)分成三种功能:连接相关的包含CONNECT,CONNACK和DISCONNECT;发布相关的包含PUBLISH,PUBACK,PUBREC,PUBREL,PUBCOMP;订阅相关的包含SUBSCRIBE,SUBACK,UNSUBSCRIBE,UNSUBACK。
控制报文 | 固定报头标志 | 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 |
DUP1 =控制报文的重复分发标志; QoS2 = PUBLISH报文的服务质量等级; RETAIN3 = PUBLISH报文的保留标志.
某些MQTT控制报文包含可变报头。可变报头的内容根据报文类型而有所不同。报文 可变头部:可变头部根据报文类型不同而不同。可变报头的报文标识符(Packet Identifier)字段存在于在多个类型的报文里。
Bit | 7~0 |
---|---|
Byte1 | 报文标识符 MSB |
Byte2 | 报文标识符 LSB |
SUBSCRIBE,UNSUBSCRIBE和PUBLISH(QoS大于0)控制报文必须包含一个非零的16位报文标识符(Packet Identifier)。客户端每次发送一个新的这些类型的报文时都必须分配一个当前未使用的报文标识符。如果一个客户端要重发这个特殊的控制报文,在随后重发那个报文时,它必须使用相同的标识符。当客户端处理完这个报文对应的确认后,这个报文标识符就释放可重用。QoS1的PUBLISH对应的是PUBACK,QoS2的PUBLISH对应的是PUBCOMP,与SUBSCRIBE对应的是SUBACK,与UNSUBSCRIBE对应的是UNSUBACK。PUBACK, PUBREC, PUBREL报文必须包含与最初发送的PUBLISH报文相同的报文标识符。类似地,SUBACK和UNSUBACK必须包含在对应的SUBSCRIBE和UNSUBSCRIBE报文中使用的报文标识符。
CONNECT,CONNACK,DISCONNECT,PINGREQ,PINGRESP控制报文不包含报文标识符。QoS等于0的PUBLISH报文不能包含报文标识符。
客户端和服务器彼此独立的分配报文标识符。所以当客户端和服务器组合使用相同的报文标识符可以实现并发的消息交换。
某些MQTT控制报文在报文的最后部分包含一个有效载荷。对于PUBLISH来说有效载荷就是应用消息。下表列出了需要有效载荷的控制报文:
控制报文 | 有效载荷 |
---|---|
CONNECT | 需要 |
CONNACK | 不需要 |
PUBLISH | 可选 |
PUBACK | 不需要 |
PUBREC | 不需要 |
PUBREL | 不需要 |
PUBCOMP | 不需要 |
SUBSCRIBE | 需要 |
SUBACK | 需要 |
UNSUBSCRIBE | 需要 |
UNSUBACK | 不需要 |
PINGREQ | 不需要 |
PINGRESP | 不需要 |
DISCONNECT | 不需要 |
客户端到服务端的网络连接建立后,客户端发送给服务端的第一个报文必须是CONNECT报文。在一个网络连接上,客户端只能发送一次CONNECT报文。服务端必须将客户端发送的第二个CONNECT报文当作协议违规处理并断开客户端的连接。有效载荷包含一个或多个编码的字段。包括客户端的唯一标识符,Will主题,Will消息,用户名和密码。除了客户端标识之外,其它的字段都是可选的,基于标志位来决定可变报头中是否需要包含这些字段。
CONNECT的报文类型是1,固定报头的其余部分已经在前面详细介绍了。
CONNECT的可变报头比较复杂.它按照下列次序包含四个字段:协议名(Protocol Name),协议级别(Protocol Level),连接标志(Connect Flags)和保持连接(Keep Alive)。
协议名(Protocol Name)由构成如下表:
Byte | 说明 | (H)7 ~ 0(L) |
---|---|---|
byte 1 | 长度 MSB (0) | 0x00 |
byte 2 | 长度 LSB (4) | 0x04 |
byte 3 | ‘M’ | 0x4D |
byte 4 | ‘Q’ | 0x51 |
byte 5 | ‘T’ | 0x54 |
byte 6 | ‘T’ | 0x54 |
协议名是表示协议名 MQTT 的UTF-8编码的字符串。MQTT规范的后续版本不会改变这个字符串的偏移和长度。如果协议名不正确服务端可以断开客户端的连接,也可以按照某些其它规范继续处理CONNECT报文。对于后一种情况,按照本规范,服务端不能继续处理CONNECT报文。
协议级别(Protocol Level)表示支持的MQTT协议版本标识。 Byte| 说明 | (H)7 ~ 0(L) :—:|:—:|:———: byte 7 | Level(4) | 0x04
客户端用8位的无符号值表示协议的修订版本。对于3.1.1版协议,协议级别字段的值是4(0x04)。如果发现不支持的协议级别,服务端必须给发送一个返回码为0x01(不支持的协议级别)的CONNACK报文响应CONNECT报文,然后断开客户端的连接。
正是因为这个原因,才没有MQTT4直接有MQTT v3.1.1跳到MQTT v5.
连接标志(Connect Flags)字段包含一些用于指定MQTT连接行为的参数。它还指出有效载荷中的字段是否存在。 Byte | 7 | 6 | 5 | 4~3 | 2 | 1 | 0 :——:|:—:|:—:|:—:|:—:|:—:|:—:|:—: 含意 | User Name | Password | Will Retain | Will QoS | Will Flag |Clean Session | Reserved | byte 8 | X | X | X | X | X | X | 0
服务端必须验证CONNECT控制报文的保留标志位(第0位)是否为0,如果不为0必须断开客户端连接。这部分的内容比较复杂,可以结合MQTT的通讯过程掌握。
保持连接(Keep Alive)字段是一个以秒为单位的时间间隔,表示为一个16位的字,它是指在客户端传输完成一个控制报文的时刻到发送下一个报文的时刻,两者之间允许空闲的最大时间间隔。客户端负责保证控制报文发送的时间间隔不超过保持连接的值。如果没有任何其它的控制报文可以发送,客户端必须发送一个PINGREQ报文 。
mqtt.fx
mqttx
MQTT Explorer
MQTT Intro:
MQTT Library:
MQTT Client: