1. 前言
MQTT协议的一些知识整理。
2. 什么是MQTT
MQTT是一种基于标准的消息传递协议或规则集,用于机器对机器的通信。智能传感器、可穿戴设备和其他物联网(IOT)设备通常必须通过带宽有限的资源受限网络传输和接收数据。这些物联网设备使用MQTT进行数据传输,因为它易于实施,并且可以有效地传输物联网数据。MQTT支持设备到云端和云端到设备之间的消息传递。
目前应用比较广泛的是MQTT3.1.1,这个版本包括各种数据传输所需的功能和特征,而且对应生态非常成熟,下面关于MQTT协议格式的介绍则以MQTT3.1.1为例。
3. MQTT优势
MQTT协议已成为物联网数据传输的标准,因为它具有以下优势:
- 轻量、高效
IOT设备上的MQTT实施需要最少的资源,因此它甚至可以用于小型微控制器。例如,最小的MQTT控制消息可以少至两个数据字节。MQTT消息的标头也很小,因此可以优化网络带宽。
- 可扩展
MQTT实施需要最少的代码,在操作中消耗的功率非常少。该协议还具有支持与大量物理网设备通信的内置功能。因此,可以实施MQTT协议来连接数百万台此类设备。
- 可靠
许多IOT设备通过低带宽、高延迟的不可靠蜂窝网络连接。MQTT具有内置功能,可减少IOT设备重新连接云所需的时间。它还定义了三种不同的服务质量级别,以确保IOT用例的可靠性————最多一次(0)、至少一次(1)和恰好一次(2)。
- 安全
MQTT使开发人员可以轻松地使用现代身份验证协议(例如OAuth、TLS1.3、客户管理的证书等)加密消息并对设备和用户进行身份验证。
- 良好的支持
几种语言(如Python)对MQTT协议的实施提供广泛的支持。因此,开发人员可以在任何类型的应用程序中以最少的编码快速实现它。
4. MQTT协议原理
4.1 MQTT协议实现方式
实现MQTT协议需要客户端和服务端通讯完成,在通讯过程中,MQTT协议中有三种身份:发布者(Publish)、代理(Broker,服务器)、订阅者(Subscribe)。其中,消息的发布者和订阅者都是客户端,消息代理是服务器,消息发布者可以同时是订阅者。
MQTT传输的消息分为:主题(Topic)和负载(payload)两部分:
- Topic:可以理解为消息的类型,订阅者订阅(Subscribe)后,就会收到该主题的消息内容(payload);
- payload:可以理解为消息的内容,是指订阅者具体要使用的内容。
4.2 网络传输与应用消息
MQTT会构建底层网络传输:它将建立客户端到服务端的连接,提供两者之间的一个有序的、无损的、基于字节流的双向传输。
当应用数据通过MQTT网络发送时,MQTT会把与之相关的服务质量(QoS)和主题名(Topic)相关联。
MQTT连接始终位于一个客户端和Broker之间。客户端从不直接连接,要启动连接,客户端会向Broker发送连接消息。Broker以CONNACK消息和状态代码进行响应。连接建立后,Broker将其保持打开状态,直到客户发送断开命令或连接中断。MQTT Clients与Broker之间保持TCP长连接。
4.3 MQTT客户端
一个使用MQTT协议的应用程序或者设备,它总是建立到服务器的网络连接。客户端可以:
- 发布其他客户端可能会订阅的信息;
- 订阅其他客户端发布的消息;
- 退订或删除应用程序的消息;
- 断开与服务器连接。
4.4 MQTT服务器
MQTT服务器也称为”消息代理”(Broker),可以是一个应用程序或一台设备。它位于消息发布者和订阅者之间,服务端可以:
- 接受来自客户的网络连接;
- 接受客户发布的应用信息;
- 处理来自客户端的订阅和退订请求;
- 向订阅的客户转发应用程序消息。
4.5 MQTT安全认证
MQTT是基于TCP的,默认情况下通讯不加密。以下是三种解决方法:
- 网络层:通过拉专线或者使用VPN来连接设备与MQTT代理。
- 传输层:传输层使用TLS加密,可以防止中间人攻击(Man-In-The-Middle Attack)。客户端证书不但可以作为设备的身份凭证,还可以用来验证设备。
- 应用层:MQTT还提供客户标识(Client Identifier),用户名密码以及X509证书,在应用层验证设备。
4.6 MQTT协议中的订阅、主题、会话
- 订阅(Subscription)
订阅包含主题筛选器(Topic Filter)和最大服务质量(QoS)。订阅会与一个会话(Session)关联。一个会话可以包含多个订阅。每一个会话中的每个订阅都有一个不同的主题筛选。
- 会话(Session)
每个客户端与服务端建立连接后就是一个会话,客户端和服务器之间有状态交互。会话存在于一个网络之间,也可能在客户端和服务器之间跨越多个连续的网络连接。
- 主题名(Topic Name)
连接到一个应用程序消息的标签,该标签与服务器的订阅匹配。服务器会将消息发送给订阅所匹配标签的每个客户端。
- 主题筛选器(Topic Filter)
一个对主题名通配符筛选器,在订阅表达式中使用,表示订阅所匹配到的多个主题。
- 负载(Payload)
消息订阅者所具体接收的内容。
4.7 MQTT协议中的方法
MQTT协议定义了一些方法(也被称为动作),来用于表示对确定资源所进行操作。这个资源可以代表预先存在的数据或动态生成数据,这取决于服务器的实现。
通常来说,资源指服务器上的文件或输出。主要方法有:
- Connect:等待与服务器建立连接。
- Disconnect:等待MQTT客户端完成所做的工作,并与服务器断开TCP/IP会话。
- Subscribe:等待完成订阅。
- UnSubscribe:等待服务器取消客户端的一个或多个topics订阅。
- Publish:MQTT客户端发送消息请求,发送完成后返回应用程序线程。
5. MQTT协议数据包结构
在MQTT协议中,一个MQTT数据包由:固定头(Fixed header)、可变头(Variable header)、消息体(payload)三部分构成。
MQTT数据包结构如下:
- 固定头(Fixed header):存在于所有MQTT数据包中,表示数据包类型及数据包的分组类标识。
- 可变头(Variable header):存在于部分MQTT数据包中,数据包类型决定了可变头是否存在及其具体内容。
- 消息体(payload):存在于部分MQTT数据包中,表示客户端收到的具体内容。
5.1 MQTT固定头
所有的MQTT报文都包含固定报头,可变报头与有效载荷是部分MQTT报文包含。
固定报头占据两字节的空间:
5.2 报文类型
固定报头的第一个字节分为控制报文的类型(4bit),以及控制报文类型的标志位,控制类型共有14种,其中0与15被系统保留出来。
类型 | 值 | 报文流动方向 | 说明 |
---|---|---|---|
Reserved | 0 | 禁止 | 系统保留 |
CONNECT | 1 | 客户端到服务端 | 客户端请求连接服务器 |
CONNACK | 2 | 服务端到客户端 | 连接报文确认 |
PUBLISH | 3 | 两个方向都允许 | 发布消息 |
PUBACK | 4 | 两个方向都允许 | 消息发布收到确认(QoS 1) |
PUBREC | 5 | 两个方向都允许 | 发布收到(QoS2 第一阶段) |
PUBREL | 6 | 两个方向都允许 | 发布释放(QoS2 第二阶段) |
PUBCOMP | 7 | 两个方向都允许 | 消息发布完成(QoS2 第三阶段) |
SUBSCRIBE | 8 | 客户端到服务端 | 客户端订阅请求 |
SUBACK | 9 | 服务端到客户端 | 订阅请求报文确认 |
UNSUBSCRIBE | 10 | 客户端到服务端 | 客户端取消订阅请求 |
UNSUBACK | 11 | 服务端到客户端 | 取消订阅报文确认 |
PINGREQ | 12 | 客户端到服务端 | 心跳请求 |
PINGRESP | 13 | 服务端到客户端 | 心跳响应 |
DISCONNECT | 14 | 客户端到服务器 | 客户端断开连接 |
Reserved | 15 | 禁止 | 系统保留 |
5.3 报文类型的标志位
固定报头(Byte 1)的bit0-bit3
为标志位,依照报文类型有不同的含义。
在不使用标识位的消息类型中,标识位被作为保留位。如果收到无效的标志位时,接收端必须关闭网络连接。
DUP:发布消息的副本,用来保证消息的可靠传输。如果设置为1,则在下面的变长中增加
MessageId
,并且需要回复确认,以保证消息传输完成,但不能用于检测消息重新发送。QoS:发布消息的服务质量(可靠性控制QoS),即:保证消息传递的次数。
- 00:<=1,至多一次,只发一次,不确保到达。
- 01:>=1,至少一次,确保消息到达但可能有重复。
- 10:=1,只有一次,确保消息到达且无重复。
- 11:预留,客户端或服务器认为这是一条非法的消息,会关闭当前连接。
RETAIN:发布保留标识,表示服务器要保留这次推送的信息,如果有新的订阅者出现,就把这消息推送给它,如果没有那么推送至当前订阅者后释放。
目前Bit[3-0]
只在PUBLISH
协议中使用有效,并且表中指明了是MQTT 3.1.1
版本,其他报文的标志位均为系统保留。对于其它MQTT协议版本,内容可能不同。
所有固定头标记为”保留”的协议类型,Bit[3-0]
必须保持与表中保持一致,如SUBSCRIBE
协议,其Bit 1
必须为1。如果接收方接收到非法的消息,会强行关闭当前连接。
PUBLISH
报文的第一字节bit3
是控制报文的重复分发标志(DUP),bit1-bit2
是服务质量等级,bit0
是PUBLISH
报文的保留标志,用于标识PUBLISH
是否保留。
当客户端发送一个PUBLISH
消息到服务器,如果保留标识位置1,那么服务器应该保留这条消息,当一个新的订阅者订阅这个主题的时候,最后保留的主题消息应该被发送到新订阅的用户。如果DUP字段(bit 3
)设置为1,表明这是一条重复消息,否则是第一次发布消息。为了保证消息的可靠性传递,当QoS
设置为1时,客户端或服务器发布消息时,需要得到对方的确认(PUBACK
),如果一段时间后没收到PUBACK
,那么会再次发送当前消息,并将DUP字符按标记为1。
报文类型 | 固定头标记 | Bit3 | Bit2 | Bit1 | Bit0 |
---|---|---|---|---|---|
CONNECT | 保留 | 0 | 0 | 0 | 0 |
CONNACK | 保留 | 0 | 0 | 0 | 0 |
PUBLISH | Used in MQTT 3.1.1 | DUP | QoS | QoS | RETAIN |
PUBACK | 保留 | 0 | 0 | 0 | 0 |
PUBREC | 保留 | 0 | 0 | 0 | 0 |
PUBREL | 保留 | 0 | 0 | 1 | 0 |
PUBCOMP | 保留 | 0 | 0 | 0 | 0 |
SUBSCRIBE | 保留 | 0 | 0 | 1 | 0 |
SUBACK | 保留 | 0 | 0 | 0 | 0 |
UNSUBACRIBE | 保留 | 0 | 0 | 1 | 0 |
UNSUBACK | 保留 | 0 | 0 | 0 | 0 |
PINGREQ | 保留 | 0 | 0 | 0 | 0 |
PINGRESP | 保留 | 0 | 0 | 0 | 0 |
DISCONNECT | 保留 | 0 | 0 | 0 | 0 |
5.4 剩余长度(Remaining Length)
1 | 剩余长度(Remaining Length) = Variable Header + Payload长度(如果存在) |
剩余长度不包括用于编码剩余长度字段本身的字节数。
剩余长度从Byte 2
开始,最长可达4Byte
。所以剩余长度范围是Byte[2-5]
。
确定其长度到底是1Byte
还是4Byte
,这取决于字节的最高位Bit 7
(默认都是高字节在前),如果这个值是1,那么就继续计算字节长度,如果是0,那么就不再计算字节长度。
剩余长度字段使用一个变长度编码方案,对小于128(2^7)
的值它使用单字节编码,而对于更大的数值则按下面的方式处理:每个字节的低7位用于编码数据长度,最高位(bit7)
用于标识剩余长度字段是否有更多的字节,且按照大端模式进行编码,因此每个字节可以编码128个数值和一个延续位,剩余长度字段最大可拥有4个字节,最大可以表示
$$
(128128128128)/(10241024) Byte=256MB
$$
字节对应取值:
- 当剩余长度使用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]
。
总结:MQTT
报文理论上可以发送最大256MB
的报文。
例子:
- 假设消息长度是
[0x60]
,其二进制是01100000
,字节最高位Bit 7
(从左边起第0位)是0,所以不需要继续往后计算。那么消息长度就是0x60,十进制是96。 - 如果消息长度是[0xC1, 0xC2, 0x33],那么他们的二进制分别为
1 | 0xC1 = 193 = 1100 0001 |
第一字节最高位是1,那么需要继续向后计算,去掉标记位(193 % 128)
,得到100 0001=0x41=65
第二字节最高位是1,那么需要继续向后计算,去掉标记位(194 % 128)
,得到100 0010=0x42=66
第三字节最高位是0,不需要向后计算,其结果就是0x33=51
因为低位在前,高位在后,那么长度计算为
1 | Length = 65 + 66*128 + 51*128*128 = 844097 Byte = 824 KB |
需要注意的是,消息长度=可变头部长度+消息内容长度
。不包括首字节和消息长度本身,如果消息长度为5(占用1字节长度),那么说明这条消息后边还有5字节,整条消息长度为7(首字节+1位长度字节+5)。
另外,如果消息长度为4字节,最后一位不能超过0x7f=127
,因为如果超出这个值,其最高位Bit 7
是1,还需要往后计算,这与消息最大长度为4字节矛盾。所以如果出现[0xff,0xff,0xff,0xff]
这样的消息长度,那么接收方认为这是一条非法的消息。
5.5 MQTT可变头(Variable header)
MQTT数据包中包含一个可变头,它位于固定头和负载之间。可变头的内容因数据包类型而不同,较长的应用是作为包的标识。
只有某些报文才拥有可变报头,它在固定报头和有效负载之间,可变报头的内容会根据报文类型的不同而有所不同,但可变报头的报文标识符(Packet Identifier)字段存在于多个类型的报文里,而有一些报文又没有报文标识符字段,具体键表格,报文标识符结构如下:
控制报文 | 报文标识符字段 |
---|---|
CONNECT | 不需要 |
CONNACK | 不需要 |
PUBLISH | 需要(如果QoS > 0) |
PUBACK | 需要 |
PUBREC | 需要 |
PUBREL | 需要 |
PUBCOMP | 需要 |
SUBSCRIBE | 需要 |
SUBACK | 需要 |
UNSUBSCRIBE | 需要 |
UNSUBACK | 需要 |
PINGREQ | 不需要 |
PINGRESP | 不需要 |
DISCONNECT | 不需要 |
5.6 Payload消息体
Payload消息体位于MQTT数据包的第三部分,包含CONNECT
、SUBSCRIBE
、SUBACK
、UNSUBSCRIBE
、PUBLISH
(类型可选)五种类型的消息,除了上面列出的报文类型,其它的报文类型都没有Payload
。
- CONNECT,消息体内容主要是:客户端的ClientID、订阅的Topic、Message以及用户名和密码。
- SUBSCRIBE,消息体内容是一系列的要订阅的主题以及QoS。
- SUBACK,消息体内容是服务器对于SUBSCRIBE所申请的主题及QoS进行确认和回复。
- UNSUBSCRIBE,消息体内容是要订阅的主题。
6. 参考链接
1 | 什么是 MQTT? |
发布时间: 2023-12-12
最后更新: 2023-12-12
本文标题: MQTT协议解析
本文链接: https://foxcookie.github.io/2023/12/12/MQTT协议解析/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明出处!