MQTT教程详解
一篇从原理到可运行 Java 代码的 MQTT 完整教程,
MQTT技术点包括:协议核心概念、Broker 选择、Java 依赖、TCP 连接、发布 / 订阅示例、QOS / 遗嘱 / 保留消息、TLS 加密、以及一个可直接跑的 Spring Boot 整合示例。
一、MQTT 是什么(极简理解)
MQTT(Message Queuing Telemetry Transport)是一种专为物联网(IoT)设计的轻量级发布/订阅消息传输协议。
它基于 TCP/IP,核心优势在于低功耗、低带宽占用,非常适合传感器、移动设备等资源受限环境。
轻量级、基于发布 - 订阅模式的消息协议,跑在 TCP 之上,专为 ** 物联网(IoT)** 设计,特点:
- 小带宽、低功耗、网络不稳定也能工作
- Broker(中间服务器)转发消息,客户端之间不直连。
- 核心:Broker(代理服务器)、Topic(主题)、Client(客户端)、Publisher(发布者)、Subscriber(订阅者)
1.1 核心角色
- Broker(代理服务器):消息中转站。负责接收、过滤和转发消息。常见的 Broker消息服务器(如 EMQX、Mosquitto、HiveMQ)。
- Topic(主题):消息的“地址”或“频道”。采用层级结构(如
home/livingroom/temperature),订阅者通过订阅主题来接收消息。 - Client(客户端):发布者(Publisher)或订阅者(Subscriber)。如传感器、手机 App。
- Publisher(发布者):发布消息到某个 Topic。
- Subscriber(订阅者):订阅某个 Topic,接收消息。
1.2. 发布/订阅模式
这是一种解耦的通信模式。发布者和订阅者不需要知道对方的存在,只需与 Broker 交互。
-
工作流程:Client 连接 Broker → 订阅 Topic → 另一 Client 发布消息到该 Topic → Broker 推送给所有订阅者。
1.3 关键特性
- 轻量级:协议头最小仅 2 字节,远小于 HTTP。
- Topic(主题):层级字符串,如
sensor/temp/room1,支持通配符:+:单层通配符,sensor/+/room1#:多层通配符,sensor/#
-
QoS(服务质量):这是 MQTT 的灵魂机制,决定了消息的可靠性。
-
QoS 0(最多一次):发完即忘,可能丢失。适用于传感器周期性上报(丢一两条无所谓)。
-
QoS 1(至少一次):有确认机制,保证送达,但可能重复。适用于控制指令。
-
QoS 2(恰好一次):严格的四步握手,保证不丢不重。适用于金融、计费等关键场景。
-
- Clean Session:
true:断开后 Broker 清空会话,离线消息不收false:断开后 Broker 保留会话,重连后收到离线期间 QoS1/2 消息
- 遗嘱消息(Last Will):客户端异常断开时,Broker 自动发布预设消息,通知其他设备该客户端已离线。
- 保留消息(Retained):最新一条消息被 Broker 保留,新订阅者上线立即收到
二、进阶详解:协议报文与最佳实践
1. 报文结构
MQTT 报文由三部分组成:
-
固定头(Fixed Header):包含报文类型(如 CONNECT, PUBLISH)和标志位。
-
可变头(Variable Header):包含 Topic 名称、Packet ID(用于 QoS > 0)等。
-
载荷(Payload):实际传输的数据(如 JSON 格式的传感器读数)。
2. 连接流程(CONNECT/CONNACK)
客户端发起连接时需携带关键参数:
-
ClientId:唯一标识符。
-
Clean Session:为
true时,Broker 不保存历史会话;为false时,Broker 会保留离线期间的消息(需配合 QoS > 0)。 -
Keep Alive:心跳间隔(秒),用于检测连接是否存活。
3. 主题通配符
-
单层通配符
+:匹配一层目录。例如home/+/temperature可匹配home/livingroom/temperature,但不匹配home/livingroom/device/temperature。 -
多层通配符
#:匹配任意多级目录。例如home/#可匹配所有以home/开头的主题。
4. 安全配置(生产环境必做)
-
认证:在 Broker 端开启用户名/密码认证(EMQX 管理台可直接配置)。
-
加密:使用 TLS/SSL(端口通常为 8883)加密通信链路,防止数据泄露。
三、准备:Broker + Java 依赖
3.1 免费公共 Broker(测试用)
- EMQX:
tcp://broker.emqx.io:1883(无密码) - HiveMQ:
tcp://broker.hivemq.com:1883
本地部署推荐 EMQX(Docker 一键启动):
# 拉取镜像
docker pull emqx/emqx
# 1,运行容器(1883 是 MQTT 端口,18083 是 Web 管理台)
docker run -d --name emqx -p 1883:1883 -p 18083:18083 emqx/emqx:latest
# 2,运行容器(1883 是 MQTT 端口,18083 是 Web 管理台)
docker run -d --name emqx -p 1883:1883 -p 8083:8083 emqx/emqx
启动后访问 http://localhost:18083(账号 admin,密码 public)即可进入管理界面。
3.2. 客户端测试(使用 MQTTX 工具)
-
连接:在 MQTTX 中新建连接,服务器地址填
localhost:1883。 -
订阅:添加订阅,Topic 填
test/topic。 -
发布:在消息发布框输入相同 Topic 和消息内容,点击发送。你将在订阅窗口收到消息
3.3 Maven 依赖(Paho Java 客户端)
<!-- MQTT 客户端 -->
<dependency>
<groupId>org.eclipse.paho</groupId>
<artifactId>org.eclipse.paho.client.mqttv3</artifactId>
<version>1.2.5</version>
</dependency>
四、Java 基础示例(TCP 连接)
4.1 公共常量
public interface MqttConstants {
String BROKER = "tcp://broker.emqx.io:1883"; // 公共Broker
String TOPIC = "java/mqtt/demo";
int QOS = 1; // 推荐QoS1
String CLIENT_ID_PREFIX = "mqtt-java-";
}
4.2 发布者(Publisher)
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import java.util.UUID;
public class MqttPublisher implements MqttConstants {
public static void main(String[] args) {
String clientId = CLIENT_ID_PREFIX + UUID.randomUUID();
try (MqttClient client = new MqttClient(BROKER, clientId, new MemoryPersistence())) {
// 连接配置
MqttConnectOptions options = new MqttConnectOptions();
options.setCleanSession(true);
options.setKeepAliveInterval(60); // 心跳60s
// 连接Broker
client.connect(options);
System.out.println("已连接Broker:" + BROKER);
// 构造消息
String payload = "Hello MQTT! 时间:" + System.currentTimeMillis();
MqttMessage message = new MqttMessage(payload.getBytes());
message.setQos(QOS);
message.setRetained(false); // 不保留消息
// 发布
client.publish(TOPIC, message);
System.out.println("消息已发布:" + payload);
// 断开
client.disconnect();
} catch (MqttException e) {
e.printStackTrace();
}
}
}
4.3 订阅者(Subscriber)
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import java.util.UUID;
public class MqttSubscriber implements MqttConstants {
public static void main(String[] args) {
String clientId = CLIENT_ID_PREFIX + UUID.randomUUID();
try (MqttClient client = new MqttClient(BROKER, clientId, new MemoryPersistence())) {
MqttConnectOptions options = new MqttConnectOptions();
options.setCleanSession(true);
options.setKeepAliveInterval(60);
// 消息回调
client.setCallback(new MqttCallback() {
// 连接丢失
@Override
public void connectionLost(Throwable cause) {
System.out.println("连接丢失:" + cause.getMessage());
}
// 收到消息
@Override
public void messageArrived(String topic, MqttMessage message) throws Exception {
String payload = new String(message.getPayload());
System.out.println("收到消息 -> 主题:" + topic + ",内容:" + payload);
}
// 消息送达确认(QoS1/2)
@Override
public void deliveryComplete(IMqttDeliveryToken token) {
// System.out.println("消息已送达");
}
});
client.connect(options);
System.out.println("已连接Broker,订阅主题:" + TOPIC);
// 订阅
client.subscribe(TOPIC, QOS);
// 阻塞等待消息
Thread.sleep(Long.MAX_VALUE);
} catch (MqttException | InterruptedException e) {
e.printStackTrace();
}
}
}
4.4 运行测试
- 先启动 MqttSubscriber(订阅)
- 再启动 MqttPublisher(发布)
- 订阅者控制台打印:
已连接Broker,订阅主题:java/mqtt/demo 收到消息 -> 主题:java/mqtt/demo,内容:Hello MQTT! 时间:xxx
五、高级特性示例
5.1 遗嘱消息(异常断开通知)
// 在连接选项中设置
options.setWill("java/mqtt/will", "客户端异常断开".getBytes(), 1, false);
5.2 保留消息(新订阅者上线即收)
message.setRetained(true); // 发布时设置
5.3 TLS 加密连接(安全端口 8883)
String BROKER_SSL = "ssl://broker.emqx.io:8883";
// 信任公共证书,直接连接
MqttClient client = new MqttClient(BROKER_SSL, clientId, new MemoryPersistence());
六、Spring Boot 整合 MQTT(生产常用)
6.1 配置文件 application.yml
mqtt:
broker: tcp://broker.emqx.io:1883
topic: java/mqtt/spring
qos: 1
client-id-prefix: mqtt-spring-
6.2 配置类
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.UUID;
@Configuration
public class MqttConfig {
@Value("${mqtt.broker}")
private String broker;
@Value("${mqtt.client-id-prefix}")
private String clientIdPrefix;
@Value("${mqtt.qos}")
private int qos;
@Bean
public MqttClient mqttClient() throws Exception {
String clientId = clientIdPrefix + UUID.randomUUID();
MqttClient client = new MqttClient(broker, clientId, new MemoryPersistence());
MqttConnectOptions options = new MqttConnectOptions();
options.setCleanSession(true);
options.setKeepAliveInterval(60);
client.connect(options);
return client;
}
}
6.3 发布服务
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
@Service
public class MqttPublishService {
@Autowired
private MqttClient mqttClient;
@Value("${mqtt.topic}")
private String topic;
@Value("${mqtt.qos}")
private int qos;
public void publish(String payload) throws Exception {
MqttMessage message = new MqttMessage(payload.getBytes());
message.setQos(qos);
mqttClient.publish(topic, message);
}
}
6.4 订阅监听
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
@Component
public class MqttSubscribeListener implements MqttCallback {
@Autowired
private MqttClient mqttClient;
@Value("${mqtt.topic}")
private String topic;
@Value("${mqtt.qos}")
private int qos;
@PostConstruct
public void init() throws Exception {
mqttClient.setCallback(this);
mqttClient.subscribe(topic, qos);
}
@Override
public void connectionLost(Throwable cause) {
System.out.println("连接丢失:" + cause.getMessage());
}
@Override
public void messageArrived(String topic, MqttMessage message) throws Exception {
String payload = new String(message.getPayload());
System.out.println("Spring 收到消息 -> 主题:" + topic + ",内容:" + payload);
}
@Override
public void deliveryComplete(IMqttDeliveryToken token) {}
}
七、常见问题(FAQ)
- 连接失败:检查 Broker 地址 / 端口、网络防火墙、客户端 ID 唯一。
- 收不到消息:主题名完全匹配、QoS 一致、订阅在发布之前。
- 重复消息:QoS1 正常,业务端需做幂等处理。
八、面试题
Q1:MQTT 和 HTTP 有什么区别?
A:MQTT 是双向通信(服务器可主动推消息),开销极小;HTTP 是请求-响应模式,头部开销大,不适合频繁的小数据量通信。
Q2:QoS 级别如何选择?
A:数据采集(如温度)用 QoS 0;设备控制(如开关灯)用 QoS 1;关键业务(如订单)用 QoS 2。
Q3:如何保证消息顺序?
A:MQTT 协议本身不保证全局顺序。通常做法是单 Topic 单生产者,或由消费者根据消息中的时间戳/序列号自行排序。
Q4:ClientId 重复会怎样?
A:如果两个客户端使用相同的 ClientId 连接,先连接的会被后连接的踢下线。请确保 ClientId 唯一(如使用设备 MAC 地址或 UUID)。

4万+

被折叠的 条评论
为什么被折叠?



