mqtt协议 学习笔记
文章目录1.MQTT协议介绍2.MQTT协议特性3.MQTT协议的通信模型1.MQTT协议介绍MQTT(MessageQueuingTelemetryTransport)消息队列遥测传输Telemetry表面MQTT协议适合于遥测数据的传输,,如遥测数据的上传,制动命令下发等等。mqtt由IBM提出,发布的是MQTTV3.1版本。在2014年,被OASIS采用,发布了MQTTV3.1.1版本。现在
参考资料:mqtt v3.1.1 官方文档
1. MQTT协议简介
MQTT(Message Queuing Telemetry Transport)消息队列遥测传输。MQTT协议 适合于 遥测数据的传输,如遥测数据的上传,制动命令下发等等。
mqtt 由IBM 提出,发布的是MQTT V3.1版本。在2014年,被OASIS采用,发布了MQTT V3.1.1版本。现在MQTT还是有OASIS维护和开发。到现在2023年已经发展到了MQTT V5.0版本,本文的MQTT协议以MQTT V3.1.1版本为标准。
1.1 mqtt的三类角色简介
MQTT 协议中,有三类角色。分别是:订阅方,发布方,消息代理方(服务器)。如下图所示。
发布方 向 消息代理方(服务器) 发布消息,消息代理方(服务器) 收到之后, 会把消息 发送给 订阅方。
订阅方 可以作为 发布方 发布消息。同理,发布方 也可以作为 订阅方 接收消息。就是说,订阅方和发布方两者之间可以互相转换角色。如下图:
1.2 mqtt的主题与消息简介
在mqtt协议中有主题的字段(在阿里云物联网平台中,主题是在平台中设置的),主题可以有很多个,如主题A,主题B,主题♥等等。发布的消息必须填写好发布消息的主题。这样可以实现消息的传播去向。
如,发布方往主题A发布消息,只有订阅了主题A的订阅方才能收到消息。具体请看下图:client#1 往 ♥主题 发布 消息1,只有 订阅了 ♥主题 的 client#3,才能收到 消息1。
额外,有个骚操作:如果自己订阅了主题A,自己往主题A发布消息,自己也会接收到主题A的消息。
1.3 MQTT协议特性
- 轻量级协议,开销小
适用于M2M(machine to machine),WSN(wireless sensor network),Iot。
14种类型消息 - 异步通信模式,解耦通信双方
空间解耦,通信双方不需要知道对方的IP地址和端口号。
时间解耦,通信双方不需要同时在线。(使用topic 把双方联系起来) - 基于TCP
MQTT是应用层的协议,是基于TCP协议的。 - 保持会话和遗嘱的特性
略
2. MQTT协议的通信模型
2.1 mqtt客户端和服务端
MQTT 应用于M2M(机器节点与机器节点之间)的通信,两个机器之间不需要知道对方的IP地址和端口号,只需要 接收/发送 服务器(消息代理)的数据就行。所以MQTT可以总括为两种角色:MQTTCLient、MQTTServer

- MQTTClient MQTT客户端
MQTTClient 一般是传感器节点/制动器节点。主要是往 MQTT Server 发布消息,订阅消息,并且接收之后MQTT Server 发送过来的订阅消息。
MQTT客户端可以是发布者,也可以是订阅者,取决于应用逻辑。例如:stm32节点需要往MQTT服务器发布消息,上传传感器数据,故stm32是发布者;同时stm32也需要订阅服务器的消息,接收服务器发送的消息,故stm32也是订阅者。 - MQTT Server MQTT服务端
服务端工作主要是 主题队列维护、消息的队列维护、消息的转发(接收客户端的主题订阅,并转发该主题下的消息到对应的客户端)。
2.2 mqtt的主题和消息
- 主题Topic:一个自定义的字符串,可以理解为 消息的种类
- 消息:就是具体的消息内容
以一个智能家居系统为例子,我们把 客厅温度 作为一个 Topic主题;客厅温度传感器连接到MQTT server服务器,往 客厅温度 这个Topic主题发布温度数据消息。;然后手机APP作为客户端,订阅客厅温度这个主题,这是服务器就会把接收到的客厅温度传感器的温度数据消息转发到所有订阅了客厅温度这个Topic主题的客户端。
MQTT的主题
主题是自定义的一些字符串标识符,它具有一个层级属性。可以使用+和#配合主题字符串实现灵活发布消息。
如有 主题myhome/groundfloor、主题myhome/groundfloor/livingrom、主题myhome/groundfloor/kitchen。
往主题myhome/groundfloor/#发送消息,订阅主题myhome/groundfloor/livingrom、主题myhome/groundfloor/kitchen的客户端都会收到消息。
具体如下图:
2.3 MQTT协议的连接与会话
最后了解一下mqtt的连接过程。
mqtt的连接由客户端发起,会建立一个会话,把客户端附着到服务器上。
服务器根据连接参数(ClientID,用户名,密码)对客户端进行鉴权和授权。
连接参数(CleanSession)决定此次会话是否是持久会话(Persistent Session)。
在建立mqtt的连接中,有一些重要的参数:
-
检测心跳ping
在心跳间隔中如果没有数据交互,需要发送心跳报文保活连接,否则连接将会断开. -
会话保持 Clean Session:
对与客户端:可在CONNECT报文中设置CleanSession 标志位,0表示不清理会话,1表示清理会话。若在CONNECT报文中设置CleanSession为0,且服务器回复的CONNACK报文中的Session Present位为1,则表示当前连接将会复用服务器保存的会话。
对于服务端:根据客户端CONNECT报文中的CleanSession 标志位,来决定是否保存此次连接中客户端所订阅的主题的记录,在客户端断开连接后,服务器还得保存后面往该主题发送的消息。
eg:客户端的连接报文中,CleanSession 设置为0,服务端会保存此次客户端订阅的所有主题,连接断开后,仍然保存订阅的消息。在客户端下线,如果服务端接收到这些主题的消息,服务器会保存这些主题的消息,在客户端上线后,再往客户端推送这些主题的消息。
3. MQTT 报文介绍
3.1 报文通用格式
了解了mqtt协议的一下特性和通信的模式,下面就了解一下mqtt报文的格式。
mqtt的报文分为三个部分:固定报头,可变报头,有效负载。
固定报头
固定报头,有2~5字节,所有报文都包含固定报头。
固定报头中,
byte1前4bit是PacketType,其存放mqtt报文类型的标识;byte1后4bit只在PUBLISH报文中用到。
从byte2开始保存的是 可变报头 和 有效负载 的长度,可以是1~4个字节,其表示遵循一定的规则。
剩余长度表示的是 可变头部 + 负载 的长度
剩余长度的表示规则
①剩余长度中,每个字节的 权重 都不一样,第一个字节权重为1,第二个字节权重为128,第三个字节权重是128x128=16384,第四个字节的权重是128x128x128=2097152;
②每个字节最高位用于表示该字节后还有没有字节,0表示无,1表示有;
③每个字节的低7位用于表示数值,该数值要与该字节的权重相乘才是长度;
④把每个字节算出的长度相加起来的和就是剩余长度。

十进制数转换成MQTT剩余长度计算举例:
- 十进制64
其剩余长度十六进制表示是0x40;
第一个字节0x40,最高位为0表示其后面还有字节,低七位转成十进制为64表示数值64,数值乘以该字节权重(第一个字节权重为1)得出长度:64x1=64;
所以剩余长度0x40表示的是十进制数:64 - 十进制128
其剩余长度十六进制表示是0x80 0x01
第一个字节0x80=0b1000 0000,最高位为1表示其后面还有字节,低七位转成十进制为0表示数值为0
第二个字节0x01=0b000 0001,最高位为0表示其后面无字节,低七位转成十进制为1表示数值1,数值乘以该字节权重(第二个字节权重为128)得出长度:1x128=128;
所以剩余长度0x80 0x01表示的是十进制数:0+128=128 - 十进制321
其剩余长度十六进制表示是0xc1 0x02
第一个字节0xc1=0b1100 0001,最高位为1表示其后面还有字节,低七位转成十进制为65表示数值65,数值乘以该字节权重(第一个字节权重为1)得出长度:65x1=65;
第二个字节0x02=0b0000 0010,最高位为0表示其后面无字节,低七位转成十进制为1表示数值1,数值乘以该字节权重(第二个字节权重为128)得出长度:2x128=256;
所以剩余长度0xc1 0x02表示的是十进制数:65+128*2=321
剩余长度计算算法如下
可变报头
可变报头,长度不固定。
而且不同类型的mqtt报文,其可变报头都不同。
有效负载
有效负载,长度不固定,部分报文包含有效负载。
3.2 报文类型汇总
在固定报头中,标识了MQTT的报文类型,MQTT中有 14种 报文类型(MQTTV5有15种)。
最常用的报文类型是:连接报文CONNECT、连接回复报文CONNACK、发布报文PUBLISH、订阅报文SUBSCRIBE、订阅回复报文SUBACK、取消订阅报文UNSUBSCRIBE、Ping报文,断开连接报文DISCONNECT。
报文类型如下图:(下表的 值 是填写在MessageType的十进制值)
报文类型写在固定报文的前4bit。(上表的 值 是填写在MessageType的十进制值)
i. CONNECT连接报文
连接报文的报文结构如下图所示:
在可变报头(绿色)中包含了一些需要连接信息的标识,这些标识flag决定了有效负载的内容(如:有效负载中是否有遗嘱,遗嘱信息,用户名,密码)\此次连接的心跳间隔等等.
固定报头

CONNECT连接报文固定报头为:0x10, 0x??,??表示后面的可变报头和有效负载的长度。
可变报头
CONNECT连接报文固定报头有四部分:协议名称,协议版本,一些连接的标志位,保活时长。
协议名称
协议版本
mqtt v3.1.1 版本的 协议等级字段 填写0x04
连接标志位
这些连接标志位标识决定了有效负载的内容。
Clean Session 清理会话位:
置1,
服务器 与 客户端开启新的会话。
置0,
服务器 与 客户端延续旧的会话,若没有旧的会话则开启新会话。延续旧的会话后,客户端无需再次订阅在旧会话中已订阅过的主题,服务器也会把 在客户端不在线这段时间内 接收到的消息发送给客户端。
Will Flag 遗嘱标志位:
置1,
若该CONNECT连接报文成功连接数服务器,遗嘱消息(其定义在连接报文有效负载中)会在连接断开之后发往遗嘱主题(其定义在连接报文有效负载中);在DISCONNECT断开连接报文中可以把遗嘱取消掉。
置0,
无遗嘱。
Will Qos 遗嘱消息等级
与PUBLISH发布报文的Qos字段一个意思,具体请看PUBLISH发布报文的Qos字段的解释。
如果WillFlag置0,那么Will Qos必须置0.
Will Retain遗嘱保留
与PUBLISH发布报文的Retain字段一个意思,具体请看PUBLISH发布报文的Retain字段的解释。
如果WillFlag置0,那么Will Retain必须置0.
User Name Flag用户名标志位
置1,表示使用用户名,有效负载中有用户名字段
置0,表示不用用户名,有效负载中无用户名字段
Password Flag密码标志位
置1,表示使用密码,有效负载中有密码字段
置0,表示不用密码,有效负载中无密码字段
注意:如果用户名标志被设置为0,密码标志也必须设置为0 ;
保活时长
该字段填写的是一个以秒为单位的时间间隔,表示客户端与服务器无报文交互的最大时间间隔。
在MQTT连接建立后,服务器如果没有在 Keep Alive 的1.5倍时间内,收到来自客户端的任何包,则会认为和客户端之间的连接出现了问题,此时服务器便会断开和客户端的连接。
故在连接建立后,客户端需要确保,自己任意两次MQTT协议包的发送间隔不超过 Keep Alive 的值,如果客户端当前处于空闲状态,没有可发送的包,则可以发送 PINGREQ协议包。
当客户端发送PINGREQ协议包后,服务器必须返回一个PINGRESP协议包,如果客户端在一个可靠的时间内,没有收到服务器的PINGRESP协议包,则说明当前存在半连接、或者Broker已经下线、或者出现了网络故障,这个时候,客户端应当关闭当前连接。
这里再啰嗦一下连接报文的遗嘱消息。
遗嘱消息是客户端连接服务端时,告诉服务端的消息.
所以在连接报文中有几个与遗嘱消息相关的参数,这几个参数是客户端对遗嘱消息的描述.
服务端接收到遗嘱消息后,先会把遗嘱消息保存起来.
如果客户端意外断开连接,服务端会把遗嘱消息转发到所有订阅了此遗嘱消息主题的其他客户端.
服务端会一直保存遗嘱消息,直到客户端发送DISCONNECT 类型的消息(即正常离线)才会删除此遗嘱消息.
遗嘱消息不是必须的,取决一个客户端连接保证中的 Will Flag 是否置位.
遗嘱消息也会有retain属性,发布和转发的Qos级别.
有效负载

有效负载一定有Client Indentifier 字段,根据前面的设置的 连接标志位 决定有没有WillTopic, WillMessage, UserName, Password字段。
CONNECT连接报文的有效负载 由一个或多个 数据长度(16bit)+数据(nbit) 的字段组成,如下图所示。
有效负载的一个示例,如下图。
Client Indentifier 客户端标识符
其必须是 1~23个字节的字符(这些字符必须是阿拉伯数字,或大小写的字母),
WillTopic 遗嘱主题
WillFlag=1时,才有该字段。
WillMessage 遗嘱消息
WillFlag=1时,才有该字段。
UserName 用户名
User Name Flag=1时,才有该字段。
Password 密码
User Name Flag=1,Password Flag=1时,才有该字段。
ii. CONNACK连接报文回复
固定报头

CONNACK的 PacketType是2,所以连接报文回复的固定报头是0x20, 0x02
可变报头

byte1的bit0=1,表示服务器保存了旧会话的信息;
byte1的bit0=0,表示服务器没保存了旧会话的信息;客户端需重新订阅主题。
byte2是返回响应码,返回码的值与描述如下图所示。
由上可知,如服务器回复 0x20, 0x02, 0x00, 0x00 则表示服务器已经接收此次连接。
有效负载
CONNACK连接回复报文无有效负载。
iii. PUBLISH 发布报文
发布报文的报文结构如下图所示。
发布操作的对象是某个主题的消息.因此发布报文中重要的参数就是主题名称和消息负载
发布报文的QosLevel 是作用于客户端到服务端消息上行的过程
固定报头

RETAIN
retain=1时,指保留消息的意思,具体意义请看下面示例。
如上图,默认发布和订阅的消息都是同一个主题.
客户端1 发布retain=0的消息(灰色),客户端2在客户端1发布消息之后才向服务端订阅消息,服务端不会转发客户端1上次发布的消息(灰色)
只有在客户端1 再次发布消息(绿色)时,客户端2才能接收到客户端1发布的消息。
客户端1发布retain=1的消息(黄色),服务器会保存该发布的消息和Qos等级。
客户端3 在客户端1发布消息之后才向服务端订阅消息,这时服务端会往客户端3转发客户端1上次发布的消息(黄色)
客户端1 再次发布retain=1的消息(浅绿色),服务器会转发此发布消息,并且保存该发布的数据和Qos等级,丢弃上一个保存的retain=1的消息.
Qos level
在订阅报文和发布报文中,MQTT 协议中定义了三种报文的服务质量。
- Qos0(可能丢失消息)
发送方只发送一次消息,不管对方是否接收到消息。
接收方接收到消息后也不用回应(这里的发送方可以是客户端,也可以是服务端)
发送的消息质量完全由,TCP/IP 网络决定,如果发送工程中,TCP连接断开,数据就会丢失
应用场景是传感器数据的定时发送,个别数据的丢失,对于应用来说不是关键的问题,一般应用都会对一段时间的数据进行整合处理:如取平均值 - Qos1(保证收到消息,但消息可能重复)
发送方要保证接收方一定接收到了消息。
所以服务器收到消息后,一定要回应一个PUBACK的报文应答。
如果发送方没有接收到PUBACK的报文应答,就会再次发送消息,接收方会受到两次消息
应用场景:门开关的状态传感器-门的开关状态一定要无遗失的告诉订阅者,重复的消息可以通过msgId来过滤丢弃。 - Qos2(保证消息既不丢失也不重复)
协议开销很大,略。
DUP Flag
dup 标志指示此消息是否是重复发送的消息。
当 MQTT 报文的接收方没有及时向报文发送发回复确认收到报文时,发送方会以为对方没有收到信息,会再次重复发送 MQTT 报文在重复发送 MQTT 报文时,发送方会将此消息的dup设置为 true。
注意:重发标志只在 QoS 级别大于 0 时使用。
剩余长度
请看 3.1 报文通用格式 的固定报文
可变报头
可变报头中有两个字段,先后分别是TopicName,PacketId。
topicName是主题字符串,发布的消息会发往该主题。
该字段前16bit填写 主题名称字符串 的长度。
PacketId,指发布消息的Id,范围是0~65535。 (QOS > 0 的发布报文才拥有该字段)
可以PacketID来判断是否是重复的发布报文,以此过滤掉Qos1中收到的重复发布的报文。
下图是一个例子,topicName是a/b,packetId是10.
有效负载
即发布报文附带的 应用消息。
其 长度 根据 固定报头的剩余长度 减去 可变报头长度 得到。
iv. PUBACK 发布报文回复
固定报头

可变报头

可变报头内容就是 PUBLISH发布报文的 PackId
有效负载
发布报文回复无有效负载。
v. SUBSCRIBE订阅报文
订阅报文的报文结构如下。
固定报头

可变报头

可变报头是 16bit的 PackId,其范围是0~65535.。
有效负载
有效负载的格式如下。
在有效负载中,可以订阅一个或多个主题。
TopicFilter
订阅主题的字符串。
该字段前16bit的 Length 是填写TopicFilter的长度。
Requseted Qos
订阅的Qos等级。
服务器记录了该订阅的消息,当有该主题的消息到来后,服务器会以订阅报文设置的Qos级别来转发消息。这里的Qos级别是用于服务端到客户端下行转发数据的Qos级别.
因为一次订阅操作可以订阅多个主题,与订阅主题的Qos也可以不一样,因此Qos这个域不是在订阅报文的固定报头,而是紧紧的跟随在主题名之后
订阅多个主题的有效负载示例,如下图

vi. SUBACK 订阅报文回复
固定报头

可变报头

这里的 Packet Id 就是 订阅报文中的 Packet Id
有效负载

ReturnCode是订阅的结果码,其值和描述看上图。
若订阅报文的订阅主题是多个,那么SUBACK有效负载中的ReturnCode也是多个。
下面是有效负载的多个ReturnCode的例子。
vii. PINGREQ 心跳请求报文
心跳请求报文只有固定报头, PING请求报文固定为0xC0, 0x00
viii. PINGRESP 心跳响应报文
心跳响应报文只有固定报头, PING响应报文固定为0xD0, 0x00
ix. DISCONNECT 断开连接报文
断开连接报文也只有固定报头,DISCONNECT 报文固定为0xE0, 0x00
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐


所有评论(0)