* 几乎100%参考
MQTT协议
-
MQTT(Message Queuing Telemetry Transport,消息队列遥测传输协议),是一种基于
发布/订阅
(publish/subscribe
)模式的“轻量级”通讯协议,该协议构建于TCP/IP协议上,由IBM在1999年发布。 -
MQTT 是二进制协议 (http是文本协议)
-
MQTT最大优点在于,用极少的代码和有限的带宽,为连接远程设备提供实时可靠的消息服务。 作为一种低开销、低带宽占用的即时通讯协议,使其在物联网、小型设备、移动应用等方面有较广泛的应用。
发布和订阅
MQTT
使用的发布/订阅消息模式,它提供了一对多的消息分发机制,从而实现与应用程序的解耦。
这是一种消息传递模式,消息不是直接从发送器发送到接收器(即点对点),而是由MQTT server
(或称为 MQTT Broker)分发的。
QoS(Quality of Service levels)
服务质量是 MQTT 的一个重要特性。当我们使用 TCP/IP 时,连接已经在一定程度上受到保护。但是在无线网络中,中断和干扰很频繁,MQTT 在这里帮助避免信息丢失及其服务质量水平。这些级别在发布时使用。如果客户端发布到 MQTT 服务器,则客户端将是发送者,MQTT 服务器将是接收者。当MQTT服务器向客户端发布消息时,服务器是发送者,客户端是接收者。
QoS 0
这一级别会发生消息丢失或重复,消息发布依赖于底层TCP/IP网络。即:<=1
QoS 1
QoS 1 承诺消息将至少传送一次给订阅者
QoS 2
使用 QoS 2,我们保证消息仅传送到目的地一次。为此,带有唯一消息 ID 的消息会存储两次,首先来自发送者,然后是接收者。QoS 级别 2 在网络中具有最高的开销,因为在发送方和接收方之间需要两个流。
MQTT 消息头
- 由 2~5 个字节 组成,包含
- 控制报文类型(Control Packet Type)
- 标志位(Flags)
- 剩余长度(Remaining Length) 。
- 剩余长度采用可变字节编码(Variable Byte Encoding),最多占 4 字节。
#include <stdint.h>
#include <stdbool.h>
// MQTT 控制报文类型(4 bits)
typedef enum {
MQTT_CONNECT = 1, // 客户端请求连接
MQTT_CONNACK = 2, // 服务端确认连接
MQTT_PUBLISH = 3, // 发布消息
MQTT_PUBACK = 4, // 发布确认
MQTT_PUBREC = 5, // 发布收到(QoS 2)
MQTT_PUBREL = 6, // 发布释放(QoS 2)
MQTT_PUBCOMP = 7, // 发布完成(QoS 2)
MQTT_SUBSCRIBE = 8, // 客户端订阅
MQTT_SUBACK = 9, // 订阅确认
MQTT_UNSUBSCRIBE = 10, // 取消订阅
MQTT_UNSUBACK = 11, // 取消订阅确认
MQTT_PINGREQ = 12, // 心跳请求
MQTT_PINGRESP = 13, // 心跳响应
MQTT_DISCONNECT = 14 // 断开连接
} MqttPacketType;
// PUBLISH 标志位(4 bits)
typedef struct {
bool retain; // 保留标志(服务器是否保存消息)
uint8_t qos; // QoS 等级(0/1/2)
bool dup; // 重复发送标志(重传时置 1)
} MqttPublishFlags;
// MQTT 固定头结构体
typedef struct {
uint8_t packet_type; // 控制报文类型(高 4 位)
union {
uint8_t flags; // 通用标志位(低 4 位)
MqttPublishFlags publish_flags; // PUBLISH 专用标志位
};
uint32_t remaining_length; // 剩余长度(可变字节编码)
} MqttFixedHeader;
uint32_t remaining_length : 当前 MQTT 控制报文(从可变头开始到 payload 结束)的总剩余字节数。它的作用类似于 HTTP 中的
Content-Length
,但设计更为紧凑(采用可变字节编码)
### 可变字节编码(Remaining Length 解析)
// 从字节流解析 Remaining Length(返回解析的字节数)
int mqtt_parse_remaining_length(const uint8_t *buf, uint32_t *result) {
uint32_t multiplier = 1;
*result = 0;
int bytes = 0;
do {
*result += (buf[bytes] & 0x7F) * multiplier;
multiplier *= 128;
bytes++;
} while ((buf[bytes - 1] & 0x80) != 0 && bytes < 4);
return bytes;
}
// 将 Remaining Length 编码到字节流(返回写入的字节数)
int mqtt_encode_remaining_length(uint32_t length, uint8_t *buf) {
int bytes = 0;
do {
uint8_t byte = length % 128;
length /= 128;
if (length > 0) byte |= 0x80; // 设置继续位
buf[bytes++] = byte;
} while (length > 0 && bytes < 4);
return bytes;
}
解析 MQTT 固定头
// 解析 MQTT 固定头(返回总字节数)
int mqtt_parse_fixed_header(const uint8_t *buf, MqttFixedHeader *header) {
// 解析报文类型和标志位
header->packet_type = (buf[0] >> 4) & 0x0F; // 高 4 位
header->flags = buf[0] & 0x0F; // 低 4 位
// 解析 Remaining Length
int len_bytes = mqtt_parse_remaining_length(buf + 1, &header->remaining_length);
return 1 + len_bytes; // 固定头总字节数
}
// 示例:打印 PUBLISH 消息头
void print_publish_header(const MqttFixedHeader *header) {
printf("Packet Type: PUBLISH (%d)\n", header->packet_type);
printf("Flags: retain=%d, qos=%d, dup=%d\n",
header->publish_flags.retain,
header->publish_flags.qos,
header->publish_flags.dup);
printf("Remaining Length: %d\n", header->remaining_length);
}
关键点
-
固定头结构:
- 第 1 字节:高 4 位是报文类型,低 4 位是标志位(PUBLISH 有特殊含义)。
- 后续字节:
remaining_length
(可变字节编码)。
-
特殊处理:
- PUBLISH 的
flags
需要单独解析为retain
、qos
、dup
。 remaining_length
最大支持 268,435,455 字节(4 字节编码)。
- PUBLISH 的
MQTT 消息体(Packet Structure)
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
// MQTT 控制报文类型(高4位)
typedef enum {
MQTT_CONNECT = 1,
MQTT_CONNACK = 2,
MQTT_PUBLISH = 3,
// ... 其他类型见前文
} MqttPacketType;
// PUBLISH 标志位(低4位)
typedef struct {
bool retain;
uint8_t qos;
bool dup;
} MqttPublishFlags;
// 固定头(Fixed Header)
typedef struct {
uint8_t packet_type; // 高4位:报文类型
union {
uint8_t flags; // 低4位:通用标志
MqttPublishFlags publish_flags; // PUBLISH 专用标志
};
uint32_t remaining_length; // 剩余长度(已解码为uint32_t)
} MqttFixedHeader;
// 可变头(Variable Header) - 以 PUBLISH 为例
typedef struct {
uint16_t topic_len; // 主题名长度
char *topic; // 主题名(需动态分配)
uint16_t packet_id; // QoS > 0 时的 Packet ID
} MqttPublishVarHeader;
// 有效载荷(Payload) - 以 PUBLISH 为例
typedef struct {
uint8_t *data; // 消息内容(二进制安全)
uint32_t data_len; // 消息长度
} MqttPayload;
// 完整的 MQTT PUBLISH 消息
typedef struct {
MqttFixedHeader fixed_header;
MqttPublishVarHeader var_header;
MqttPayload payload;
} MqttPublishPacket;
重点
- 有效载体
// 有效载荷(Payload) - 以 PUBLISH 为例
typedef struct {
uint8_t *data; // 消息内容(二进制安全)
uint32_t data_len; // 消息长度
} MqttPayload;
- 完整的MQTT publish 消息
// 完整的 MQTT PUBLISH 消息
typedef struct {
MqttFixedHeader fixed_header;
MqttPublishVarHeader var_header;
MqttPayload payload;
} MqttPublishPacket;
MQTT 的保留消息(Retained Message)是什么?有什么用途?
- 服务器保存主题的最后一条消息,新订阅者立即收到。用途:设备上线后快速获取最新状态。
MQTT 的 CONNECT 报文包含哪些关键字段?
- `clientId`、`cleanSession`、`username/password`、`keepAlive`、`willTopic/willMessage`(遗嘱消息)。
Payload消息体
Payload
消息体是MQTT
数据包的第三部分,CONNECT、SUBSCRIBE、SUBACK、UNSUBSCRIBE四种类型的消息 有消息体:
CONNECT
,消息体内容主要是:客户端的ClientID、订阅的Topic、Message以及用户名和密码SUBSCRIBE
,消息体内容是一系列的要订阅的主题以及QoS
。SUBACK
,消息体内容是服务器对于SUBSCRIBE
所申请的主题及QoS
进行确认和回复。UNSUBSCRIBE
,消息体内容是要订阅的主题。
MQTT 的 Clean Session 标志位作用是什么?
- `cleanSession=true`:服务器不保存会话状态(无持久化订阅和未确认消息)。
- `cleanSession=false`:恢复会话(QoS 1/2 消息重传)。
MQTT 的 Keep Alive 机制如何工作?
-客户端定期发送 PINGREQ,服务器响应 PINGRESP。若超时(通常 1.5×keepAlive),服务器断开连接。
MQTT 5.0 相比 3.1.1 有哪些重要改进?
- 新增:原因码(Reason Code)、共享订阅(`$share/group/topic`)、消息过期、流量控制。
- 增强:会话过期(Session Expiry)、用户属性(User Properties)。
MQTT 的 Topic 通配符有哪些?如何使用?
- `+`:单层匹配(如 `sensor/+/temperature`)。
- `#`:多层匹配(如 `sensor/#` 匹配所有子主题)。
MQTT 的遗嘱消息(Last Will)是什么?应用场景?
- 客户端异常断开时,服务器自动发布预设消息。场景:设备离线告警。
如何解决 MQTT 消息堆积问题?
- 服务端:限制消息保留数量/时间(MQTT 5.0 的 `Message Expiry`)。
- 客户端:提高消费速度或使用 QoS 0。
MQTT 在弱网络环境下如何保证可靠性?
- 使用 QoS 1/2 + 持久化会话(`cleanSession=false`)。
- 合理设置 `keepAlive` 和重试机制。 **MQTT 的遗嘱消息(Last Will)是什么?应用场景?**
- 答案:客户端异常断开时,服务器自动发布预设消息。场景:设备离线告警。
如何解决 MQTT 消息堆积问题?
- 服务端:限制消息保留数量/时间(MQTT 5.0 的 `Message Expiry`)。
- 客户端:提高消费速度或使用 QoS 0。
MQTT 在弱网络环境下如何保证可靠性?
- 使用 QoS 1/2 + 持久化会话(`cleanSession=false`)。
- 合理设置 `keepAlive` 和重试机制。 **MQTT 的遗嘱消息(Last Will)是什么?应用场景?**
- 答案:客户端异常断开时,服务器自动发布预设消息。场景:设备离线告警。
如何解决 MQTT 消息堆积问题?
- 服务端:限制消息保留数量/时间(MQTT 5.0 的 `Message Expiry`)。
- 客户端:提高消费速度或使用 QoS 0。
MQTT 在弱网络环境下如何保证可靠性?
- 使用 QoS 1/2 + 持久化会话(`cleanSession=false`)。
- 合理设置 `keepAlive` 和重试机制。