MQTT教程详解-01.基础使用

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(测试用)

  • EMQXtcp://broker.emqx.io:1883(无密码)
  • HiveMQtcp://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 工具)

  1. 连接:在 MQTTX 中新建连接,服务器地址填 localhost:1883

  2. 订阅:添加订阅,Topic 填 test/topic

  3. 发布:在消息发布框输入相同 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 运行测试

  1. 先启动 MqttSubscriber(订阅)
  2. 再启动 MqttPublisher(发布)
  3. 订阅者控制台打印:
    已连接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)

  1. 连接失败:检查 Broker 地址 / 端口、网络防火墙、客户端 ID 唯一。
  2. 收不到消息:主题名完全匹配、QoS 一致、订阅在发布之前。
  3. 重复消息: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)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值