1.什么是MQTT协议?
1.1 mqtt发展历史
MQTT是用于物联网(IoT)的OASIS(结构化信息标准促进组织)标准消息传输协议。它被设计成一种非常轻量级的发布/订阅消息传输,非常适合于以较小的代码占用和最小的网络带宽连接远程设备。MQTT目前广泛应用于各种行业,如汽车、制造业、电信、石油和天然气等。
IBM公司的安迪·斯坦福-克拉克及Cirrus Link公司的阿兰·尼普于1999年撰写了该协议的第一个版本。该协议的可用性取决于该协议的使用环境。IBM公司在2013年就向结构化资讯标准促进组织提交了 MQTT 3.1 版规范,并附有相关章程,以确保只能对规范进行少量更改。
MQTT-SN是针对非 TCP/IP 网络上的嵌入式设备主要协议的变种,与此类似的还有ZigBee协议。
1.2 mqtt协议版本
mqtt协议由OASIS MQTT技术委员会维护,成员包括:
- MQTT 5:【最新版本】,OASIS标准,协议可适用。在v3.1.1版本的基础上增加了会话/消息延时功能、原因码、主题别名、in-flight流控、属性、共享订阅等功能,增加了用于增强认证的AUTH报文。
- MQTT 3.1.1:【旧版本】,是ISO及OASIS标准,协议可适用
- MQTT 3.1:【历史版本】,使用mqtt 3.1协议的历史参考版本
- MQTT-SN v1.2:【传感器网络版本】以前称为,MQTT-S,英文描述为MQTT for Sensor Networks,传感器网络的MQTT针对的是非TCP/IP网络上的嵌入式设备,如Zigbee。MQTT-SN是一种用于无线传感器网络(WSN)的发布/订阅消息传递协议,其目的是将MQTT协议扩展到传感器和执行器解决方案的TCP/IP基础设施之外。
1.3 物联应用协议对比
功能 | http | websocket | coap | mqtt | mqtt-sn |
---|---|---|---|---|---|
安全性 | ssl/tls | ssl/tls | dtls | ssl/tls | ssl/tls |
低协议开销 | 否 | 否 | 是 | 是 | 是 |
低功耗 | 否 | 否 | 是 | 是 | 是 |
对不稳定网络的容忍 | 否 | 否 | 是 | 是 | 是 |
传输层 | tcp | tcp | udp | tcp | non-ip或udp |
实时性 | 否 | 是 | 否 | 是 | 否 |
网络穿透性 | 否 | 是 | 否 | 是 | 否 |
操作方式 | / | / | 1对1 | 多对多 | 多对多 |
结构 | 请求/应答 | 请求/应答 | 请求/应答 | 发布/订阅 | 发布/订阅 |
QoS | ack | ack | ack | 3种 | 3种 |
1.4 mqtt优点
- 轻巧高效:MQTT客户端非常小,需要最少的资源,因此可以在小型微控制器上使用。MQTT消息头很小,占用很低的网络带宽。
- 双向通信:MQTT允许设备到云和云到设备之间的消息传递。这使得将消息广播到多个对象组是很容易的。
- 可扩展到数百万个对象:MQTT可以扩展以连接数百万个物联网设备。
- 可靠的传递消息:消息传递的可靠性对于许多物联网场景非常重要。这就是MQTT有3个定义的服务质量级别的原因:0—最多一次,1—至少一次,2—正好一次
- 对不可靠网络的支持:许多物联网设备通过不可靠的蜂窝网络连接。MQTT对持久会话的支持减少了将客户机与代理重新连接的时间。
- 安全性支持:MQTT使使用TLS加密消息和使用现代身份验证协议(如OAuth)对客户端进行身份验证变得非常容易。
1.5 mqtt缺点
- 需要SDK支持:不同于类似http协议,mqtt协议在不同平台上(例如MCU,Linux,Android,IOS,WEB)需要集成对应的SDK软件包才能与MQTT服务器进行通信,以实现互连和互操作性。
- 不支持文件和流媒体传输:在某些应用场景中,需要传输的信息可能不限于信令,需要通过文件或流媒体例如语音和视频信号。
- 不支持点对点通信:从理论上讲,点对点通信可以通过相互订阅来实现,但是逻辑相对复杂并且涉及设备安全性。当设备B和设备C相同时-在主题的情况下,设备A无法知道消息是来自设备B还是来自设备C,并且消息很可能是被设备D窃听的。
- 不支持群组通信:只能通过群组管理方式实现群组通信,如果一个设备由多个人控制或多个设备由一个人控制,则此功能特别有用。
2.MQTT数据包结构
//**该部分内容整理自MQTT-3.1.1标准协议文档**
每条MQTT消息最多由三部分组成,始终排列为(固定的报头 | 可变包头 | 有效负荷),如下所示:
MQTT数据包 |
---|
固定报头(Fixed Header),存在于所有MQTT控制数据包中 |
可变报头(Variable Header),存在于某些MQTT控制数据包中 |
有效负载(Payload),存在于某些MQTT控制数据包中 |
2.1 固定报头(Fixed Header)
MQTT固定报头最少2个字节
- 第一个字节包含MQTT控制报文类型和控制报文类型的【标志位】;
- 第二个字节开始是剩余长度字段,该长度是后面的【可变报头】和【有效负载】的总长度,该字段最多允许4个字节。
2.1.1 MQTT控制报文类型
位置:第 1 个字节,二进制位 7-4。表示为 4 位无符号值,这些值的定义见下表。
名字 | 值 | 报文流动方向 | 描述 |
---|---|---|---|
Reserved | 0 | 禁止 | 保留 |
CONNECT | 1 | C -> S | 客户端请求连接服务端 |
CONNACK | 2 | S -> C | 连接报文确认 |
PUBLISH | 3 | 两个方向都允许 | 发布消息 |
PUBACK | 4 | 两个方向都允许 | QoS 1 消息发布收到确认 |
PUBREC | 5 | 两个方向都允许 | 发布收到(保证交付第一步) |
PUBREL | 6 | 两个方向都允许 | 发布释放(保证交付第二步) |
PUBCOMP | 7 | 两个方向都允许 | QoS 2 消息发布完成(保证交互第三步) |
SUBSCRIBE | 8 | C -> S | 客户端订阅请求 |
SUBACK | 9 | S -> C | 订阅请求报文确认 |
UNSUBSCRIBE | 10 | C -> S | 客户端取消订阅请求 |
UNSUBACK | 11 | S -> C | 取消订阅报文确认 |
PINGREQ | 12 | C -> S | 心跳请求 |
PINGRESP | 13 | S -> C | 心跳响应 |
DISCONNECT | 14 | C -> S | 客户端断开连接 |
Reserved | 15 | 禁止 | 保留 |
2.1.2 用于指定控制报文类型的标志位
位置:固定报头第 1 个字节的剩余的4位[3-0],包含每个MQTT控制报文类型特定的标志,见下表。
表中任何标记为“保留”的标志位,都是保留给以后使用的,必须设置为表格中列出的值,如果收到非法的标志,接收者必须关闭网络连接。
控制报文 | 固定报头标志 | bit3 | bit2 | bit1 | bit0 |
---|---|---|---|---|---|
CONNECT | Reserved | 0 | 0 | 0 | 0 |
CONNACK | Reserved | 0 | 0 | 0 | 0 |
PUBLISH | Used in MQTT 3.1.1 | DUP | QoS | QoS | RETAIN |
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 |
- DUP:控制报文的重复分发标志
- QoS:PUBLISH报文的服务质量等级
- RETAIN:PUBLISH报文的保留标志
PUBLISH控制报文的DUP、QoS和RETAIN标志描述后面会详细描述。
2.1.3 剩余长度
位置:从第 2 个字节开始。
剩余长度(Remaining Length)表示当前报文剩余部分的字节数,包括可变报头和负载的数据。不包括用于编码剩余长度字段本身的字节数。
剩余长度字段使用一个变长度编码方案,对小于128的值它使用单字节编码。更大的值按下面的方式处理。每个字节低7位有效位用于编码数据,最高有效位用于指示是否有更多的字节。因此每个字节可以编码 128 个数值和一个延续位(continuation bit)。剩余长度字段最大4个字节。
字节数 | 最小值(0,1,2,3) | 最大值(0,1,2,3) |
---|---|---|
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) |
2.2 可变报头(Variable Header)
某些MQTT 控制报文包含一个可变报头部分。它在固定报头和负载之间。可变报头的内容根据报文类型的不同而不同。可变报头的报文标识符(Packet Identifier)字段存在于在多个类型的报文里。
2.2.1 报文标识符
很多控制报文的可变报头部分包含一个两字节的报文标识符字段。这些报文是 PUBLISH(QoS>0 时),PUBACK,PUBREC,PUBREL,PUBCOMP,SUBSCRIBE, SUBACK,UNSUBSCIBE,UNSUBACK。
SUBSCRIBE,UNSUBSCRIBE 和 PUBLISH(QoS 大于 0)控制报文必须包含一个非零的 16 位报文标识符(Packet Identifier)。客户端每次发送一个新的这些类型的报文时都必须分配一个当前未使用的报文标识符。如果一个客户端要重发这个特殊的控制报文,在随后重发那个报文时,它必须使用相同的标识符。当客户端处理完这个报文对应的确认后,这个报文标识符就释放可重用。
QoS 1 的 PUBLISH 对应的是 PUBACK,QoS 2 的 PUBLISH 对应的是 PUBCOMP,与SUBSCRIBE 或UNSUBSCRIBE 对应的分别是 SUBACK 或 UNSUBACK。发送一个 QoS 0 的 PUBLISH报文时,相同的条件也适用于服务端。
QoS 设置为 0 的 PUBLISH 报文不能包含报文标识符。
PUBACK, PUBREC, PUBREL报文必须包含与最初发送的 PUBLISH 报文相同的报文标识符。类似地,SUBACK 和 UNSUBACK 必须包含在对应的 SUBSCRIBE 和 UNSUBSCRIBE 报文中使用的报文标识符。
需要报文标识符的控制报文在下表中列出。
控制报文 | 报文标识符字段 |
---|---|
CONNECT | 不需要 |
CONNACK | 不需要 |
PUBLISH | 需要(如果QoS>0) |
PUBACK | 需要 |
PUBREC | 需要 |
PUBREL | 需要 |
PUBCOMP | 需要 |
SUBSCRIBE | 需要 |
SUBACK | 需要 |
UNSUBSCRIBE | 需要 |
UNSUBACK | 需要 |
PINGREQ | 不需要 |
PINGRESP | 不需要 |
DISCONNECT | 不需要 |
客户端和服务端彼此独立地分配报文标识符。因此,客户端服务端组合使用相同的报文标识符可以实现并发的消息交换。
2.3 有效载荷(payload)
某些 MQTT 控制报文在报文的最后部分包含一个有效载荷,这将在介绍控制报文内容时详细描述。对于 PUBLISH 来说有效载荷就是应用消息。下表列出了需要有效载荷的控制报文。
控制报文 | 有效载荷 |
---|---|
CONNECT | 需要 |
CONNACK | 不需要 |
PUBLISH | 可选 |
PUBACK | 不需要 |
PUBREC | 不需要 |
PUBREL | 不需要 |
PUBCOMP | 不需要 |
SUBSCRIBE | 需要 |
SUBACK | 需要 |
UNSUBSCRIBE | 需要 |
UNSUBACK | 不需要 |
PINGREQ | 不需要 |
PINGRESP | 不需要 |
DISCONNECT | 不需要 |