编辑
2025-04-18
记录知识
0
请注意,本文编写于 70 天前,最后修改于 59 天前,其中某些信息可能已经过时。

目录

MQTT是什么?
MQTT名词解释
消息发送
QoS0方式消息发送如下
QoS1方式发送
QoS2方式发送
测试验证
在Linux环境上体验体验MQTT
使用tcpdump抓数据包
分析MQTT数据包
总结

本文基于物联网来介绍一下MQTT

MQTT是什么?

MQTT:MQ Telemetry Transport,消息队列遥测传输协议。 它是非常轻量的消息传递协议,对于与需要较小代码占用和/或网络带宽非常宝贵的远程位置建立连接,它最有用。 MQTT是分布式的软总线,它的消息通过订阅者,发布者,消息代理三个角色实现,它能存在于所有的智能设备上。从而达到万物互联。当然实现物联网的物联网协议也很多,如CoAP,HTTP,UPnP,XMPP等,但MQTT具有更加简单,轻量的优势。

MQTT名词解释

  1. 订阅者(Subscriber)
    就是消息的接收者,设备需要消息,就从消息代理获取发布者推送的消息
  2. 发布者(Publisher)
    就是消息的发送者,它能提供给消息代理特定含义的消息
  3. 消息代理(Broker)
    就是接收订阅者和发布者的请求和消息,并根据请求转发消息
  4. 主题(Topic)
    就是发布者用于标识发布什么类型的消息,订阅者用于标识订阅什么类型的消息
  5. QoS
    就是用于确定发布者发布消息的等级,主要包括三个等级 qos0:表示未知服务,代表此消息最多发送一次,且不保证订阅者是否能够成功接收 qos1:表述已知服务,代表此消息发布后,需要得到至少一个订阅者的确定收到的回复,否则消息将被重发 qos2:表述保证服务,代表此消息发布后,不仅需要得到订阅者的回复,还会在收到订阅者回复后,再发送回复消息给订阅者确定。从而保证消息是完全稳定可靠的
  6. 临终遗嘱(Last Will and Testament) 就是当连接是异常断开的情况下,消息代理会按照发布者的临终遗嘱发送一条预设的消息。
  7. 保留消息(Retained)
    就是当订阅者订阅消息时,判断是否打开保留消息从而决定是否立即发送上一条消息给订阅者
  8. 树形结构(Tree Struct)
    就是指主题的分类按照树形结构分类,如卧室温度消息的结构为“home/houseroom/temperature”
  9. 通配符(+/#)
    就是可以通过通配符找到符合正则表达的所有主题,如 “home/houseroom/#”表示订阅所有卧室消息 “home/+/temperature”表示订阅所有房间的温度消息
  10. 清理会话(CleanSession)
    如果置位为0,已断开过的服务端必须基于当前会话状态恢复新的客户端连接 如果置位为1,必须丢弃之前的任何会话,并开始新的会话
  11. 客户端(Client)
    客户端包括订阅者和发布者,它会总是连接到MQTT服务器。 客户端可以:
  • (1)发布其他客户端可能会订阅的信息。
  • (2)订阅其它客户端发布的消息。
  • (3)退订或删除应用程序的消息。
  • (4)断开与服务器连接。
  1. MQTT服务器
    服务器就是消息代理Broker,它可以是一个应用程序,也可以是设备。它位于发布者和订阅者之间 服务器可以:
  • (1)接受来自客户的网络连接;
  • (2)接受客户发布的应用信息;
  • (3)处理来自客户端的订阅和退订请求;
  • (4)向订阅的客户转发应用程序消息。
  1. 保持连接(Keep Alive)
    就是指在客户端传输完成一个控制报文的时刻到发送下一个报文的时刻,两者最大允许的时间间隔。客户端需要保证控制报文发送时间不超过这个值。

消息发送

QoS0方式消息发送如下

image.png

QoS0级别的消息发送,意味着接收者并不会响应消息,发送者也不会做重试判断,所以消息最多可能送达一次,但也有可能无法送达 发送者:
发送这个 PUBLISH报文
接收者:
接收这个PUBLISH报文

QoS1方式发送

image.png

QoS1级别的消息发送,意味着消息至少被发送一次,并确定至少送达一次
发送者:
必须在每个新的消息上分配一个报文标识符,发送的PUBLISH报文必须标识为QoS=1,DUP=0,且必须将这个报文当作未确认的报文,直到接收到对应的响应报文
接收者:
响应PUBACK报文必须包含一个报文标识符,需要与接收到的PUBLISH报文相同
发送PUBACK报文后,接收者必须将任何包含相同报文标识符的PUBLISH报文当作一个新的消息。保证下一次仍能正常接收。

QoS2方式发送

image.png

QoS2是最高级别的发送方式,他确保消息不被丢失,同时也确保消息不重复。
发送者:
1.为消息分配一个未使用的报文标识符,然后将PUBLISH报文的标识符为置为QoS2 DUP0。
2.在发送PUBLISH报文时,将PUBLISH报文看作是未被确认的,直到收到PUBREC报文。且在收到PUBREC报文后必须发送一个PUBREL报文,该报文必须和PUBLISH报文具有相同的报文标识符。
3.同时,也必须将这个PUBREL报文看作是未确认的,直到从接收者那里收到对应的PUBCOMP报文。
4.最后,一旦发送了对应的PUBREL报文,就不能重发这个PUBLISH报文。

接收者:
1.响应的 PUBREC 报文必须包含报文标识符,这个标识符来自接收到的、已经接受所有权的PUBLISH 报文。
2.在收到对应的 PUBREL 报文之前,接收者必须发送 PUBREC 报文确认任何后续的具有相同标识符的 PUBLISH 报文。 在这种情况下,它不能重复分发消息给任何后续的接收者。
3.响应 PUBREL 报文的 PUBCOMP 报文必须包含与 PUBREL 报文相同的标识符。
4.发送 PUBCOMP 报文之后,接收者必须将包含相同报文标识符的任何后续 PUBLISH 报文当作一个新的发布。

测试验证

在Linux环境上体验体验MQTT

  1. 安装mosquitto
apt update && apt install mosquitto mosquitto-clients
  1. 创建认证文件
touch /etc/mosquitto/pwfile

创建用户和密码

mosquitto_passwd /etc/mosquitto/pwfile kylin

密码 qwe123

  1. 设置ACL规则
vim /etc/mosquitto/aclfile user kylin topic write test/# topic read test/#
  1. 启动消息代理(Broker)
mosquitto -c /etc/mosquitto/mosquitto.conf -d
  1. 订阅一个主题
mosquitto_sub -h localhost -t "test/a" -u kylin -P qwe123 -i "c1" -h 为指定mqtt的host地址 -t 为指定mqtt的主题 -u 为用户 -P 为密码 -i 为process id
  1. 发布一个主题
mosquitto_pub -h localhost -V mqttv311 -t 'test/a' -u kylin -P qwe123 -i "c3" -m "Hello World" -M 0 其中-h -t -u -i -P 意义与上一致 -V 指定发布消息的版本(mqttv311/mqttv31) -m 为发布的消息字符串 -M 为指定QoS等级

使用tcpdump抓数据包

image.png

如上图可以看到,在订阅者这里会阻塞轮询消息,直到发布者发送消息,订阅者就能收到消息“Hello World”

那么,随即而来就会产生一个疑问。之前将了这么多概念,那实际上这是怎么实现的呢?
探索如何实现这个事情,就需要利用tcpdump了
TCP抓包:

tcpdump -i lo tcp port 1883 -X

-i: 代表网络接口
tcp: mqtt是基于tcp传输的
port 1883: mqtt默认传输端口为1883
-X:在抓包时,以十六进制和 ASCII 表示打印每个数据包的数据

上面为完整的数据包内容,鉴于TCP有三次握手,所以取第四个数据包来简单解析MQTT数据包。如下

image.png

因为这是一个完整的TCP数据包,所以需要去掉一些TCP协议相关的数据 其中

4500 0053 8998 4000 4006 b30a 7f00 0001 7f00 0001 :为TCP的报文头。不具体分析 ae80 075b 6e6a c507 dded ec9e 8018 0200 fe47 0000 0101 080a 265c 053f 265c 053f :传输控制协议报文信息。不具体分析 101d 0004 4d51 5454 04c2 003c 0002 6333 0005 6b79 6c69 6e00 0671 7765 3132 33 :为真正的TCP数据包,也就是MQTT的数据包。主要分析这块数据

分析MQTT数据包

101d: 0x10由协议表:2.2.1可以查到,对应CONNECT,0x1d为字节长度,这里计算为29个字节

image.png

0004 4d51 5454 04c2 003c:
0004 :协议名字长度为4
4d51 5454:名字为MQTT(ASCII码)(如果是3.1协议,则字串为MQIsdp(4d51 4973 6470),长度为6)
04:版本号:3.1.1版本号为4, 3.1版本号为3。
c2:连接标志,包含:名称,密码,QoS0,清理会话。(3.1.2.3章节)

image.png

003c:保持连接时间,默认为60秒。超时情况下会发送PINGREQ报文用于探测broker和client(发布者和订阅者)直接是否仍在线(3.1.2.10章节)

image.png

0002 6333: 0002为Client ID 长度为2,6333 ID为字符串c3(也就是process ID),我发布的时候用-i参数指定了c3
0005 6b79 6c69 6e00 0671 7765 3132 33: 这里为账户kylin 密码 qwe123的字符串明文

总结

至此,一个完整的connect包已经解析完成了。在connect之后,其实后面还有许多数据包都能进行解析。