什么是MQTT
MQTT(Message Queuing Telemetry Transport,消息队列遥测传输协议),是一种基于发布/订阅(publish/subscribe)模式的"轻量级"通讯协议,该协议构建于TCP/IP协议上,由IBM在1999年发布。MQTT最大优点在于,可以以极少的代码和有限的带宽,为连接远程设备提供实时可靠的消息服务。MQTT协议是轻量、简单、开放和易于实现的,这些特点使它适用范围非常广泛。在很多情况下,包括受限的环境中,如:机器与机器(M2M)通信和物联网(IoT)。其在,通过卫星链路通信传感器、偶尔拨号的医疗设备、智能家居、及一些小型化设备中已广泛使用。
协议解析
MQTT协议由三部分组成:
- 固定报头(Fixed header)
- 可变报头(Variable header)
- 有效载荷(Payload)
固定报头
每个MQTT控制报文都包含一个固定报头(长度为2~5个bytes)
图示
Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|
byte1 | MQTT控制报文的类型 | 用于指定控制报文类型的标志位 | ||||||
byte2 | 剩余长度 |
- MQTT控制报文的类型
位于固定报头的第一个字节高四位,详细值代表类型如下表
名字 | 值 | 报文流动方向 | 描述 |
---|---|---|---|
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 | 禁止 | 保留 |
- 用于指定控制报文类型的标志位
该标志位表示MQTT报文类型特定的标值,此处只要关注PUBLISH报文的标志位。
- Bit3是DUP位,重复分发。在QoS质量>0的情况下,PUBLISH、PUBREL报文在一定时间内,没有收到客户端的回复,则需要重发该消息,置该标志位为1,且保证报文标识符相同。(下面说明什么是报文标识符)
- Bit2 & Bit1 是QoS位,即消息服务质量等级。
- Bit0是保留信息位,如果该位置为1,则MQTT Broker会把该次报文的信息保留下来,如有新客户端订阅,Broker会下发该保留信息。
- 剩余长度
表示当前报文剩余部分的字节数,包括可变报头和负载的数据。
可变报头
- 报文标识符
主要作用是在客户端与服务端信息传递时消息的唯一验证,在重复消息的情况下能保证消息的准确性。
Bit | 7 - 0 |
---|---|
byte 1 | 报文标识符 MSB |
byte 2 | 报文标识符 LSB |
需要注意以下几点:
- SUBSCRIBE,UNSUBSCRIBE和PUBLISH(QoS大于0)控制报文必须设置报文标识符(以下称为packetId)
- 客户端发送新的消息时必须分配一个当前未使用的packetId
- 重发消息时,packetId保持一致
- QoS=0的PUBLISH报文不能包含packetId
- PUBACK, PUBREC, PUBREL报文必须包含与最初发送的PUBLISH报文相同的packetId 。类似地,SUBACK和UNSUBACK必须包含在对应的SUBSCRIBE和UNSUBSCRIBE报文中使用的packetId。
- 其他
不同的报文类型有不同的可变报头,具体的说明可以看下面控制报文一章
有效载荷
有效载荷就是我们传递的主要消息体。
控制报文
下面来简述下MQTT的控制报文类型,可参考MQTT协议中文版
CONNECT
当客户端与服务端(以下称为Broker)网络连接成功后,客户端会给Broker发送CONNECT报文。
我们主要根据可变报头的连接标志位来解析有效载荷(Payload)
- 遗嘱
顾名思义,客户端在非正常的与Broker断开通信时,Broker会发送遗嘱消息。
包括但不限于以下条件:
-
服务端检测到了一个I/O错误或者网络故障。
-
客户端在保持连接(Keep Alive)的时间内未能通讯。
-
客户端没有先发送DISCONNECT报文直接关闭了网络连接。
-
由于协议错误服务端关闭了网络连接。
当Will Flag为1时,该客户端需要Broker保存遗嘱信息。
遗嘱信息主要包含有效消息、主题、Qos。Broker还需要根据Will Retain来判断是否需要保留遗嘱信息。
-
用户名和密码
用来校验客户端是否能连接Broker -
客户端标识符 Client Identifier
客户端的唯一标识,连接Broker时必须要带上此参数,否则Broker会拒绝连接。 -
清除会话 Clean Session
客户端连接Broker时会带上Clean Session参数,该参数作用就是决定本次连接是否要恢复上一次连接的相关会话信息。
当CleanSession 为1时,Broker应把该客户端(客户端标识符作为一标识)上一次连接会话的相关数据删除,包括客户端的订阅信息、没有完成的QoS1或QoS2的消息(包括PUBLISH、PUBREL报文)、即将传输的QoS1或QoS2的消息。
当CleanSession为0时,本次连接应基于上一次该客户端会话相关数据来创建会话,恢复包括订阅信息,重发没有完成的QoS1或QoS2的消息(包括PUBLISH、PUBREL报文),重发即将传输的QoS1或QoS2的消息。
CONNACK
Broker收到客户端的CONNECT报文后,经过一系列处理,会返回去CONNACK报文。
包含以下返回码:
值 | 返回码响应 | 描述 |
---|---|---|
0 | 0x00连接已接受 | 连接已被服务端接受 |
1 | 0x01连接已拒绝,不支持的协议版本 | 服务端不支持客户端请求的MQTT协议级别 |
2 | 0x02连接已拒绝,不合格的客户端标识符 | 客户端标识符是正确的UTF-8编码,但服务端不允许使用 |
3 | 0x03连接已拒绝,服务端不可用 | 网络连接已建立,但MQTT服务不可用 |
4 | 0x04连接已拒绝,无效的用户名或密码 | 用户名或密码的数据格式无效 |
5 | 0x05连接已拒绝,未授权 | 客户端未被授权连接到此服务器 |
PUBLISH
PUBLISH的重发(DUP)还有保留(RETAIN)就不重复描述了,在这里主要介绍下服务质量QoS。
QoS值 | Bit 2 | Bit 1 | 描述 |
---|---|---|---|
0 | 0 | 0 | 最多分发一次 |
1 | 0 | 1 | 至少分发一次 |
2 | 1 | 0 | 只分发一次 |
- QoS0
此等级的消息,发布者只会发送一次,不管订阅者是否能收到消息 - QoS1
此等级的消息,会确保订阅者至少收到一次,发布者会一直尝试发送PUBLISH,直到它收到PUBACK才会停止 - QoS2
此等级的消息,会确保订阅者只收到一次。
下面贴张网上找的图片,帮助大家熟悉不同消息质量的交互(图片来源:https://www.jianshu.com/p/8b0291e8ee02)。
初学者可能会对Broker在数据交互过程中的定位会有疑问,下一章我会写到基于Netty开发的MQTT Broker,希望能帮助到大家。
PUBACK
当QoS = 1时,收到来自发布者消息的订阅者会返回PUBACK报文,响应发布者已收到消息,至此,QoS = 1的消息交互结束。
PUBREC
当QoS = 2时,订阅者收到PUBLISH报文后,会返回PUBREC报文。
PUBREL
当QoS = 2时,发布者收到订阅者返回的PUBREC报文后,会发送PUBREL报文到订阅者。
PUBCOMP
当QoS = 2时,订阅者收到发布者发送的PUBREL报文后,会返回PUBCOMP报文到订阅者。至此,QoS = 2的消息交互结束。
大家可以借助上图来理解
SUBSCRIBE
订阅报文必须包含有效载荷,且至少要有一对主题过滤器和QoS等级字段组合。
若保留消息中存在匹配该主题的消息,Broker应发送该保留信息到刚订阅的客户端。
主题过滤器支持通配符#和+两种符号
- +,单层通配符,支持匹配一层主题。
例如,topicFilter为 /hello/+/world,则符合的有下面这些情况/hello/a/world、/hello/b/world、/hello/c/world…但像/hello/a/b/world这种情况就不能匹配了。 - #,多层匹配符,支持匹配多层主题。
例如,topicFilter为 /hello/#,则符合的有下面这些情况/hello/a/world、/hello/b/world、/hello/a/b/world、/hello/a/b/c/world、/hello
需要注意,多层匹配符一定要在主题的最后
SUBACK
订阅确认,类似于连接确认,按顺序返回主题过滤器的QoS质量值集合。
UNSUBSCRIBE
取消订阅,必须包含有效载荷,且至少要有一对主题过滤器和QoS等级字段组合。
Broker在删除该订阅时,
- 它必须停止分发任何新消息给这个客户端 。
- 它必须完成分发任何已经开始往客户端发送的QoS 1和QoS 2的消息。
- 它可以继续发送任何现存的准备分发给客户端的缓存消息。
UNSUBACK
确认订阅取消报文
PINGREQ
客户端发送PINGREQ报文给Broker的。用于:
- 在没有任何其它控制报文从客户端发给服务的时,告知服务端客户端还活着。
- 请求服务端发送 响应确认它还活着。
- 使用网络以确认网络连接没有断开。
PINGRESP
Broker发送PINGRESP报文响应客户端的PINGREQ报文。表示还存活。
DISCONNECT
断开连接报文,Broker收到客户端的DISCONNECT报文后,应
- 删除遗嘱消息
- 关闭网络连接
总结
本篇文章主要是介绍MQTT协议的部分知识点,结合了MQTT协议官方文档和自己的理解,方便自己学习,也希望能帮助到感兴趣的伙伴,如有不明白或有问题的地方欢迎指出。计划下一篇会写基于Netty开发的MQTT Broker,欢迎关注。