目录
[3.1.1 剩余长度的计算](#3.1.1 剩余长度的计算)
[1.1 固定报头(2~5字节)](#1.1 固定报头(2~5字节))
[1.2 可变报头(固定10字节)](#1.2 可变报头(固定10字节))
③连接标志(1字节,用于判断后续有效载荷中会包含有哪些内容,重要)
[1.3 有效载荷(长度不定)](#1.3 有效载荷(长度不定))
[1.4 示例](#1.4 示例)
[2.1 固定报头](#2.1 固定报头)
[2.2 可变报头](#2.2 可变报头)
[3.1 固定报头](#3.1 固定报头)
[1.1 固定报头](#1.1 固定报头)
[1.2 可变报头](#1.2 可变报头)
[1.3 有效载荷](#1.3 有效载荷)
[2.1 固定报头](#2.1 固定报头)
[2.2 可变报头](#2.2 可变报头)
[2.3 有效载荷](#2.3 有效载荷)
[3.1 固定报头](#3.1 固定报头)
[3.2 可变报头](#3.2 可变报头)
[3.3 有效载荷](#3.3 有效载荷)
[4.1 固定报头](#4.1 固定报头)
[4.2 可变报头](#4.2 可变报头)
[三、发布消息与发布消息确认 (由服务质量等级形成不同组合)(通信方向均为双向)](#三、发布消息与发布消息确认 (由服务质量等级形成不同组合)(通信方向均为双向))
[1.1 固定报头](#1.1 固定报头)
[1.1.1 固定报头中标志位的含义](#1.1.1 固定报头中标志位的含义)
[1.2 可变报头](#1.2 可变报头)
[1.3 有效载荷](#1.3 有效载荷)
[2.发布消息确认(PUBACK QoS=1)](#2.发布消息确认(PUBACK QoS=1))
[2.1 固定报头](#2.1 固定报头)
[2.2 可变报头](#2.2 可变报头)
[3.发布消息收到(PUBREC QoS=2,第一步)](#3.发布消息收到(PUBREC QoS=2,第一步))
[3.1 固定报头](#3.1 固定报头)
[3.2 可变报头](#3.2 可变报头)
[4.发布消息释放(PUBREL QoS=2,第二步)](#4.发布消息释放(PUBREL QoS=2,第二步))
[4.1 固定报头](#4.1 固定报头)
[4.2 可变报头](#4.2 可变报头)
[5.发布消息完成(PUBCOMP QoS=2,第三步)](#5.发布消息完成(PUBCOMP QoS=2,第三步))
[4.1 固定报头](#4.1 固定报头)
[4.2 可变报头](#4.2 可变报头)
[1.1 固定报头](#1.1 固定报头)
[2.1 固定报头](#2.1 固定报头)
[1.QoS 0 -- 最多一次](#1.QoS 0 – 最多一次)
[2.QoS 1 -- 至少一次](#2.QoS 1 – 至少一次)
[3.QoS 2 -- 恰好一次](#3.QoS 2 – 恰好一次)
[补充2:CONNECT报文中Clean Session位(会话清理)与Will Flag位的作用(遗嘱)](#补充2:CONNECT报文中Clean Session位(会话清理)与Will Flag位的作用(遗嘱))
简介:MQTT协议
1.通信流程
MQTT运行在TCP/IP 之上(默认端口1883,加密端口8883),通过定义多种控制报文来完成交互。整个通信流程如下:
-
建立连接:客户端(发布者或订阅者)向Broker发起CONNECT报文,Broker回复CONNACK确认连接。
-
订阅主题:订阅者发送SUBSCRIBE报文,Broker回复SUBACK确认。
-
发布消息:发布者发送PUBLISH报文到指定主题,Broker根据订阅关系将消息转发给所有匹配的订阅者。
-
心跳保活:客户端定期发送PINGREQ,Broker回复PINGRESP,维持连接并检测断线。
-
断开连接:客户端发送DISCONNECT报文优雅关闭。
2.通信过程图示
客户端 Client
使用MQTT的程序或设备。客户端总是通过网络连接到服务端。它可以
- 发布应用消息给其它相关的客户端。
- 订阅以请求接受相关的应用消息。
- 取消订阅以移除接受应用消息的请求。
- 从服务端断开连接。
服务端 Server
一个程序或设备,作为发送消息的客户端和请求订阅的客户端之间的中介。服务端
- 接受来自客户端的网络连接。
- 接受客户端发布的应用消息。
- 处理客户端的订阅和取消订阅请求。
- 转发应用消息给符合条件的已订阅客户端。

3.控制报文的格式
控制报文一般由三部分组成:固定报头、可变报头、有效载荷,其中固定报头是所有报文都必须包含的
3.1固定报头(可占用2~5个字节)
|-----------|-------|-------|-------|-------|-------|-------|-------|-------|
| Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
| byte 1 | MQTT控制报文的类型 |||| 用于指定控制报文类型的标志位 ||||
| byte 2... | 剩余长度 ||||||||
byte1(分高低两部分) :高四位为控制报文的类型(取值范围0~15)
名字 值 报文流动方向 描述 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 禁止 保留 低四位根据不同类型的报文取值各不相同
控制报文 固定报头标志 Bit 3 Bit 2 Bit 1 Bit 0 CONNECT Reserved 0 0 0 0 CONNACK Reserved 0 0 0 0 PUBLISH Used in MQTT 3.1.1 DUP1 QoS2 QoS2 RETAIN3 PUBACK Reserved 0 0 0 0 PUBREC Reserved 0 0 0 0 PUBREL Reserved 0 0 1 0 PUBCOMP Reserved 0 0 0 0 SUBSCRIBE Reserved 0 0 1 0 SUBACK Reserved 0 0 0 0 UNSUBSCRIBE Reserved 0 0 1 0 UNSUBACK Reserved 0 0 0 0 PINGREQ Reserved 0 0 0 0 PINGRESP Reserved 0 0 0 0 DISCONNECT Reserved 0 0 0 0
- DUP1 =控制报文的重复分发标志
- QoS2 = PUBLISH报文的服务质量等级
- RETAIN3 = PUBLISH报文的保留标志
byte2...(用于统计剩余长度,可占1~4字节) :剩余长度用于统计后续可变报头+有效载荷所占用的字节数(每个字节的低7位用于编码数据,最高位是标志位)
字节数 最小值 最大值 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)
3.1.1 剩余长度的计算
- 正推剩余长度
- 反推剩余长度
3.2可变报头
某些MQTT控制报文包含一个可变报头部分。它在固定报头和负载之间。可变报头的内容根据报文类型的不同而不同。
3.3有效载荷
某些MQTT控制报文在报文的最后部分包含一个有效载荷
一、连接、连接确认与断开连接
1.连接(CONNECT):
- 方向:客户端→服务端
- 固定报头、可变报头与有效载荷均有
1.1 固定报头(2~5字节)
|-----------|-------|-------|-------|-------|-------|-------|-------|-------|
| Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
| byte 1 | MQTT报文类型 (1) |||| Reserved 保留位 ||||
| | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 |
| byte 2... | 剩余长度(不定) ||||||||
1.2 可变报头(固定10字节)
可变报头按顺序包含以下字段:
字段 长度 描述 协议名 2 字节长度 + 4 字节字符串 固定为 "MQTT"(十六进制:00 04 4D 51 54 54)协议级别 1 字节 MQTT 3.1.1 固定为 0x04连接标志 1 字节 详见下文 保活时间 2 字节 以秒为单位的最大空闲时间间隔
①协议名(6字节)
结构 :先两个字节表示字符串长度(
0x00 0x04),紧接着 4 字节'M' 'Q' 'T' 'T'。重要性:协议名必须精确匹配,否则服务端会断开连接(或返回 CONNACK 中拒绝)。
②协议级别(1字节)
对于 MQTT 3.1.1,该字段值为
0x04。如果服务端不支持此协议级别,会在 CONNACK 中返回
0x01(不可接受的协议版本)。
③连接标志(1字节,用于判断后续有效载荷中会包含有哪些内容,重要)
该字节的各位含义如下(从高位到低位):
Bit 7 6 5 4 3 2 1 0 含义 User Name Flag Password Flag Will Retain Will QoS (MSB) Will QoS (LSB) Will Flag Clean Session 保留位(必须为0) 各标志位详解:
保留位 (Bit 0)
- 必须为 0,否则服务端将拒绝连接(通常会断开连接)。
Clean Session (清理会话)
若为
1:服务端必须丢弃客户端之前的所有会话状态,并创建一个全新的会话。若为
0:服务端尝试恢复之前与该客户端标识符关联的会话状态(例如未完成的 QoS 1/2 消息)。如果会话不存在,则创建一个新的会话。Will Flag (遗嘱标志)
若为
1:有效载荷中必须包含 遗嘱主题 和 遗嘱消息。服务端在检测到客户端非正常断开(如网络故障、超时)时,会将这些遗嘱消息发布到指定的遗嘱主题上。若为
0:有效载荷中不能出现遗嘱相关字段。Will QoS (遗嘱服务质量)
当 Will Flag = 1 时,这两位表示遗嘱消息的 QoS 等级(0、1 或 2)。
当 Will Flag = 0 时,这两位应被服务端忽略。
Will Retain (遗嘱保留)
当 Will Flag = 1 时,若该位为 1,遗嘱消息作为保留消息发布;若为 0,则不作为保留消息。
当 Will Flag = 0 时,该位被忽略。
Password Flag (密码标志)
若为
1:有效载荷中包含密码字段。若为
0:有效载荷中不包含密码字段。User Name Flag (用户名标志)
若为
1:有效载荷中包含用户名字段。若为
0:有效载荷中不包含用户名字段。
④保活时间(2字节)
两个字节(16 位无符号整数),单位:秒。
含义:客户端与服务端之间允许的最大空闲时间间隔。若在此时间内没有交换任何报文,服务端将断开连接。客户端必须在保活时间内至少发送一个 PINGREQ 报文来维持连接。不管保持连接的值是多少,客户端任何时候都可以发送PINGREQ报文,并且使用PINGRESP报文判断网络和服务端的活动状态。
典型值:60 秒、300 秒等。若设置为 0,表示不启用保活机制。
如果保持连接的值非零,并且服务端在一点五倍的保持连接时间内没有收到客户端的控制报文,它必须 断开客户端的网络连接,认为网络连接已断开。客户端发送了PINGREQ报文之后,如果在合理的时间内仍没有收到PINGRESP报文,它应该关闭到服务端的网络连接。
1.3 有效载荷(长度不定)
- CONNECT报文的有效载荷(payload)包含一个或多个以长度为前缀的字段,可变报头中的标志决定是否包含这些字段。如果包含的话,必须按这个顺序出现:①客户端标识符,②遗嘱主题,③遗嘱消息,④用户名,⑤密码
- 有效载荷按顺序包含以下字段,具体哪些字段出现取决于连接标志中的相应位。++所有字符串的编码均为 UTF-8 ,且使用 2 字节长度 + 字符串内容 的形式++ 。遗嘱消息为 二进制数据,同样采用长度+内容的结构。
| 字段 | 出现条件 | 说明 |
|---|---|---|
| 客户端标识符 (Client Identifier) | 始终存在 | 唯一标识客户端的字符串,长度至少 1 字节(除非服务器允许零长度标识符且 Clean Session=1) |
| 遗嘱主题 (Will Topic) | Will Flag = 1 | UTF-8 字符串,表示遗嘱消息发布的主题 |
| 遗嘱消息 (Will Message) | Will Flag = 1 | 二进制数据,即遗嘱消息的内容 |
| 用户名 (User Name) | User Name Flag = 1 | UTF-8 字符串,用于身份验证 |
| 密码 (Password) | Password Flag = 1 | 二进制数据(MQTT 3.1.1 规范中为 UTF-8 编码,但实际允许任意二进制数据),用于身份验证 |
①客户端标识符
每个客户端连接到服务端时,必须提供一个唯一的客户端标识符。
长度限制:MQTT 3.1.1 协议中未规定最大长度,但通常实现会限制在 23 字节或更长。
如果客户端标识符为空字符串(长度为 0),且 Clean Session = 1,服务端可以为客户端分配一个临时标识符。如果 Clean Session = 0 且标识符为空,服务端通常会拒绝连接(返回 CONNACK 的返回码 0x02 "标识符无效")。
②遗嘱主题与③遗嘱消息
当 Will Flag = 1 时,有效载荷中必须按顺序包含遗嘱主题和遗嘱消息。
遗嘱主题是一个 UTF-8 字符串,必须符合 MQTT 主题命名规则。
遗嘱消息是二进制数据,长度可以为零。
当客户端正常断开连接(发送 DISCONNECT 报文)时,遗嘱消息不会被发布。
④用户名和⑤密码
用户名是 UTF-8 字符串,密码是二进制数据。
如果 User Name Flag = 1 而 Password Flag = 0,则只有用户名出现在有效载荷中。
如果两个标志都为 1,则用户名在前,密码在后。
密码字段的二进制数据允许包含任意字节,包括空字符。
1.4 示例
假设收到如下内容:
10 1C 00 04 4D 51 54 54 04 C2 00 3C 00 00 00 04 55 73 65 72 00 08 50 61 73 73 77 6F 72 64固定头(2字节)
字节序号 十六进制 二进制 含义 1 0x100001 0000报文类型 :高4位 0001表示 CONNECT;低4位为标志位,CONNECT报文固定为0。2 0x1C0001 1100剩余长度:十进制28,表示从下一个字节开始到报文末尾的总字节数。 可变头(10字节)
字节序号 十六进制 二进制 含义 3-4 0x00 0x040000 0000 0000 0100协议名长度:2字节,值为4,表示协议名"MQTT"占4个字节。 5-8 0x4D 0x51 0x54 0x540100 1101 0101 0001 0101 0100 0101 0100协议名:ASCII字符"M"、"Q"、"T"、"T"。 9 0x040000 0100协议级别:4,表示MQTT 3.1.1版本。 10 0xC21100 0010连接标志 :各位含义如下: bit7(User Name Flag)=1 bit6(Password Flag)=1 bit5(Will Retain)=0 bit4-3(Will QoS)=00 bit2(Will Flag)=0 bit1(Clean Session)=1 bit0(Reserved)=0 11-12 0x00 0x3C0000 0000 0011 1100保活时间:2字节,值为60秒。 有效载荷(18字节)
字节序号 十六进制 二进制 含义 13-14 0x00 0x000000 0000 0000 0000客户端标识符长度:长度为0,表示空字符串。 15-16 0x00 0x040000 0000 0000 0100标识用户名长度:4字节。 17-20 0x55 0x73 0x65 0x720101 0101 0111 0011 0110 0101 0111 0010用户名:ASCII字符"U"、"s"、"e"、"r"。 21-22 0x00 0x080000 0000 0000 1000标识密码长度:8字节。 23-30 0x50 0x61 0x73 0x73 0x77 0x6F 0x72 0x640101 0000 0110 0001 0111 0011 0111 0011 0111 0111 0110 1111 0111 0010 0110 0100密码:ASCII字符"P"、"a"、"s"、"s"、"w"、"o"、"r"、"d"。
2.连接确认(CONNACK):
- 方向:服务端→客户端
- 无有效载荷部分
服务端收到 CONNECT 报文后,会进行校验:
检查协议名和协议级别是否正确;
验证连接标志的保留位是否为 0;
检查客户端标识符的有效性;
根据用户名/密码进行认证(若需要)。
如果一切正确,服务端回复 CONNACK 报文,其中包含:
连接确认标志 (Connect Acknowledge Flags):目前只使用第 0 位(Session Present)。若 Clean Session = 0 且存在该客户端的旧会话,Session Present 设为 1;否则为 0。
返回码 (Return Code):
0x00:连接已接受
0x01:协议版本不可接受
0x02:客户端标识符无效
0x03:服务不可用
0x04:无效的用户名或密码
0x05:未授权(MQTT 3.1.1 新增)如果校验失败(例如连接标志的保留位为 1),服务端可以不发送 CONNACK 而直接断开连接。
2.1 固定报头
|-----------|-------|-------|-------|-------|-------|-------|-------|-------|
| Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
| byte 1 | MQTT报文类型 (2) |||| Reserved 保留位 ||||
| | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 |
| byte 2... | 剩余长度 (后续剩余长度固定使用2字节,故剩余长度统计值固定为0x02) ||||||||
| | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
2.2 可变报头
| 字节 | 字段 | 说明 |
|---|---|---|
| 1 | 连接确认标志 (Connect Acknowledge Flags) | 目前只使用第 0 位(Session Present),其余位保留且必须为 0 |
| 2 | 返回码 (Return Code) | 指示连接结果,0x00 表示成功,非 0 表示错误 |
①连接确认标志
该字段只有 bit 0 有意义,称为 Session Present(会话存在标志)。其结构如下:
Bit 7 6 5 4 3 2 1 0 值 0 0 0 0 0 0 0 Session Present
Session Present = 1
表示服务端已经持有了该客户端之前的会话状态(例如之前未完成的 QoS 1/2 消息、订阅信息等)。这通常发生在:
客户端 CONNECT 报文中 Clean Session = 0
服务端能找到与该客户端标识符对应的会话
Session Present = 0
客户端 CONNECT 报文中 Clean Session = 1
本次连接将使用全新的会话,没有之前的会话状态被恢复。
注意:
当连接报文中的标志字段中的 Clean Session = 1 时,服务端必须将 Session Present 设为 0。
如果服务端因为某种原因无法恢复之前的会话(即便 Clean Session = 0),也应当将 Session Present 设为 0,并创建一个新会话。
该标志位对客户端非常重要,客户端可以根据它决定是否需要重新订阅主题(如果之前订阅过)。
②返回码
返回码 十六进制 含义 说明 0 0x00连接已接受 (Connection Accepted) 连接成功建立,服务端接受连接请求。 1 0x01不可接受的协议版本 (Unacceptable Protocol Version) 服务端不支持客户端请求的协议级别(例如 CONNECT 报文中协议级别不是 0x04)。 2 0x02标识符被拒绝 (Identifier Rejected) 客户端标识符不符合服务端的规范(如长度无效、包含非法字符、为空且 Clean Session=0 等)。 3 0x03服务端不可用 (Server Unavailable) 服务端暂时无法处理连接请求(例如过载、维护中)。 4 0x04无效的用户名或密码 (Bad User Name or Password) 用户名或密码格式错误,或认证失败。 5 0x05未授权 (Not Authorized) 客户端未被授权连接(例如 IP 限制、权限不足)。
- 其他返回码值(6~255)均为 保留,服务端不应使用;客户端如果收到应视为错误。
3.断开连接(DISCONNECT):
- 方向:客户端→服务端
- 只有固定报头部分
- 固定报头固定为E0 00
E0:固定报头(类型 14,标志位 0)
00:剩余长度 = 0客户端发送DISCONNECT报文之后:
- 必须关闭网络连接。
- 不能通过那个网络连接再发送任何控制报文。
服务端在收到DISCONNECT报文时:
- 必须丢弃任何与当前连接关联的未发布的遗嘱消息。
- 应该关闭网络连接,如果客户端 还没有这么做。
3.1 固定报头
|---------|-------|-------|-------|-------|-------|-------|-------|-------|
| Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
| byte 1 | MQTT控制报文类型 (14) |||| 保留位 ||||
| | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 |
| byte 2 | 剩余长度 (后续剩余长度固定使用0字节,故剩余长度统计值固定为0x00) ||||||||
| | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
二、订阅与订阅确认,取消订阅与取消订阅确认
1.订阅(SUBSCRIBE)
- 方向:客户端→服务端
- 固定报头、可变报头与有效载荷均有
1.1 固定报头
|---------|-------|-------|-------|-------|-------|-------|-------|-------|
| Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
| byte 1 | MQTT控制报文类型 (8) |||| 保留位 ||||
| | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
| byte 2 | 剩余长度 ||||||||
1.2 可变报头
SUBSCRIBE 报文的可变报头只包含 报文标识符(Packet Identifier),长度为 2 字节。
|---------|-------|-------|-------|-------|-------|-------|-------|-------|
| Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
| byte 1 | 报文标识符 MSB ||||||||
| byte 2 | 报文标识符 LSB ||||||||
客户端必须为每个未完成的 SUBSCRIBE 报文分配一个唯一的报文标识符(2字节数据均非0)。
服务端在 SUBACK 报文中使用相同的标识符来确认该订阅请求(类似点餐号和取餐号要保持一致)。
1.3 有效载荷
|---------------------------------|-------|-------|-------|-------|-------|-------|-------|-------|
| 描述 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
| 主题过滤器 |||||||||
| byte 1 | 长度 MSB ||||||||
| byte 2 | 长度 LSB ||||||||
| byte 3..N | 主题过滤器(Topic Filter) ||||||||
| | 保留位 |||||| 服务质量等级 ||
| byte N+1(服务质量要求(Requested QoS)) | 0 | 0 | 0 | 0 | 0 | 0 | X | X |有效载荷包含一个或多个 主题过滤器(Topic Filter) 及其对应的 请求的 QoS(Requested QoS),按顺序排列。有效载荷由以下三部分组成:
主题过滤器的统计长度:2字节,用于表明主题过滤器的字节数。
主题过滤器 :UTF-8 编码的字符串,可包含通配符(
+单层通配符,#多层通配符)。请求的 QoS:1 字节,表示客户端希望服务端转发消息时使用的最大 QoS 等级(0、1 或 2)。
注意:有效载荷中不包含任何其他字段(如分隔符),订阅条目是连续存放的。
2.订阅确认(SUBACK)
- 方向:服务端→客户端
- 固定报头、可变报头与有效载荷均有
2.1 固定报头
|---------|-------|-------|-------|-------|-------|-------|-------|-------|
| Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
| byte 1 | MQTT控制报文类型 (9) |||| 保留位 ||||
| | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 |
| byte 2 | 剩余长度 ||||||||
2.2 可变报头
SUBACK 报文的可变报头只包含 报文标识符(Packet Identifier),长度为 2 字节,必须与对应的 SUBSCRIBE 报文中的标识符相同。(类似点餐号和取餐号要保持一致)。
|---------|-------|-------|-------|-------|-------|-------|-------|-------|
| Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
| byte 1 | 报文标识符 MSB ||||||||
| byte 2 | 报文标识符 LSB ||||||||
2.3 有效载荷
|---------|-------|-------|-------|-------|-------|-------|-------|-------|
| Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
| | 返回码 ||||||||
| byte 1 | X | 0 | 0 | 0 | 0 | 0 | X | X |允许的返回码值:
- 0x00 - 最大QoS 0
- 0x01 - 成功 -- 最大QoS 1
- 0x02 - 成功 -- 最大 QoS 2
- 0x80 - Failure 失败
0x00, 0x01, 0x02, 0x80之外的SUBACK返回码是保留的,不能使用
3.取消订阅(UNSUBSCRIBE)
- 方向:客户端→服务端
- 固定报头、可变报头与有效载荷均有
3.1 固定报头
|---------|-------|-------|-------|-------|-------|-------|-------|-------|
| Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
| byte 1 | MQTT控制报文类型 (10) |||| 保留位 ||||
| | 1 | 0 | 1 | 0 | 0 | 0 | 1 | 0 |
| byte 2 | 剩余长度 ||||||||
3.2 可变报头
UNSUBSCRIBE 报文的可变报头只包含 报文标识符(Packet Identifier),长度为 2 字节。
|---------|-------|-------|-------|-------|-------|-------|-------|-------|
| Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
| byte 1 | 报文标识符 MSB ||||||||
| byte 2 | 报文标识符 LSB ||||||||
客户端必须为每个未完成的 UNSUBSCRIBE 报文分配一个唯一的报文标识符(2字节数据均非0)。
服务端在 UNSUBACK 报文中使用相同的标识符来确认该取消订阅请求(类似点餐号和取餐号要保持一致)。
3.3 有效载荷
|-----------|-------|-------|-------|-------|-------|-------|-------|-------|
| 描述 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
| 主题过滤器 |||||||||
| byte 1 | 长度 MSB ||||||||
| byte 2 | 长度 LSB ||||||||
| byte 3..N | 主题过滤器(Topic Filter) ||||||||有效载荷包含一个或多个 主题过滤器(Topic Filter) ,按顺序排列。每个主题过滤器由 2 字节长度 + UTF-8 字符串 组成。注意:UNSUBSCRIBE 的有效载荷中不包含 QoS 字段,这与 SUBSCRIBE 报文不同。
主题过滤器的统计长度:2 字节无符号整数,表示主题过滤器的字节数(不是字符数)。
主题过滤器 :UTF-8 编码字符串,可以包含通配符 (
+和#),表示取消订阅该过滤器匹配的所有主题。有效载荷至少包含一个主题过滤器,否则服务端应视为协议违规。
4.取消订阅确认(UNSUBACK)
- 方向:服务端→客户端
- 无有效载荷
4.1 固定报头
|---------|-------|-------|-------|-------|-------|-------|-------|-------|
| Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
| byte 1 | MQTT控制报文类型 (11) |||| 保留位 ||||
| | 1 | 0 | 1 | 1 | 0 | 0 | 0 | 0 |
| byte 2 | 剩余长度 ((后续剩余长度固定使用2字节,故剩余长度统计值固定为0x02)) ||||||||
| | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
4.2 可变报头
UNSUBACK 报文的可变报头只包含 报文标识符(Packet Identifier),长度为 2 字节,必须与对应的 UNSUBSCRIBE 报文中的标识符相同。(类似点餐号和取餐号要保持一致)。
|---------|-------|-------|-------|-------|-------|-------|-------|-------|
| Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
| byte 1 | 报文标识符 MSB ||||||||
| byte 2 | 报文标识符 LSB ||||||||
三、发布消息与发布消息确认 (由服务质量等级形成不同组合)(通信方向均为双向)
1.发布消息(PUBLISH)
1.1 固定报头
|-----------|-------|-------|-------|-------|-------|-------|-------|--------|
| Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
| byte 1 | MQTT控制报文类型 (3) |||| DUP | QoS-H | QoS- | RETAIN |
| | 0 | 0 | 1 | 1 | X | X | X | X |
| byte 2... | 剩余长度 ||||||||
1.1.1 固定报头中标志位的含义
DUP(重复标志)
当客户端或服务端重新发送一个 PUBLISH 报文时,该位设为 1。仅用于 QoS > 0 的消息。
对于原始发送的报文,DUP 必须为 0。接收方不应依赖 DUP 标志来检测重复消息(QoS 2 有专门机制)。
QoS(服务质量)
两位表示消息的交付保证等级:
00:QoS 0 -- 最多一次
01:QoS 1 -- 至少一次
10:QoS 2 -- 恰好一次
11:保留,服务端必须拒绝此类 PUBLISH 报文。RETAIN(保留标志)
若为 1,服务端需存储该消息作为该主题的保留消息 。新订阅该主题的客户端会立即收到此保留消息(即使后续有新粉丝订阅你,也能收到你以前发布的内容,仅限最近的一条)。
若为 0,服务端不存储该消息(除非作为普通消息分发)。
1.2 可变报头
字段 长度 说明 主题名 2 字节长度 + UTF-8 字符串 必须存在,且不能包含通配符( +或#)报文标识符 2 字节(可选) 仅当 QoS > 0 时存在;QoS 0 时不得出现
主题名 :发送方自己的主题名,UTF-8 编码的字符串,表示消息发布的主题。长度字段为 2 字节,表示主题名的字节数(非字符数)。主题名必须符合 MQTT 主题命名规则(不允许通配符)。
报文标识符:16 位无符号整数,范围 1~65535。发送方为每个未完成确认的 PUBLISH 分配唯一标识符。接收方在确认报文(PUBACK、PUBREC、PUBREL、PUBCOMP)中使用相同标识符(用于QoS为1或2时使用,在接收方回复时起到类似点餐号和取餐号的作用)。
1.3 有效载荷
内容:应用消息,可以是任意二进制数据(包括空数据)。
长度:由剩余长度减去可变报头长度决定,最大不超过服务端或客户端约定的最大报文长度(协议未规定上限,但实现有限制)。
格式:无固定结构,完全由应用定义。
2.发布消息确认(PUBACK QoS=1)
2.1 固定报头
|-----------|-------|-------|-------|-------|-------|-------|-------|-------|
| Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
| byte 1 | MQTT报文类型 (4) |||| 保留位 ||||
| | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 |
| byte 2... | 剩余长度(后续剩余长度固定使用2字节,故剩余长度统计值固定为0x02) ||||||||
| | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
2.2 可变报头
PUBACK 报文的可变报头只包含 报文标识符(Packet Identifier),长度为 2 字节,必须与对应的 PUBLISH 报文中的标识符相同。(点餐号和取餐号)
|---------|-------|-------|-------|-------|-------|-------|-------|-------|
| Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
| byte 1 | 报文标识符 MSB ||||||||
| byte 2 | 报文标识符 LSB ||||||||
3.发布消息收到(PUBREC QoS=2,第一步)
3.1 固定报头
|---------|-------|-------|-------|-------|-------|-------|-------|-------|
| Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
| byte 1 | MQTT控制报文类型 (5) |||| 保留位 ||||
| | 0 | 1 | 0 | 1 | 0 | 0 | 0 | 0 |
| byte 2 | 剩余长度 (后续剩余长度固定使用2字节,故剩余长度统计值固定为0x02) ||||||||
| | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
3.2 可变报头
PUBREC 报文的可变报头只包含 报文标识符(Packet Identifier),长度为 2 字节,必须与对应的 PUBLISH 报文中的标识符相同。(点餐号和取餐号)
|---------|-------|-------|-------|-------|-------|-------|-------|-------|
| Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
| byte 1 | 报文标识符 MSB ||||||||
| byte 2 | 报文标识符 LSB ||||||||
4.发布消息释放(PUBREL QoS=2,第二步)
4.1 固定报头
|---------|-------|-------|-------|-------|-------|-------|-------|-------|
| Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
| byte 1 | MQTT控制报文类型 (6) |||| 保留位 ||||
| | 0 | 1 | 1 | 0 | 0 | 0 | 1 | 0 |
| byte 2 | 剩余长度 (后续剩余长度固定使用2字节,故剩余长度统计值固定为0x02) ||||||||
| | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
4.2 可变报头
PUBREL 报文的可变报头只包含 报文标识符(Packet Identifier),长度为 2 字节,必须与对应的 PUBLISH 报文中的标识符相同。(点餐号和取餐号)
|---------|-------|-------|-------|-------|-------|-------|-------|-------|
| Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
| byte 1 | 报文标识符 MSB ||||||||
| byte 2 | 报文标识符 LSB ||||||||
5.发布消息完成(PUBCOMP QoS=2,第三步)
4.1 固定报头
|---------|-------|-------|-------|-------|-------|-------|-------|-------|
| Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
| byte 1 | MQTT控制报文类型 (7) |||| 保留位 ||||
| | 0 | 1 | 1 | 1 | 0 | 0 | 0 | 0 |
| byte 2 | 剩余长度 (后续剩余长度固定使用2字节,故剩余长度统计值固定为0x02) ||||||||
| | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
4.2 可变报头
PUBCOMP 报文的可变报头只包含 报文标识符(Packet Identifier),长度为 2 字节,必须与对应的 PUBLISH 报文中的标识符相同。(点餐号和取餐号)
|---------|-------|-------|-------|-------|-------|-------|-------|-------|
| Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
| byte 1 | 报文标识符 MSB ||||||||
| byte 2 | 报文标识符 LSB ||||||||
四、保活申请与保活回复
1.保活(PINGREQ):
- 方向:客户端→服务端
- 只有固定报头部分
- 固定报头固定为C0 00
C0:固定报头(类型 12,标志位 0)
00:剩余长度 = 0客户端:
客户端在没有其他控制报文需要发送 时,必须按照 CONNECT 报文中指定的**保活时间(Keep Alive)**间隔发送 PINGREQ 报文。
具体规则:从上次发送任何控制报文开始计时,若在保活时间内未发送任何报文,客户端必须发送一个 PINGREQ。
客户端可以在任何时候发送 PINGREQ,不限于空闲时段,例如也可用于主动探测网络连接状况。
客户端发送 PINGREQ 后,应当等待服务端回复 PINGRESP 报文,若在合理时间内未收到,应认为网络连接已失效并主动关闭连接。
服务端:
服务端收到 PINGREQ 后,必须立即回复 PINGRESP 报文 (同样是一个简单的 2 字节报文
D0 00)。服务端通过收到 PINGREQ 来重置保活计时器(即在 1.5 倍保活时间内未收到任何报文时断开连接)。
1.1 固定报头
|---------|-------|-------|-------|-------|-------|-------|-------|-------|
| Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
| byte 1 | MQTT控制报文类型 (12) |||| 保留位 ||||
| | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 |
| byte 2 | 剩余长度 (后续剩余长度固定使用0字节,故剩余长度统计值固定为0x00) ||||||||
| | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
2.保活回复(PINGRESP):
- 方向:服务端→客户端
- 只有固定报头部分
- 固定报头固定为D0 00
D0:固定报头(类型 13,标志位 0)
00:剩余长度 = 0客户端:
客户端发送 PINGREQ 后,应在合理时间内收到 PINGRESP。如果超时未收到,客户端应认为网络连接已中断,并主动关闭连接(或执行重连逻辑)。
客户端收到 PINGRESP 后,可以重置本地的心跳计时器,确认服务端仍然活跃。
服务端:
服务端在收到 PINGREQ 报文后,必须立即回复 PINGRESP 报文,无需等待任何其他条件。
PINGRESP 仅表明服务端收到了 PINGREQ 且连接正常,不携带任何额外信息。
2.1 固定报头
|---------|-------|-------|-------|-------|-------|-------|-------|-------|
| Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
| byte 1 | MQTT控制报文类型 (13) |||| 保留位 ||||
| | 1 | 1 | 0 | 1 | 0 | 0 | 0 | 0 |
| byte 2 | 剩余长度 (后续剩余长度固定使用0字节,故剩余长度统计值固定为0x00) ||||||||
| | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
补充1:服务质量等级QoS
在 MQTT 3.1.1 协议中,QoS(Quality of Service,服务质量) 是核心机制之一,它定义了消息从发布者到订阅者之间的交付保证等级。QoS 不仅影响消息的传输可靠性,还决定了协议层面的确认流程和状态存储要求。
MQTT 提供了三个 QoS 等级,由低到高依次为:
QoS 值 等级名称 交付保证 0 最多一次 (At most once) 消息可能丢失,不会重发 1 至少一次 (At least once) 消息至少到达一次,可能重复 2 恰好一次 (Exactly once) 消息仅且仅到达一次,无重复 QoS 的保证范围是从发布客户端到服务端 ,以及从服务端到订阅客户端。服务端会根据发布消息的 QoS 和订阅时请求的 QoS,最终决定消息实际传递时使用的 QoS(详见后文协商规则)。
每个 QoS 等级都基于报文标识符(Packet Identifier) 和特定的确认报文来实现可靠性。
1.QoS 0 -- 最多一次
流程
发送方(发布客户端或服务端)发送 PUBLISH 报文,其中 QoS = 0 ,不包含报文标识符。
接收方(服务端或订阅客户端)收到后不发送任何确认报文。
消息可能因网络中断、接收方故障等原因丢失,发送方不会重试。
特点
效率最高:无确认开销,报文最小。
可靠性最低:无保障,可能丢失。
适用场景:对数据完整性要求不高、可容忍丢失的场景,如传感器实时数据、状态更新等。
2.QoS 1 -- 至少一次
流程
发送方 :发送 PUBLISH 报文,其中 QoS = 1 ,并分配一个唯一的报文标识符(Packet Identifier)。发送方保存该报文,等待确认。
接收方 :收到 PUBLISH 后,回复 PUBACK 报文,其中包含相同的报文标识符。
发送方:收到 PUBACK 后,确认消息已送达,可以丢弃保存的报文副本。
若发送方在合理时间内未收到 PUBACK,会重发 PUBLISH(DUP 标志位设为 1,表示重复分发),直到收到 PUBACK 或达到重试次数限制。
特点
可靠性:确保消息至少到达一次,但可能产生重复(因为重发机制可能导致接收方收到多次)。
确认开销:需要一次确认(PUBACK),报文标识符管理。
适用场景:需要确保消息送达,但可以容忍重复的业务,如通知、日志等。
3.QoS 2 -- 恰好一次
流程
QoS 2 通过四次握手确保消息既不丢失也不重复。步骤如下:
发送方:发送 PUBLISH 报文,QoS = 2,分配唯一的报文标识符。保存报文。
接收方 :收到 PUBLISH 后,不立即交付给应用 ,而是先回复 PUBREC(发布接收),包含相同的报文标识符。接收方保存该报文标识符,防止重复处理。
发送方 :收到 PUBREC 后,回复 PUBREL(发布释放),包含相同的报文标识符。发送方可以丢弃原始 PUBLISH 副本。
接收方 :收到 PUBREL 后,现在将消息交付给应用 ,然后回复 PUBCOMP(发布完成),包含相同的报文标识符。接收方清除该报文标识符的记录。
若任何一步超时,发送方会重发对应的报文(PUBLISH、PUBREL),直到收到对应的确认。
特点
可靠性最高:确保消息恰好一次送达,无丢失、无重复。
开销最大:四次握手,报文标识符需在两端保持状态。
适用场景:对数据准确性要求极高、不容重复或丢失的场景,如计费、关键指令等。
4.实际应用中的选择建议
场景 推荐 QoS 高频传感器数据,少量丢失可接受 QoS 0 需要确保送达,但允许重复(如通知) QoS 1 关键数据,绝对不能重复或丢失(如支付) QoS 2 需要注意的是,QoS 2 虽然可靠性最高,但会带来更多的网络流量和处理开销。在低带宽或高延迟网络中,应谨慎使用。
补充2:CONNECT报文中Clean Session位(会话清理)与Will Flag位的作用(遗嘱)
作用:控制会话状态的生命周期,决定服务端是恢复之前的会话还是创建一个全新的会话。
Clean Session = 1:
客户端和服务端必须丢弃任何之前的会话信息,并创建一个全新的会话。
连接成功后,客户端不会收到之前未接收的离线消息(因为旧会话已被清除)。
这种模式通常用于每次连接都是短暂、无状态的场景,如移动端 App 每次启动重新订阅。
Clean Session = 0:
服务端必须恢复客户端上次连接时的会话(如果存在)。
如果存在旧会话,客户端会收到在断开期间发布的、符合其订阅的 QoS 1 或 QoS 2 消息。
服务端会保留客户端的订阅信息和未确认的消息,直到会话超时或客户端主动断开且 Clean Session 为 0 时仍会保留。
作用:决定是否在本次连接中设置"遗嘱消息"。如果该位为 1,则 CONNECT 报文中必须包含遗嘱相关字段(Will Topic、Will QoS、Will Retain、Will Message)。
Will Flag = 1:
服务端将存储客户端提供的遗嘱消息(主题、QoS、保留标志、消息内容)。
当服务端检测到客户端非正常断开 (如网络异常、客户端崩溃、会话超时等)时,会主动发布这条遗嘱消息。
正常断开(发送 DISCONNECT 报文)时,服务端会清除遗嘱消息,不会发布。
Will Flag = 0:
- 本次连接不设置遗嘱消息,服务端不存储相关信息。
遗嘱机制常用于设备异常下线通知,例如在物联网中,当设备离线时向其他设备广播"设备已掉线"。
补充4:易忘点
连接报文中:
- 可变报头的连接标志这一字节用于规定后续的有效载荷内容有哪些
- 所有字符串的编码均为 UTF-8 ,且使用 2 字节长度 + 字符串内容 的形式
- 有效载荷必须按这个顺序出现:①客户端标识符,②遗嘱主题,③遗嘱消息,④用户名,⑤密码
发布报文中:
- 固定报头中的RETAIN(保留标志位)置为1时,即使是新粉丝也能收到你从前发布的内容(仅限最近的一条)
- 固定报头中的DUP(重复标志位)为0时,表示这是第一次发送该数据,为1时,表示这是重发的数据(即在QoS>0时,只要发送方第一次发送没有收到接收方的回复,那么后续发送方发送同样的数据时都要将DUP位置1)
- 可变报头中的主题名是发送方自己的主题名
- 有效载荷部分的字符串前不使用两字节进行统计
- 接收方收到的数据不只是有效载荷,而是固定包头、可变报头和有效载荷都收到(可以理解:当同时订阅多个topic时,收到数据需要知道是谁发来的,将所有信息都接收下来便于解析是谁发来的,发送长度是多少以及发了什么)











