MQTT协议详解

该文章已生成可运行项目,

一、MQTT协议概述

1.1 简介

MQTT(Message Queuing Telemetry Transport)是一种轻量级的发布/订阅模式消息传输协议,专为低带宽、高延迟或不可靠网络环境下的物联网(IoT)设备通信而设计。

1.2 协议特性

  • 轻量高效
    • 协议头最小仅 2字节,适合资源受限设备(如传感器)及低带宽网络(如2G/蜂窝网络)。
  • 灵活的服务质量(QoS)
    • QoS 0(最多一次):消息可能丢失,适用于可容忍数据丢失的场景(如环境监测)。
    • QoS 1(至少一次):确保消息到达,但可能重复(需订阅者去重)。
    • QoS 2(恰好一次):通过握手机制保证消息不重复、不丢失,适合关键指令(如支付)。
  • 适应弱网络环境
    • 支持高延迟、不稳定网络,自动处理连接中断(如断线重连、消息缓存)。
  • 持久会话
    • 客户端可设置 Clean Session 标志,断开后代理保留订阅和未接收消息,恢复后继续通信。
  • 遗嘱消息(Last Will)
    • 客户端异常断开时,代理自动发布预设的遗嘱消息,通知其他设备离线状态。

1.3 应用场景

  • 物联网(IoT):传感器数据上报(如温湿度)、设备远程控制。
  • 移动应用:即时通讯、推送通知(如聊天消息)。
  • 工业监控:设备状态实时监控、故障预警。
  • 车联网:车辆位置追踪、状态诊断。

1.4 名词解释

  • Broker(代理服务器)

    MQTT 的中心枢纽,负责接收客户端发布的消息,并将消息路由到订阅了相关主题的客户端,它用于管理客户端连接、消息存储和转发,确保消息按规则传递,常见的 Broker 包括 Mosquitto、EMQX、HiveMQ 等。

  • Client(客户端)

    使用 MQTT 协议的设备或应用程序,可以是消息的发布者(Publisher)或订阅者(Subscriber),例如传感器、手机 App、智能家居设备等。

    • Publisher:向 Broker 发送消息。

    • Subscriber:从 Broker 接收消息。

  • Topic(主题)

    消息的分类标识符,采用层级化字符串结构(用 / 分隔),用于定义消息的类型或路径,home/living_room/temperature,可以采用通配符来订阅多个主题。

    • +:单层通配符(如 home/+/temperature 匹配 home/kitchen/temperature)。

    • #:多层通配符(如 home/# 匹配所有以 home/ 开头的主题)。

  • 订阅(Subscription)
    订阅包含一个主题过滤器(Topic Filter)和一个最大的服务质量(QoS)等级。订阅与单个会话(Session)关联。会话可以包含多于一个的订阅。会话的每个订阅都有一个不同的主题过滤器。

  • 会话(Session)
    客户端和服务端之间的状态交互。一些会话持续时长与网络连接一样,另一些可以在客户端和服务端的多个连续网络连接间扩展。

  • 客户端标识(Clientid)
    ​ 标识不同客户端,一个用户名密码可以创建多个客户端标识。类似王者荣耀里面一个QQ账号可以创建多个区的游戏角色,彼此之间都是互相独立的。

二、MQTT协议原理

MQTT协议采用发布/订阅模式来进行数据通信,不同的客户端通过订阅同一个主题(Topic)就可以实现相互之间的通信,这样的好处是无需知道客户端的IP/端口号就可以实现彼此间的通信。

如图所示,客户端Client1和Client2都订阅了Topic1,Broker负责维护Topic1。因此Client1和Client2都是Topic1的订阅者(Subscriber),当Client1向Topic1发布(Publish)消息时,此时Client1的角色也是发布者(Publisher),由于Client2已经订阅 了Topic1因此Broker会把该消息推送到Client2。Client2向Topic1发布的消息也是一样的流程。这只是简单的通信场景,实际上在MQTT协议中还定义了多个字段,可以在通信过程中实现不同的功能。
在这里插入图片描述

下面以某破站为例解释一下,如图,张三和李四是两位阿婆主,他们之间相互(关注)订阅,当张三发布了一条新动态到服务端,由于李四关注了张三,因此服务端会向李四推送这条动态。同理,当李四发布了新动态,服务端也会将此动态推送给张三。如果只是单向关注的话则不会推送,比如张三关注了李四,但是李四没有回关张三,那么张三发布的所有动态都不会被服务端推送到李四,但是李四发布的动态都会被服务端推送给张三。

image

三、MQTT控制报文

作为一个协议,少不了需要进行各项数据的交互。那么就需要协议报文去实现这个功能。下面就一起看一下MQTT中定义的协议报文以及作用吧。

3.1 控制报文结构

在这里插入图片描述

3.1.1 固定报头

固定报头格式
在这里插入图片描述

固定报头第一个字节高四位(4-7)是控制报文类型,主要有以下类型

名字描述
Reserved0保留
CONNECT1客户端请求连接服务端
CONNACK2连接报文确认
PUBLISH3发布消息
PUBACK4QoS1消息发布收到确认
PUBREC5QoS2发布收到(保证交付第一步)
PUBREL6QoS2发布释放(保证交付第二步)
PUBCOMP7QoS2消息发布完成(保证交互第三步)
SUBSCRIBE8客户端订阅请求
SUBACK9订阅请求报文确认
UNSUBSCRIBE10客户端取消订阅请求
UNSUBACK11取消订阅确认
PINGREQ12心跳请求
PINGRESP13心跳相应
DISCONNECT14客户端断开连接
Reserved15保留

固定报头第一个字节低四位(0-3)是标志位包含每个 MQTT 控制报文类型特定的标志,需要根据报文的不同正确设置该值,如果收到非法的标志,接收者必须关闭网络连接

剩余长度
固定报头第二个字节剩余长度(Remaining Length)表示当前报文剩余部分的字节数,包括可变报头和负载的数据。剩余长度不包括用于编码剩余长度字段本身的字节数。
剩余长度 = 可变长度 + 载荷 剩余长度 = 可变长度 + 载荷 剩余长度=可变长度+载荷

剩余长度的计算

剩余长度字段使用一个变长度编码方案,对小于 128 的值它使用单字节编码(字节间的进位是128)。更大的值按下面的方式处理。低 7 位有效位用于编码数据最高有效位用于指示是否有更多的字节。因此每个字节可以编码 128 个数值和一个延续位(continuation bit)。剩余长度字段最大 4 个字节

76543210
00000000
标志位数值位数值位数值位数值位数值位数值位数值位

各个数量的字节可以表示的数值范围如下表所示。

在这里插入图片描述

每个字节可以表示的10进制数的数值范围是0-255共256个数,由于高位是作为一个标志位,因此在这个场景中一个字节可以表示的十进制数值范围是0-127共128个数。
而标志位是否置位代表着进制,如第一个字节没有进位的话则每位的进位代表着1280=1,
依次类推,如果用到了第二个字节那么第一个字节的标识位则已经置位,第二个字节的进位就是1280+1=128
第三个字节的进位是1280+1+1=16384;
第四个字节的进位是1280+1+1+1=2097152;
为什么是128的n次方呢?
因为7个数值位可以表示27=128,

假设现在一个报文的剩余长度是10000,它的剩余长度计算如下:根据上表我们可以知道需要2个字节才可以表示,因此第一个字节的标志位需要置位,采用除模取余法计算:

10000/128=78.....16

商78填入第二个字节上,将其转换成8位2进制得到0100 1110,因为高位是标志位,因此我们只需要取低7位(100 1110)按顺序填入到第2个字节上就行。

余数16的8位2进制表示为0001 0000,取低7位(001 0000)直接填入到第2个字节上

最后得到的剩余长度两个字节0x90 0x4E

第一个字节(0x90)

76543210
10010000

第二个字节(0x4E)

76543210
01001110

3.1.2 可变报头

可变报头顾名思义,他在MQTT报文中是非必要的,对于一些报文来讲的话是可以为零的也就是没有该字段。某些 MQTT 控制报文包含一个可变报头部分。它在固定报头和负载之间。可变报头的内容根据报文类型的不同而不同。可变报头的报文标识符(Packet Identifier)字段存在于在多个类型的报文里

在这里插入图片描述

对于一些控制报文客户端每次发送一个新的这些类型的报文时都必须携带一个当前未使用的报文标识符(一个非零的 16 位报文标识符)。如果一个客户端要重发这个特殊的控制报文,在随后重发那个报文时,它必须使用相同的标识符。当客户端处理完这个报文对应的确认后,这个报文标识符就释放可重用。

QoS 设置为 0 的 PUBLISH 报文不能包含报文标识符;QoS 1 的 PUBLISH 对应的是 PUBACK;QoS 2 的 PUBLISH 对应的是 PUBCOMP,与 SUBSCRIBE 或UNSUBSCRIBE 对应的分别是 SUBACK 或 UNSUBACK 。发送一个 QoS 0 的 PUBLISH报文时,相同的条件也适用于服务端。

说白了,这个字段类似我们吃麻辣烫的时候服务员会给你分配的那一对牌子,一个用于你取餐一个夹在你的自选的菜品上,两个牌子的号码是一致的,出餐的时候凭借牌子上的号码来取餐,你的牌子和菜品上的牌子号码一致时方可取走餐品,当你取过餐以后该对牌子又可以回收,可以继续分配给下一位客人使用。报文标识符在报文没有处理完毕时客户端和服务端都必须需要一直使用同样的标识符去发送报文,当报文处理完毕后该报文标识符便可重新释放用于下一次通信。

需要报文标识符的控制报文

控制报文是否需要报文标识符
CONNECT不需要
CONNACK不需要
PUBLISH需要(如果 QoS > 0)
PUBACK需要
PUBREC需要
PUBREL需要
PUBCOMP需要
SUBSCRIBE需要
SUBACK需要
UNSUBSCRIBE需要
UNSUBACK 需要需要
PINGREQ不需要
PINGRESP不需要
DISCONNECT不需要

客户端和服务端彼此独立地分配报文标识符。因此,客户端服务端组合使用相同的报文标识符可以实现并发的消息交换。

3.1.3 有效载荷

某些 MQTT 控制报文在报文的最后部分包含一个有效载荷用以承载报文数据,对于 PUBLISH 来说有效载荷就是应用消息。

控制报文有效载荷
CONNECT需要
CONNACK不需要
PUBLISH可选
PUBACK不需要
PUBREC不需要
PUBREL不需要
PUBCOMP不需要
SUBSCRIBE需要
SUBACK需要
UNSUBSCRIBE需要
UNSUBACK 需要不需要
PINGREQ不需要
PINGRESP不需要
DISCONNECT不需要

3.2 控制报文类型

3.2.1 MQTT会话报文

MQTT会话连接和断开涉及到CONNET、CONNACK、DISCONNECT报文

报文类型报文标识符有效载荷报文方向
CONNECT1不需要需要C–>S
CONNACK2不需要不需要S–>C
DISCONNECT14不需要不需要C–>S

网络建立后,由客户端首先向服务端发出第一个数据包CONNECT报文,该CONNECT报文中包含了建立了MQTT会话所需要的各种参数,当服务端收到CONNECT报文会检查是否符合规范,不符合直接关闭连接。符合则进一步校验包里面的参数,如果参数不对则返回一个非零的错误连接码并关闭这个连接。

如需要断开会话客户端直接向服务器发送DISCONNECT报文就行了,客户端发送完DISCONNECT报文后必须关闭网络连接且不能通过那个网络连接再发送任何控制报文 。 服务端在收到DISCONNECT报文时必须丢弃任何与当前连接关联的未发布的遗嘱消息,此时若客户端还未关闭网络连接,服务端应关闭网络连接。

在这里插入图片描述

3.2.1.1 CONNECT

客户端到服务端的网络连接建立后,客户端发送给服务端的第一个报文必须是 CONNECT ,CONNECT报文在同一个网络连接上,客户端只能发送一次 CONNECT 报文。服务端必须将客户端发送的第二个 CONNECT报文当作协议违规处理并断开客户端的连接。

固定报头

在这里插入图片描述

可变报头

CONNECT 报文的可变报头按下列次序包含四个字段:协议名(Protocol Name),协议级别(Protocol Level),连接标志(Connect Flags)和保持连接(Keep Alive)。

在这里插入图片描述

  • 协议名

协议名是表示协议名 MQTT 的 UTF-8 编码的字符串。MQTT 规范的后续版本不会改变这个字符串的偏移和长度。如果协议名不正确服务端可以断开客户端的连接,也可以按照某些其它规范继续处理 CONNECT 报文。对于后一种情况,根据MQTT规范书服务端不能继续处理 CONNECT 报文 。数据包检测工具,例如防火墙,可以使用协议名来识别 MQTT 流量。

写入的数据都需要在前面加上两个字节的字节长度标识,然后将这些数据使用ASCII码的十六进制方式表示进行传输。对于MQTT协议来说这个字段是固定的。

在这里插入图片描述

  • 协议级别

客户端用 8 位的无符号值表示协议的修订版本。对于 3.1.1 版协议,协议级别字段的值是 4(0x04)。如果发现不支持的协议级别,服务端必须给发送一个返回码为 0x01(不支持的协议级别)的 CONNACK 报文响应CONNECT 报文,然后断开客户端的连接

在这里插入图片描述

  • 连接标志

连接标志字节包含一些用于指定 MQTT 连接行为的参数。它还指出有效载荷中的字段是否存在,服务端必须验证 CONNECT 控制报文的保留标志位(第 0 位)是否为 0,如果不为 0 必须断开客户端连接。

在这里插入图片描述

1)清理会话(Clean Session)

它指定了会话状态的处理方式,客户端和服务端可以保存会话状态,以支持跨网络连接的可靠消息传输。

如果清理会话(CleanSession)标志被设置为 0,服务端必须基于当前会话(使用客户端标识符识别)的状态恢复与客户端的通信。如果没有与这个客户端标识符关联的会话,服务端必须创建一个新的会话。

如果清理会话(CleanSession)标志被设置为 1,客户端和服务端必须丢弃之前的任何会话并开始一个新的会话。会话仅持续和网络连接同样长的时间。与这个会话关联的状态数据不能被任何之后的会话重用

2)遗嘱标志(Will Flag)
遗嘱标志(Will Flag)被设置为 1,表示如果连接请求被接受了,遗嘱(Will Message)消息必须被存储在服务端并且与这个网络连接关联。之后网络连接关闭时,服务端必须发布这个遗嘱消息,除非服务端收到DISCONNECT 报文时删除了这个遗嘱消息 。
遗嘱消息发布的条件,包括但不限于:

  • 服务端检测到了一个 I/O 错误或者网络故障。
  • 客户端在保持连接(Keep Alive)的时间内未能通讯。
  • 客户端没有先发送 DISCONNECT 报文直接关闭了网络连接。
  • 由于协议错误服务端关闭了网络连接。

如果遗嘱标志被设置为 1,连接标志中的 Will QoS 和 Will Retain 字段会被服务端用到,同时有效载荷中必须包含 Will Topic 和 Will Message 字段。一旦被发布或者服务端收到了客户端发送的 DISCONNECT 报文,遗嘱消息就必须从存储的会话状态中移除 。

如果遗嘱标志被设置为 0,连接标志中的 Will QoS 和 Will Retain 字段必须设置为 0,并且有效载荷中不能包含 Will Topic 和 Will Message 字段。

3)遗嘱 QoS(Will QoS)

这两位用于指定发布遗嘱消息时使用的服务质量等级。如果遗嘱标志被设置为 0,遗嘱 QoS 也必须设置为 0(0x00)。如果遗嘱标志被设置为 1,遗嘱 QoS 的值可以等于 0(0x00),1(0x01),2(0x02)。它的值不能等于 3。

4)遗嘱保留

遗嘱消息被发布时需要保留,需要指定这一位的值。如果遗嘱标志被设置为 0,代表不需要遗嘱因此不需要遗嘱保留,遗嘱保留(Will Retain)标志也必须设置为 0 。
如果遗嘱标志被设置为 1,如果遗嘱保留设置为0服务端将遗嘱消息作为非保留消息发布,为1则当做保留(Retain)消息发布

5)用户名标志

标志有效载荷中是否携带用户名(UserName)字段,0不携带、1携带

6)密码标志

标志有效载荷中是否携带密码(Password)字段,0不携带、1携带。不能单独设置密码标识为1,密码不能脱离用户名而存在。因此如果用户名标识为0,密码字段也必须为0

  • 保持连接

在这里插入图片描述

保持连接(Keep Alive)是一个以秒为单位的时间间隔,表示为一个 16 位的字,它是指在客户端传输完成一个控制报文的时刻到发送下一个报文的时刻之间允许空闲的最大时间间隔。客户端必须保证相邻的两个控制报文发送的时间间隔不超过保持连接时间。如果没有任何其它的控制报文可以发送,客户端必须发送PINGREQ同时服务端返回PINGRESP 报文报文维持两者间的通信。如果保持连接的值零,并且服务端在1.5倍的保持连接时间内没有收到客户端的控制报文,服务端则认为网络连接已断开直接断开客户端的网络连接。

客户端发送了 PINGREQ 报文之后,如果在合理的时间内仍没有收到 PINGRESP 报文,它应该关闭到服务端的网络连接。保持连接的值为零表示关闭保持连接功能。这意味着,服务端不需要因为客户端不活跃而断开连接。

注意:不管保持连接的值是多少,任何时候,只要服务端认为客户端是不活跃或无响应的,可以断开客户端的连接

有效载荷

CONNECT报文的有效载荷(payload)包含一个或多个以长度为前缀的字段,可变报头中的标志决定是否 包含这些字段。如果包含的话,必须按这个顺序出现:客户端标识符,遗嘱主题,遗嘱消息,用户名,密码

在这里插入图片描述

客户端标识符:每个客户端连接服务端的客户端标识符(ClientId)必须保证唯一,服务端通过客户端标识符 (ClientId) 识别客户端,并使用 ClientId 识别两者之间的 MQTT 会话相关的状态。客户端标识符 (ClientId) 必须存在且必须是CONNECT报文有效载荷的第一个字段 。 客户端标识符采用UTF-8编码字符串 。客户端标识符只能包含大写字母、小写字母和数字(服务端允许不是这个范围的Clientid),服务端允许1到23个字节长的UTF-8编码和编码后超过23个字节的客户端标识符 (ClientId)。

如果客户端提供了一个0字节的客户端标识符 (ClientId) ,服务端会分配唯一的客户端标识符给那个客户端。然后将此客户端标识符视为客户端发送过来的正常处理这个CONNECT报文 , 还必须同时将清理会话标志设置为1。 如果清理会话标志为0,服务端必须发送返回码为0x02(表示标识符不合格)的CONNACK报文响应客户端的CONNECT报文,然后关闭网络连接 。 如果ClientId不合格也将发送返回码为0x02(表示标识符不合格)的CONNACK报文然后关闭网络连接

遗嘱主题 :当遗嘱标志被设置为1,有效载荷的下一个字段是遗嘱主题(Will Topic),遗嘱主题必须是UTF-8编码字符串。

遗嘱消息:当遗嘱标志被设置为1,有效载荷的下一个字段是遗嘱消息,遗嘱消息定义了将被发布到遗嘱主题的应用消息,这个字段由一个2字节的长度和遗嘱消息的有效载荷组成。长度给出了跟在后面的数据(遗嘱消息)的字节数,遗嘱消息被发布到遗嘱主题时,它的有效载荷只包含这个字段的数据部分,不包含开头的两个长度字节。

用户名:用户名(User Name)标志被设置为1,有效载荷的下一个字段就是它。用户名必须是UTF-8编码字符串。服务端可以将它用于身份验证和授权。

密码:密码(Password)标志被设置为1,有效载荷的下一个字段就是它。密码字段包含一个两字节的长度字段,长度表示二进制数据的字节数(不包含长度字段本身占用的两个字节),后面跟着0到65535字节的二进制数据。

只要包含有字节数据的都需要在前面加上两个字节的长度用来表示数据长度响应

3.2.1.2 CONNACK

服务端发送 CONNACK 报文响应从客户端收到的 CONNECT 报文,服务端发送给客户端的第一个报文必须是 CONNACK。如果客户端在合理的时间内没有收到服务端的 CONNACK 报文,客户端应该关闭网络连接。合理的时间取决于应用的类型和通信基础设施。

固定报头

在这里插入图片描述

对于CONNACK报文来说剩余长度的值固定为2

可变报头

在这里插入图片描述

**连接确认标志:**第0 (SP)位 是当前会话(Session Present)标志,第位1-7位是保留位且必须设置为0。

当前会话

​ 1)如果服务端收到清理会话(CleanSession)标志为1的连接,除了将CONNACK报文中的返回码设置为0 之外,还必须将CONNACK报文中的当前会话设置(Session Present)为0。

​ 2)如果服务端收到一个CleanSession为0的连接,当前会话标志的值取决于服务端是否已经保存了ClientId 对应客户端的会话状态。如果保存了会话状态CONNACK报文中的当前会话标志为1 ,反之则为0 。

一旦完成了会话的初始化设置,已经保存会话状态的客户端将期望服务端维持它存储的会话状态。如果客户端从服务端收到的当前的值与预期的不同,客户端可以选择继续这个会话或者断开连接。 如果服务端发送了一个包含非零返回码的CONNACK报文,它必须将当前会话标志设置为0

**连接返回码:**连接返回码字段使用一个字节的无符号值,如果服务端收到一个合法的CONNECT报文,但出于某些原因无法处理它,服务端应该尝试发送一个包含非零返回码(表格中的 某一个)的CONNACK报文。如果服务端发送了一个包含非零返回码的CONNACK报文,那么它必须关闭网络连接 。

返回码响应描述
00x00连接已接受连接已被服务端接受
10x01连接已拒绝,不支持的协议版本服务端不支持客户端请求的MQTT协议级别
20x02连接已拒绝,不合格的客户端标识符客户端标识符是正确的UTF-8编码,但服务 端不允许使用
30x03连接已拒绝,服务端不可用网络连接已建立,但MQTT服务不可用
40x04连接已拒绝,无效的用户名或密码用户名或密码的数据格式无效
50x05连接已拒绝,未授权客户端未被授权连接到此服务器
6-255保留

有效负载

CONNACK报文无有效负载

3.2.1.3 DISCONNECT

DISCONNECT报文是客户端发给服务端的最后一个控制报文。表示客户端正常断开连接。

固定报头

在这里插入图片描述

服务端必须验证所有的保留位都被设置为0,如果它们不为0必须断开连接

可变报头

DISCONNECT没有可变报头

有效负载

DISCONNETC报文没有有效负载

3.2.2 MQTT会话保持

会话保持主要涉及到PINGREQ和PINGRESP两个控制报文。

报文类型报文标识符有效载荷报文方向
PINGREQ12不需要不需要C–>S
PINGRESP13不需要不需要S–>C
3.2.2.1 PINGREQ

PINGREQ报文是在MQTT会话建立后用来在客户端和服务端之间保活,类似数通中OSPF中的Hello包。主要作用就是告诉客户端我还活着,别把会话断开。当然PINGREQ报文一般是在客户端和服务端之间没有其他任何控制报文发送的时候再发送,当有其他控制报文发送的时候不需要进行保活。

固定报头

在这里插入图片描述

可变报头

PINGREQ报文没有可变报头。

有效负载

PINGREQ报文没有有效负载。

3.2.2.2 PINGRESP

PINGRESP是对PINGREQ报文的相应,作用就是服务端告诉客户端我收到了你的PINGREQ报文了,同时也告知客户端自己也活着。通过两次握手继续保持双方之间的会话。

固定报头

在这里插入图片描述

可变报头

PINGRESP报文没有可变报头。

有效负载

PINGRESP报文没有有效负载。

3.2.3 MQTT主题订阅

主题订阅主要涉及到SUBSCRIBE、SUBACK、UNSUBSCRIBE和UNSUBACK四个报文。

报文类型报文标识符有效载荷报文方向
SUBSCRIBE8需要需要C–>S
SUBACK9需要需要S–>C
UNSUBSCRIBE10需要需要C–>S
UNSUBACK11需要不需要S–>C
3.2.3.1 SUBSCRIBE

客户端向服务端发送SUBSCRIBE报文用于创建一个或多个订阅。每个订阅注册客户端关心的一个或多个主题。SUBSCRIBE报文可以为每个订阅指定不同的QoS等级(0-2),服务端根据这个发送应用消息给客户端。

固定报头

在这里插入图片描述

SUBSCRIBE控制报固定报头的第3,2,1,0位是保留位,必须分别设置为0,0,1,0。服务端必须将其它的任何值都当做是不合法的并关闭网络连接

可变报头

可变报头包含报文标识符,客户端在发布消息时必须分配一个唯一的报文标识符,这是报文标识符设置为10时的可变报头。

在这里插入图片描述

有效负载

SUBSCRIBE报文的有效载荷包含了一个主题过滤器列表,它们表示客户端想要订阅的主题。服务端应该支持包含通配符主题过滤器,如果服务端选择不支持包含通配符的主题过滤器必须拒绝任何包含通配符过滤器的订阅请求。每一个过滤器后面跟着一个字节,这个字节被叫做服务质量要求(Requested QoS),它给出了服务端向客户端发送应用消息所允许的最大QoS等级。SUBSCRIBE报文的有效载荷必须包含至少一对主题过滤器 和 QoS等级字段组合。没有有效载荷的 SUBSCRIBE报文是违反协议的 。请求的最大服务质量等级字段编码为一个字节,它后面跟着UTF-8编码的主题名,那些主题过滤器 /和 QoS等级组合是连续地打包

在这里插入图片描述

现版本的协议没有用到服务质量要求(Requested QoS)字节的高六位。如果有效载荷中的任何位是非零值,或者QoS不等于0,1或2,服务端必须认为SUBSCRIBE报文是不合法的并关闭网络连接。

有效载荷示例,该示例中订阅了两个主题分别是一QoS1订阅的a/b和以QoS2订阅的c/d主题。

在这里插入图片描述

服务端收到客户端发送的一个SUBSCRIBE报文时,必须使用SUBACK报文响应 [MQTT-3.8.4-1]。 SUBACK报文必须和等待确认的SUBSCRIBE报文有相同的报文标识符

如果服务端收到一个SUBSCRIBE报文,报文的主题过滤器与一个现存订阅的主题过滤器相同,那么必须 使用新的订阅彻底替换现存的订阅。新订阅的主题过滤器和之前订阅的相同,但是它的最大QoS值可以不 同。与这个主题过滤器匹配的任何现存的保留消息必须被重发,但是发布流程不能中断

服务端发送给客户端的SUBACK报文对每一对主题过滤器 和QoS等级都必须包含一个返回码。这个返回 码必须表示那个订阅被授予的最大QoS等级,或者表示这个订阅失败。服务端可以授予比 订阅者要求的低一些的QoS等级。为响应订阅而发出的消息的有效载荷的QoS必须是原始发布消息的 QoS和服务端授予的QoS两者中的最小值。

3.2.3.2 SUBACK

服务端发送SUBACK报文给客户端,用于确认它已收到并且正在处理SUBSCRIBE报文。SUBACK报文包含一个返回码清单,它们指定了SUBSCRIBE请求的每个订阅被授予的最大QoS等级。也就是说服务端不一定能满足客户端的QoS等级要求。

固定报头

在这里插入图片描述

可变报头

可变报头包含等待确认的SUBSCRIBE报文的报文标识符。该报文表示符需与客户端发送过来的保持一致。

在这里插入图片描述

有效负载

有效载荷包含一个返回码清单。每个返回码对应等待确认的SUBSCRIBE报文中的一个主题过滤器。返回码的顺序必须和SUBSCRIBE报文中主题过滤器的顺序相同

在这里插入图片描述

返回值表

返回值描述
0x00最大Qos 0
0x01最大Qos 1–Success
0x02最大Qos 2–Success
0x80Failure

0x00, 0x01, 0x02, 0x80之外的SUBACK返回码是保留的,不能使用

有效载荷示例:

在这里插入图片描述

3.2.3.3 UNSUBSCRIBE

客户端发送UNSUBSCRIBE报文给服务端,用于取消订阅主题。

固定报头

在这里插入图片描述

UNSUBSCRIBE报文固定报头的第3,2,1,0位是保留位且必须分别设置为0,0,1,0。服务端必须认为任何其 它的值都是不合法的并关闭网络连接

可变报头

可变报头包含一个唯一的报文标识符。

在这里插入图片描述

有效负载

UNSUBSCRIBE报文的有效载荷包含客户端想要取消订阅的主题过滤器列表。UNSUBSCRIBE报文中的 主题过滤器必须是连续打包的,没有有效载荷的UNSUBSCRIBE报文是违反协议的

有效负载 示例:

在这里插入图片描述

UNSUBSCRIBE报文提供的主题过滤器(无论是否包含通配符)必须与服务端持有的这个客户端的当前主 题过滤器集合逐个字符比较。如果有任何过滤器完全匹配,那么它(服务端)自己的订阅将被删除,否则 不会有进一步的处理。

服务端必须发送UNSUBACK报文响应客户端的UNSUBSCRIBE请求。UNSUBACK报文必须包含和 UNSUBSCRIBE报文相同的报文标识符 。即使没有删除任何主题订阅,服务端也必须发 送一个SUBACK响应 。

3.2.3.4 UNSUBACK

服务端发送UNSUBACK报文给客户端用于确认收到UNSUBSCRIBE报文。

固定报头

在这里插入图片描述

可变报头

可变报头和本次会话的UNSUBSCRIBE报文的报文标识符一致

在这里插入图片描述

有效负载

UNSUBACK报文没有有效载荷。

3.2.4 MQTT消息发布

消息发布主要涉及到PUBLISH、PUBACK、PUBREC、PUBREL和PUBCOMP五种控制报文。

报文类型报文标识符有效载荷报文方向
PUBLISH3需要可选C<–>S
PUBACK4需要不需要C<–>S
PUBREC5需要不需要C<–>S
PUBREL6需要不需要C<–>S
PUBCOMP7需要不需要C<–>S
3.2.4.1 PUBLISH

PUBLISH控制报文是指从客户端向服务端或者服务端向客户端传输一个应用消息。

固定报头

在这里插入图片描述

重发标志(DUP):如果DUP标志被设置为0,表示这是客户端或服务端第一次请求发送这个PUBLISH报文。如果DUP标志 被设置为1,表示这可能是一个之前报文请求的重发。 客户端或服务端请求重发一个PUBLISH报文时,必须将DUP标志设置为1。对于QoS 0的消息,DUP标志必须设置为0。 服务端发送PUBLISH 报文给订阅者时,收到(入站)的 PUBLISH报文的 DUP标志的值不会被传播。发送(出站)的PUBLISH 报文与收到(入站)的 PUBLISH报文中的 DUP标志是独立设置的,它的值必须 单独的根据发送(出站)的PUBLISH报文是否是一个重发来确定 。

服务质量等级(QOS等级):这个字段表示应用消息分发的服务质量等级保证。PUBLISH报文不能将QoS所有的位设置为1。如果服务端或客户端收到QoS所有位都为1的PUBLISH 报文,它必须关闭网络连接。

在这里插入图片描述

保留标志(Retain):如果客户端发给服务端的 PUBLISH 报文的保留(RETAIN)标志被设置为 1,服务端必须存储这个应用消 息和它的服务质量等级(QoS),以便它可以被分发给未来的主题名匹配的订阅者 。一个 新的订阅建立时,对每个匹配的主题名,如果存在最近保留的消息,它必须被发送给这个订阅者(就像关注某个公众号默认给你推送的那条消息,欢迎关注XXX公众号)。新的保留消息会替换旧的保留消息

服务端发送PUBLISH报文给客户端时,如果消息是作为客户端一个新订阅的结果发送,它必须将报文的保 留标志设为1。当一个PUBLISH报文发送给客户端是因为匹配一个已建立的订阅时,服 务端必须将保留标志设为0,不管它收到的这个消息中保留标志的值是多少。

保留标志为1且有效载荷为零字节的PUBLISH报文会被服务端当作正常消息处理,它会被发送给订阅主题 匹配的客户端。此外,同一个主题下任何现存的保留消息必须被移除,因此这个主题之后的任何订阅者都 不会收到一个保留消息。当作正常 意思是现存的客户端收到的消息中保留标志未被设置。 服务端不能存储零字节的保留消息。

如果客户端发给服务端的PUBLISH报文的保留标志位0,服务端不能存储这个消息也不能移除或替换任何 现存的保留消息。

剩余长度:可变报头+有效负载

可变报头

可变报头按顺序包含主题名和报文标识符。区分在哪个主题发送以及对应的会话

主题名:主题名(Topic Name)用于识别有效载荷数据应该被发布到哪一个信息通道。 主题名必须是PUBLISH报文可变报头的第一个字段。 PUBLISH报文中的主题名不能包含通配符。

报文标识符:只有当QoS等级是1或2时,报文标识符(Packet Identifier)字段才能出现在PUBLISH报文

可变报头示例:示例中的主题名为 “a/b”,长度等于3,报文标识符为 “10”

在这里插入图片描述

只有当QoS等级是0,PUBLISH报文没有报文标识符(Packet Identifier)字段

有效负载

有效载荷包含将被发布的应用消息。数据的内容和格式是应用特定的。有效载荷的长度这样计算:用固定报头中的剩余长度字段的值减去可变报头的长度。包含零长度有效载荷的PUBLISH报文是合法的。

PUBLISH报文的接收者必须按照根据PUBLISH报文中的QoS等级发送响应

在这里插入图片描述

客户端使用PUBLISH报文发送应用消息给服务端,目的是分发到其它订阅匹配的客户端。 服务端使用PUBLISH报文发送应用消息给每一个订阅匹配的客户端。 客户端使用带通配符的主题过滤器请求订阅时,客户端的订阅可能会重复,因此发布的消息可能会匹配多个过滤器。对于这种情况,服务端必须将消息分发给所有订阅匹配的QoS等级最高的客户端 。服务端之后可以按照订阅的QoS等级,分发消息的副本给每一个匹配的订阅者。 收到一个PUBLISH报文时,接收者的动作取决于QoS等级。 如果服务端实现不授权某个客户端发布PUBLISH报文,它没有办法通知那个客户端。它必须按照正常的 QoS规则发送一个正面的确认,或者关闭网络连接。

3.2.4.2 PUBACK

PUBACK报文是对QoS 1等级的PUBLISH报文的响应。

固定报头

在这里插入图片描述
在这里插入图片描述

剩余长度字段 表示可变报头的长度。对PUBACK报文这个值等于2.

可变报头

可变报头包含了两个字节的报文标识符

在这里插入图片描述

有效负载

PUBACK报文没有有效载荷。

3.2.4.3 PUBREC

PUBREC报文是对QoS等级2的PUBLISH报文的响应。它是QoS 2等级协议交换的第二个报文。

固定报头

在这里插入图片描述

可变报头

可变报头包含了两个字节的报文标识符

在这里插入图片描述

有效负载

PUBACK报文没有有效载荷。

3.2.4.4 PUBREL

PUBREL报文是对PUBREC报文的响应。它是QoS 2等级协议交换的第三个报文。

固定报头

PUBREL控制报文固定报头的第3,2,1,0位是保留位,必须被设置为0,0,1,0。服务端必须将其它的任何值 都当做是不合法的并关闭网络连接

在这里插入图片描述

可变报头

可变报头包含了两个字节的报文标识符

在这里插入图片描述

有效负载

PUBREL报文没有有效载荷。

3.2.4.5 PUBCOMP

PUBCOMP报文是对PUBREL报文的响应。它是QoS 2等级协议交换的第四个也是最后一个报文。

固定报头

在这里插入图片描述

可变报头

可变报头包含了两个字节的报文标识符,该值与PUBREL的报文标识符保持一致

在这里插入图片描述

有效负载

PUBCOMP报文没有有效载荷。

3.2.4.6 QoS协议流程

QoS 0协议流程

消息的分发依赖于底层网络的能力。接收者不会发送响应,发送者也不会重试。消息可能送达一次也可能 根本没送达。客户端将PUBLISH报文的QoS字段和DUP字段都设置为0发送给服务端,然后服务端将该报文路由给订阅了该主题的其他客户端,该QOS等级的报文不需要接收者回复响应,发布者也不需要对报文进行重发。

在这里插入图片描述

QoS 1协议流程

QoS 1确保消息至少送达一次。QoS 1 的 PUBLISH 报文的可变报头中包含一个报文标识符,需要 PUBACK报文确认。

在这里插入图片描述

发送者每次发送新的消息都必须使用未经使用的报文标识符,发送的PULISH报文的QoS字段和DUP字段分别设置为1和0,当收到接收者回应的PUBACK报文本次会话才算结束,释放报文标识符。发送方在等待上次会话回复PUBACK报文时,同时可以发送其他使用不同的报文标识符的PUBLISH报文去发送 后续报文。如果接收者在发送了PUBACK报文之后,再次收到包含相同报文标识符的入站PUBLISH报文都将视为一 个新的消息,而不会将其视为上一个消息的重发,并忽略它的DUP标志的值。

QoS 2协议流程

这是最高等级的服务质量,消息丢失和重复都是不可接受的。使用这个服务质量等级会有额外的开销。 QoS 2的消息可变报头中有报文标识符。QoS 2的PUBLISH 报文的接收者使用一个两步确认过程来确认收到。

在这里插入图片描述

发布者发送的PUBLISH报文必须包含唯一的报文标识符且报文的QoS等于2,,DUP等于0。当发布者把该PUBLISH报文发送出去后在没有收到接收者回复的PUBREC报文都将视该PUBLISH报文为未确认状态,接收者在收到PUBLISH报文后回复PUBREC报文且报文标识符和收到的PUBLISH报文一致,发布者接收到PUBREC报文后需要回复一个PUBREL,该PUBREL报文携带的报文标识符和原始PUBLISH报文应保持一致,在未收到接收者回复的PUBCOMP报文前将其视作未确认状态,一旦发布者发送了PUBREL报文后就不能再重发PUBLISH报文了(因为此时已经可以确定已经有接收者收到了PUBLISH报文),当接收者发送了PUBCOMP报文后,后续收到的具有相同报文标识符的报文都将视为新的发布(不会认为是重发的)。

注意:发送者在等待确认时可以使用不同的报文标识符发送后续的PUBLISH报文,一旦发送者收到PUBCOMP报文,这个报文标识符就可以重用。

四、MQTT协议实例

博主在MQTT服务器上创建了两个用户

usernamepassword
vaevae0514
jackjack123

4.1 连接服务器

4.1.1 构造CONNECT报文

  • 无认证
固定报头
10 16 # 报文ID+剩余长度
可变报头
00 04 4D 51 54 54 #2字节长度标识+协议名MQTT
04 #协议级别固定取4
00 #连接标志 保留位0;不清理会话;无遗嘱标志;无遗嘱QoS;遗嘱不保留,不使用用户名密码
00 64 #保活时间为100s
有效载荷
00 0A 63 6C 69 65 6E 74 5F 31 31 31 #10字节长度+clientid为client_111
遗嘱主题、消息和用户名密码都没有
剩余字节长度为22,即16完整报文如下
10 16
00 04 4D 51 54 54
04
00
00 64
00 0A 63 6C 69 65 6E 74 5F 31 31 31 

在这里插入图片描述

直接连接上,且参数和报文中设置的一样

  • 有认证

在这里插入图片描述

当我们使用用户名密码进行认证,此时再次上面无认证的空用户名密码连接服务器会返回一个非零的错误码,这里返回的是0x04,查询得知是用户名或密码的数据格式无效。

因此在连接服务器的时候需要带上用户名密码vae/vae0514,再次构造一个CONNECT报文

固定报头
10 16 # 报文ID+剩余长度
可变报头
00 04 4D 51 54 54 #2字节长度标识+协议名MQTT
04 #协议级别固定取4
C0 #连接标志 使用用户名密码 其他为0
00 64 #保活时间为100s
有效载荷
#如果还包含其他的必须按这个顺序填入(客户端标识符,遗嘱主题,遗嘱消息,用户名,密码)
00 09 76 61 65 31 32 33 34 35 36 #2字节长度(9)+clientid为vae123456
00 03 76 61 65 #2字节长度(3)+用户名为vae
00 07 76 61 65 30 35 31 34  #2字节长度(7)+密码为vae0514

CONNECT报文&auth
10 23

00 04 4D 51 54 54
04
C0
00 64

00 09 76 61 65 31 32 33 34 35 36 
00 03 76 61 65 
00 07 76 61 65 30 35 31 34

发送报文

在这里插入图片描述

成功以用户名vae/vae0514连接到服务端

在这里插入图片描述

4.1.2 返回CONNECT报文

第一次应答CONNACK

# 此时返回的报文代码为0x02(CONNACK),剩余长度固定为2,SP为0因为是首次连接且sessionclean设置0
20 02 00 00  

第二次应答CONNACK

# 此时返回的报文代码为0x02,剩余长度固定为2,SP为1因为是已经连接过了连接且session clean设置0
20 02 01 00

4.1.3 构造DISCONNECT报文

E0 00 #报文代码+剩余长度

4.2 连接保活

4.2.1 构造PINGREQ报文

根据我设置的保活时间,如果在40s*1.5后,也就是60s后没有控制报文也没有保活包发送,连接就会断开

在这里插入图片描述

下图是在60s后截的图,连接已经断开(手慢了,本来想截到15:02:05的)

在这里插入图片描述

# 报文代码为12,只有固定报头,且保留位固定全部设置为0和无剩余长度,当服务端收到PINGREQ报文时会重置保活时间
C0 00

4.2.2 服务端返回PINGRESP报文

# PINGRESP报文的代码为13,只有固定报头,且保留位全置0
D0 00

4.3 主题订阅

4.3.1 订阅

示例:分别设置下面的主题

  • news
    • science
    • goverment
    • economy
    • education
    • military
      • china
      • usa

1)client1订阅了news/science/china、news/economy/#

2)client2订阅了news/+/usa、news/education/china

4.3.1.1 构造SUBSCRIEB报文
  • 构造client_111的SUBSCRIEB报文
固定报头

82 28 #报文代号8以及固定的低四位0010+剩余长度(40)

可变报头
11 11 # 设置唯一的报文标识符11 11

有效载荷

00 0E 6E 65 77 73 2F 65 63 6F 6E 6F 6D 79 2F 23 00 #2字节长度(14)+news/science/china+QoS
00 12 6E 65 77 73 2F 65 63 6F 6E 6F 6D 79 2F 63 68 69 6E 61   #2字节长度(18)+news/economy/#
00  #你想要的QoS,服务端不一定满足

客户端发送的SUBSCRIBE报文
82 28
11 11
00 0E 6E 65 77 73 2F 65 63 6F 6E 6F 6D 79 2F 23 00
00 12 6E 65 77 73 2F 65 63 6F 6E 6F 6D 79 2F 63 68 69 6E 61 00

服务端返回的SUBACK报文
90 04 11 11 00 00 # 报文代码0x09 保留位置0+剩余长度(4)+报文标识符+第一个主题过滤器授权的QoS+第二个主题过滤器授权的QoS

SUBCRIBE报文中包含了预期需要服务端授予的QoS等级,其中可选0、1、2,0是默认值,不管如何0级别肯定能满足,级别1和级别2服务端不一定能满足,低QoS级别的主题不能接受以高QoS级别去接收消息,即qos0级别主题的客户端即使收到了qos2级别的消息也只能以qos0级别接收

在这里插入图片描述

  • 构造client_222的SUBSCRIEB报文
固定报头

82 26 #报文代号8以及固定的低四位0010+剩余长度38

可变报头
22 22 # 设置唯一的报文标识符11 11

有效载荷

00 0A 6E 65 77 73 2F 2B 2F 75 73 61 01 #2字节长度(10)+news/+/usa+QoS
00 14 6E 65 77 73 2F 65 64 75 63 61 74 69 6F 6E 2F 63 68 69 6E 61 00 #2字节长度(20)+news/education/china+QoS

客户端发送的SUBSCRIBE报文
82 26
22 22
00 0A 6E 65 77 73 2F 2B 2F 75 73 61 01 
00 14 6E 65 77 73 2F 65 64 75 63 61 74 69 6F 6E 2F 63 68 69 6E 61 00

服务端返回的SUBACK报文
90 04 22 22 01 00 # 报文代码0x09 保留位置0+剩余长度(4)+报文标识符+第一个主题过滤器授权的QoS+第二个主题过滤器授权的QoS

在这里插入图片描述

4.3.1.2 服务端返回的SUBACK报文

client_111的订阅确认报文

90 04 11 11 00 00 # 报文代码0x09 保留位置0+剩余长度(4)+报文标识符+第一个主题过滤器授权的QoS+第二个主题过滤器授权的QoS

client_222的订阅确认报文

90 04 22 22 01 00 # 报文代码0x09 保留位置0+剩余长度(4)+报文标识符+第一个主题过滤器授权的QoS+第二个主题过滤器授权的QoS

4.3.2 取消订阅

还是以订阅的例子来看:

1)client1订阅了news/science/china、news/economy/#,现在将全部主题取消

2)client2订阅了news/+/usa、news/education/china,现在需要取消news/education/china

4.3.2.1 构造SUBSCRIEB报文
  • 构造client_111的UNSUBSCRIBE报文
固定报头

A2 26 #报文代号10以及固定的低四位0010+剩余长度(38)

可变报头
11 11 # 设置唯一的报文标识符11 11

有效载荷
00 0E 6E 65 77 73 2F 65 63 6F 6E 6F 6D 79 2F 23 #2字节长度(14)+news/+/usa
00 12 6E 65 77 73 2F 65 63 6F 6E 6F 6D 79 2F 63 68 69 6E 61 #2字节长度(18)+news/education/china

客户端发送的UNSUBSCRIBE报文
A2 26
11 11
00 0E 6E 65 77 73 2F 65 63 6F 6E 6F 6D 79 2F 23
00 12 6E 65 77 73 2F 65 63 6F 6E 6F 6D 79 2F 63 68 69 6E 61



服务端返回的UNSUBACK报文
B0 02 11 11 #报文编码11 保留位置0+剩余长度2+报文标识符

初始时client_111订阅的主题列表

在这里插入图片描述

client_111订阅的两个主题全部都取消了

在这里插入图片描述

  • 构造client_222的UNSUBSCRIBE报文
固定报头
A2 18 #报文代号8以及固定的低四位0010+剩余长度(24)

可变报头
22 22 # 设置唯一的报文标识符11 11

有效载荷
00 14 6E 65 77 73 2F 65 64 75 63 61 74 69 6F 6E 2F 63 68 69 6E 61 #2字节长度(20)+news/education/china

客户端发送的SUBSCRIBE报文
A2 18
22 22
00 14 6E 65 77 73 2F 65 64 75 63 61 74 69 6F 6E 2F 63 68 69 6E 61


服务端返回的SUBACK报文
B0 02 00 22  #报文编码11 保留位置0+剩余长度2+报文标识符

初始时client_222订阅的主题列表

在这里插入图片描述

client_222订阅的news/education/china主题被取消了

在这里插入图片描述

4.3.2.2 服务端返回的UNSUBACK报文

client_111的订阅取消确认报文

B0 02 11 11 #报文编码11 固定位置+剩余长度2+报文标识符

client_222的订阅取消确认报文

B0 02 00 22  #报文编码11 固定位置+剩余长度2+报文标识符

4.4 消息发布与接收

4.4.1 QoS 0等级

client_111构造PUBLISH报文,client_222使用mqttx客户端软件模拟,现在双方都订阅了news/economy/usa。client_111在news/economy/usa发布了一则消息raise tariffs(提高关税)。

订阅报文(如需要用到订阅报文)

订阅news/economy/usa
82 15
11 11
00 10 6E 65 77 73 2F 65 63 6F 6E 6F 6D 79 2F 75 73 61 
00 #请求服务端分配的QOS级别

构造QoS 0等级的PULISH报文

固定报头(DUP+2bitQoS+Retain)
30 1F #可选项都为0+剩余长度

可变报头
00 10 6E 65 77 73 2F 65 63 6F 6E 6F 6D 79 2F 75 73 61 #2字节长度+news/economy/usa



有效载荷
raise tariffs
72 61 69 73 65 20 74 61 72 69 66 66 73 #raise tariffs(不需要字节长度)


PUBLISH报文
30 1F
00 10 6E 65 77 73 2F 65 63 6F 6E 6F 6D 79 2F 75 73 61
72 61 69 73 65 20 74 61 72 69 66 66 73

在这里插入图片描述

client_222收到了client_111发布的消息(raise tariffs)
在这里插入图片描述

4.4.2 QoS 1等级

本例其他条件同上,由client_111发布QoS级别为1的消息。

PS:必须订阅QOS1级别或以上的主题

订阅报文(如需要用到订阅)

订阅news/economy/usa
82 15
11 11
00 10 6E 65 77 73 2F 65 63 6F 6E 6F 6D 79 2F 75 73 61 
01 #请求服务端分配的QOS级别
4.4.2.1 及时响应

构造PUBLISH QoS 1的报文

# 固定报头(DUP+2bitQoS+Retain)
32 22 #报文ID+qos1+剩余长度

# 可变报头
00 10 6E 65 77 73 2F 65 63 6F 6E 6F 6D 79 2F 75 73 61 #2字节长度+news/economy/usa
00 01 #报文标识符

# 有效负载
71 6F 73 20 69 73 20 6C 65 76 65 6C 20 31  #qos is level 1

# PUBLISH QoS 1的报文
32 22
00 10 6E 65 77 73 2F 65 63 6F 6E 6F 6D 79 2F 75 73 61
00 01
71 6F 73 20 69 73 20 6C 65 76 65 6C 20 31  

发送报文
在这里插入图片描述

此时client_222收到QOS级别1的PUBLISH消息会回复PUBACK消息对QOS级别1进行确认

40 02 00 01 # 4代表PUBACK报文,保留位全0,0001是报文标识符

在这里插入图片描述

4.4.3.1 超时或不响应

现在使用client_111充当发布者和订阅者,当收到QOS级别1的PUBLISH消息不会自动回复PUBACK消息,就可以模拟收不到PUBACK的情况。

在这里插入图片描述

超时后会重新发送QOS1的PUBLISH消息

3A # PUBLISH重发消息,dup、qos、retain全部置1
22 # 剩余长度
00 10 6E 65 77 73 2F 65 63 6F 6E 6F 6D 79 2F 75 73 61 #2字节长度+news/economy/usa
00 01 # 报文标识符
71 6F 73 20 69 73 20 6C 65 76 65 6C 20 31 #qos is level 1

4.4.3 QoS 2等级

本例其他条件同上,由client_111发布QoS级别为1的消息。

PS:必须订阅QOS2级别的主题

订阅报文(如需用到订阅)

订阅news/economy/usa
82 15
11 11
00 10 6E 65 77 73 2F 65 63 6F 6E 6F 6D 79 2F 75 73 61 
02 #请求服务端分配的QOS级别
4.4.3.1 及时响应

构造PUBLISH QoS 2的报文

# 固定报头(DUP+2bitQoS+Retain)
34 22 #报文ID+qos2+剩余长度

# 可变报头
00 10 6E 65 77 73 2F 65 63 6F 6E 6F 6D 79 2F 75 73 61 #2字节长度+news/economy/usa
00 01 #报文标识符

# 有效负载
71 6F 73 20 69 73 20 6C 65 76 65 6C 20 32 #qos is level 2

# PUBLISH QoS 2的报文
34 22
00 10 6E 65 77 73 2F 65 63 6F 6E 6F 6D 79 2F 75 73 61
00 01
71 6F 73 20 69 73 20 6C 65 76 65 6C 20 32 

PUBLISH-PUBREC

在这里插入图片描述

服务端返回的PUBREC

50 02 00 01# 前2两个字节是固定的,后两个字节是报文标识符

PUBREL-PUBCOMP

构造PUBREL报文

# 固定报头(DUP+2bitQoS+Retain)
62 02 # 固定的值

# 可变报头
00 01 # 报文标识符(同首次发送的PUBLISH中的报文标识符)


PUBREL报文
62 02 00 01

在这里插入图片描述

服务端返回的PUBCOMP报文

70 02 00 01 # 前2两个字节是固定的,后两个字节是报文标识符

client_222收到了qos2级别的消息

在这里插入图片描述

4.4.3.2 超时或者不响应

现在由client_222向client_111订阅的主题发送qos2级别的报文
在这里插入图片描述

当client_111收到client_222发布的qos2级别的PUBLISH报文后不响应一个PUBREC报文,此时client_222会一直向client_111重复发送该PUBLISH报文。
在这里插入图片描述

分析一下重发的PUBLISH报文

# 固定报头(DUP+2bitQoS+Retain)
3C 21
# 发现DUP置位了,代表这是一个重发的消息,QOS级别为2

# 可变报头
00 10 6E 65 77 73 2F 65 63 6F 6E 6F 6D 79 2F 75 73 61 # 两个字节长度(16)+news/economy/usa
00 01 # 报文标识符


# 有效负载
72 61 69 73 65 20 74 61 72 69 66 66 73 # raise tariffs

client_111收到client_222响应一个PUBREC报文后,此时client_222使用的报文标识符是0001,构造的PUBREC报文为50 02 00 01,服务端返回一个PUBREL报文62 02 00 01

在这里插入图片描述

如果在规定时间内client_111未发送PUBCOMP报文,则会向client_111重复发送PUBREL报文

在这里插入图片描述

直到client_111回复PUBCOMP报文才停止发送

在这里插入图片描述

4.4.4 MQTT扩展用法

4.4.4.1 Retain(保留)

构造Retain位为1的PUBLISH报文

固定报头(DUP+2bitQoS+Retain)
31 1B # Retain置位1+剩余长度

可变报头
00 10 6E 65 77 73 2F 65 63 6F 6E 6F 6D 79 2F 75 73 61 #2字节长度+news/economy/usa



有效载荷
00 0F 48 65 6C 6C 6F 20 49 27 6D 20 54 72 75 6D 70  #2字节长度+Hello I'm Trump


PUBLISH报文
31 23
00 10 6E 65 77 73 2F 65 63 6F 6E 6F 6D 79 2F 75 73 61
00 0F 48 65 6C 6C 6F 20 49 27 6D 20 54 72 75 6D 70

发送报文
在这里插入图片描述

如果有新的客户端一订阅就会推送,发送retain消息服务器不会有任何返回,除非订阅了这个主题就会收到服务器的推送

在这里插入图片描述

4.4.4.2 DUP(重发)

构造DUP位为1的PUBLISH报文

固定报头(DUP+2bitQoS+Retain)
34 1B # Retain置位1+剩余长度

可变报头
00 10 6E 65 77 73 2F 65 63 6F 6E 6F 6D 79 2F 75 73 61 #2字节长度+news/economy/usa



有效载荷
00 0F 48 65 6C 6C 6F 20 49 27 6D 20 54 72 75 6D 70  #2字节长度+Hello I'm Trump


PUBLISH报文
34 25
00 10 6E 65 77 73 2F 65 63 6F 6E 6F 6D 79 2F 75 73 61
00 AA
00 0F 48 65 6C 6C 6F 20 49 27 6D 20 54 72 75 6D 70

返回PUBREC包,重发是对QoS 2级别的PUBLISH报文进行重发的,当客户端发送QoS 2级别的PUBLISH报文但是没收到服务端返回的PUBREC报文就会对PUBLISH报文重发,对该报文不携带报文标识符的话系统会随机分配一个未使用的报文标识符

50 02 00 AA #PUBREC报文+报文标识符
4.4.4.3 Will(遗嘱)

1、Will Flag(遗嘱标志)

如果Will Flag置位1则有效载荷中会包含遗嘱主题和遗嘱消息,这两个其是可以视作特殊的主题和消息,对应的也有QoS级别和保留位。区别是它是在连接的时候就储存在服务器上的,而不是像正常消息由PUBLISH报文发布,且是在特定的时机进行发布。

构造Will Flag位为1的CONNECT报文

固定报文
10 xx 

可变报文
00 04 4D 51 54 54
04 #协议级别
04 # will flag为1
00 64 #保持连接

有效载荷
00 0A 63 6C 69 65 6E 74 5F 31 31 31 #clientid
00 0A 77 69 6C 6C 5F 74 6F 70 69 63 # 2字节长度+will_topic
00 11 49 20 61 6D 20 77 69 6C 6C 20 6D 65 73 73 61 67 65 # 2字节长度+I am will message


CONNECT报文(will flag=110 35

00 04 4D 51 54 54
04 
04 
00 64 

00 0A 63 6C 69 65 6E 74 5F 31 31 31
00 0A 77 69 6C 6C 5F 74 6F 70 69 63
00 11 49 20 61 6D 20 77 69 6C 6C 20 6D 65 73 73 61 67 65 

发送报文

在这里插入图片描述

在client_222上订阅了遗嘱主题(will_topic),当client_111断开连接后(client_111嘎掉),CONNECT报文定义的遗嘱消息就会发布在遗嘱主题

在这里插入图片描述

2、Will QoS(遗嘱 QoS)

测试用例说明,本例使用网络调试助手模拟client_111,mqttx模拟client_222,client_111设有遗嘱主题will_topic和遗嘱消息,client_222以qos2级别订阅了client_111的遗嘱主题

0)QoS 0级别的在上一小节

1)QoS 1级别

构造遗嘱消息为QoS 1级别的CONNECT报文

固定报文
10 35 

可变报文
00 04 4D 51 54 54
04 #协议级别
0C # will qos 1+will flag为1
00 64 #保持连接

有效载荷
00 0A 63 6C 69 65 6E 74 5F 31 31 31 #clientid
00 0A 77 69 6C 6C 5F 74 6F 70 69 63 # 2字节长度+will_topic
00 11 49 20 61 6D 20 77 69 6C 6C 20 6D 65 73 73 61 67 65 # 2字节长度+I am will message


CONNECT报文(will flag=1&qos1)
10 35

00 04 4D 51 54 54
04 
0C 
00 64 

00 0A 63 6C 69 65 6E 74 5F 31 31 31
00 0A 77 69 6C 6C 5F 74 6F 70 69 63
00 11 49 20 61 6D 20 77 69 6C 6C 20 6D 65 73 73 61 67 65 

在这里插入图片描述

当client_111断开连接后就可以在client_222收到qos1的遗嘱消息

在这里插入图片描述

2)QoS 2级别

构造遗嘱消息为QoS 2级别的CONNECT报文

固定报文
10 35 

可变报文
00 04 4D 51 54 54
04 #协议级别
14 # will qos 2+will flag为1
00 64 #保持连接

有效载荷
00 0A 63 6C 69 65 6E 74 5F 31 31 31 #clientid
00 0A 77 69 6C 6C 5F 74 6F 70 69 63 # 2字节长度+will_topic
00 11 49 20 61 6D 20 77 69 6C 6C 20 6D 65 73 73 61 67 65 # 2字节长度+I am will message


CONNECT报文(will flag=1&qos2)
10 35

00 04 4D 51 54 54
04 
14 
00 64 

00 0A 63 6C 69 65 6E 74 5F 31 31 31
00 0A 77 69 6C 6C 5F 74 6F 70 69 63
00 11 49 20 61 6D 20 77 69 6C 6C 20 6D 65 73 73 61 67 65 

在这里插入图片描述

当client_111断开连接后就可以在client_222收到qos2的遗嘱消息

在这里插入图片描述

3、Will Retain(遗嘱保留)

构造遗嘱保留置位1的CONNECT报文

固定报文
10 34 

可变报文
00 04 4D 51 54 54
04 #协议级别
24 # will retain为1+will flag为1
00 64 #保持连接

有效载荷
00 0A 63 6C 69 65 6E 74 5F 31 31 31 #clientid
00 0A 77 69 6C 6C 5F 74 6F 70 69 63 # 2字节长度+will_topic
00 10 49 20 61 6D 20 77 69 6C 6C 20 72 65 74 61 69 6E  # 2字节长度+I am will retain



CONNECT报文(will flag=110 34

00 04 4D 51 54 54
04 
24 
00 64 

00 0A 63 6C 69 65 6E 74 5F 31 31 31
00 0A 77 69 6C 6C 5F 74 6F 70 69 63
00 10 49 20 61 6D 20 77 69 6C 6C 20 72 65 74 61 69 6E

起始client_222没有订阅到遗嘱主题

在这里插入图片描述

client_111断开连接触发遗嘱

在这里插入图片描述

当client_222订阅遗嘱主题就会收到保留消息

在这里插入图片描述

4.4.4.4 Session Clean(会话清理)
  • 清理会话

构造session clean置位的CONNECT报文

固定报头
10 16 # 报文ID+剩余长度
可变报头
00 04 4D 51 54 54 #2字节长度标识+协议名MQTT
04 #协议级别固定取4
02 #清理会话
00 64 #保活时间为100s
有效载荷
00 0A 63 6C 69 65 6E 74 5F 31 31 31 #10字节长度+clientid为client_111

CONNECT报文&session clean为1
10 16
00 04 4D 51 54 54
04
02
00 64
00 0A 63 6C 69 65 6E 74 5F 31 31 31
订阅主题news/economy/usa
82 15
11 11
00 10 6E 65 77 73 2F 65 63 6F 6E 6F 6D 79 2F 75 73 61 
00

客户端client_111第一次连接到服务端并且订阅了主题news/economy/usa,此时client_222向该主题发布消息,client_111正常接收到消息。
在这里插入图片描述

断开client_111与服务端的连接后再次连接到服务端.

在这里插入图片描述

再次使用client_222向该主题发布消息,此时client_111原先和服务端的会话被清理了,不再次进行主题订阅是收不到client_222发布的消息。

在这里插入图片描述

  • 不清理会话

构造session clean不置位的CONNECT报文

固定报头
10 16 # 报文ID+剩余长度
可变报头
00 04 4D 51 54 54 #2字节长度标识+协议名MQTT
04 #协议级别固定取4
00 #不清理会话
00 64 #保活时间为100s
有效载荷
00 0A 63 6C 69 65 6E 74 5F 31 31 31 #10字节长度+clientid为client_111

CONNECT报文&session clean为1
10 16
00 04 4D 51 54 54
04
00
00 64
00 0A 63 6C 69 65 6E 74 5F 31 31 31
订阅主题news/economy/usa
82 15
11 11
00 10 6E 65 77 73 2F 65 63 6F 6E 6F 6D 79 2F 75 73 61 
00

客户端client_111第一次连接到服务端并且订阅了主题news/economy/usa,此时client_222向该主题发布消息,client_111正常接收到消息,当client_111断开连接再次连接到服务端.

在这里插入图片描述

再次使用client_222向该主题发布消息,client_111不需要再进行主题订阅就可以收到client_222发布的消息。
在这里插入图片描述

本文是笔者自学总结的,如有错误,烦请各位看官指点一二

本文章已经生成可运行项目
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值