1.MQTT协议概述

        MQTT是一种基于发布/订阅模式的轻量级消息传输协议,常用于低带宽、不可靠网络环境下传输消息,适用于物联网设备之间的通信。

1.1 MQTT协议的组件

  • 客户端(Client):连接到MQTT代理服务器的设备(发布者、订阅者)
  • 代理服务器(Broker):负责接收来自客户端的消息并将其转发给订阅该主题的客户端
  • 主题(Topic):消息的分类标识,客户端通过订阅或发布到特定主题来接受或发送消息
  • 消息(Message):客户端通过主题传输的数据负载

        客户端通过TCP/IP连接到代理,通过身份验证保持长连接 

2.MQTT服务器搭建

        用EMQX来搭建服务器

快速开始 | EMQX 4.3 文档icon-default.png?t=N7T8https://docs.emqx.com/zh/emqx/v4.3/getting-started/getting-started.html        下载EMQX

https://www.emqx.com/zh/try?product=brokericon-default.png?t=N7T8https://www.emqx.com/zh/try?product=broker        安装虚拟机(环境:centOS7)

        配置 EMQX Yum 源

curl -s https://assets.emqx.com/scripts/install-emqx-rpm.sh | sudo bash

        安装依赖

yum install epel-release -y

yum install -y openssl11 openssl11-devel

        安装 EMQX

sudo yum install emqx -y

        启动 EMQX

sudo systemctl start emqx

        访问管理后台:http://localhost:18083/#/login?to=/dashboard/overview

        (默认用户名:admin 密码:public)

        访问接口文档: http://localhost:18083/api-docs/index.html#

2.1 创建主题

3.使用EMQX服务器

如何在 Java 中使用 MQTT | EMQ本文主要介绍如何在 Java 项目中使用 MQTT,实现 MQTT 客户端与服务器的连接、订阅和收发消息等功能。icon-default.png?t=N7T8https://www.emqx.com/zh/blog/how-to-use-mqtt-in-java

3.1 导入依赖

<dependency>
  <groupId>org.eclipse.paho</groupId>
    <artifactId>org.eclipse.paho.client.mqttv3</artifactId>
    <version>1.2.2</version>
</dependency>

 3.2 创建MQTT连接

        String broker = "tcp://localhost:1883";
        // TLS/SSL
        // String broker = "ssl://broker.emqx.io:8883";
        String username = "emqx";
        String password = "public";
        String clientid = "publish_client";

        MqttClient client = new MqttClient(broker, clientid, new MemoryPersistence());
        MqttConnectOptions options = new MqttConnectOptions();
        options.setUserName(username);
        options.setPassword(password.toCharArray());
        client.connect(options);
  • MqttClient: 同步调用客户端,使用阻塞方法通信。
  • MqttClientPersistence: 代表一个持久的数据存储,用于在传输过程中存储出站和入站的信息,使其能够传递到指定的 QoS。
  • MqttConnectOptions: 连接选项,用于指定连接的参数,下面列举一些常见的方法。
    • setUserName: 设置用户名
    • setPassword: 设置密码
    • setCleanSession: 设置是否清除会话
    • setKeepAliveInterval: 设置心跳间隔
    • setConnectionTimeout: 设置连接超时时间
    • setAutomaticReconnect: 设置是否自动重连

3.3 发布MQTT消息

package com.mqttdemo.util;

import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.*;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

/**
 * 消息推送客户端
 */
@Slf4j
@Component
public class PublishSample  implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        PublishSample.applicationContext = applicationContext;
    }

    private final static int QOS_1 = 1;

    private final static String USER_NAME = "xxx";

    private final static String PASSWORLD = "xxx";

    private final static int KEEP_ALIVE = 60;

    //连接地址
    public static final String HOST = "tcp://xxx";

    // 订阅主题
    public static final String TOPIC = "/xxx/xxx/xxx";

    //客户端唯一ID
    private static final String clientid = "xxx";


    public static MqttClient createMqtt() {
        MqttClient client = null;

        MqttConnectOptions connectOptions = new MqttConnectOptions();
        // 设置会话心跳时间
        connectOptions.setKeepAliveInterval(KEEP_ALIVE);
        // 不建立持久会话
        connectOptions.setCleanSession(true);
        // 用户名
        connectOptions.setUserName(USER_NAME);
        // 密码
        connectOptions.setPassword(PASSWORLD.toCharArray());

        try {
            client = new MqttClient(HOST, clientid, new MemoryPersistence());
            // MQTT连接
            client.connect(connectOptions);
            // 获取 Spring 上下文中的 MqttCallBackHandle bean
            MqttCallBackHandle callbackHandle = applicationContext.getBean(MqttCallBackHandle.class);
            // 设置回调
            client.setCallback(callbackHandle);
        } catch (MqttException e) {
            log.warn("MQTT消息异常{}", e);
        }
        return client;
    }

    /**
     * 消息推送
     *
     * @param message 消息内容
     * @param topic   发送的主题
     * @author yanglingcong
     * @date 2022/4/18 21:25
     */
    public static void publishMessage(String message, String topic, MqttClient mqttClient) {
        MqttMessage mqttMessage = new MqttMessage();
        mqttMessage.setQos(QOS_1);
        mqttMessage.setPayload(message.getBytes());
        try {
            mqttClient.publish(topic, mqttMessage);
            log.info("MQTT消息发送成功:{}", message);
        } catch (MqttException e) {
            log.warn("MQTT消息推送失败", e);
        }
    }
}

3.4 订阅MQTT主题

package com.mqttdemo.util;

import com.mqttdemo.util.MqttCallBackHandle;
import lombok.extern.slf4j.Slf4j;
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.persist.MemoryPersistence;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

/**
 * 消息订阅客户端
 */
@Component
@Slf4j
public class SubscribeSample implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        SubscribeSample.applicationContext = applicationContext;
        try {
            subscribe();
        } catch (MqttException e) {
            log.error("Failed to subscribe", e);
        }
    }

    private final static int QOS_1 = 1;
    private final static String USER_NAME = "xxx";
    private final static String PASSWORLD = "xxx";
    private final static int KEEP_ALIVE = 60;
    public static final String HOST = "tcp://xxx";
    public static final String[] TOPICS = {
            "/xxx/xxx/xxx/a",
            "/xxx/xxx/xxx/b",
            "/xxx/xxx/xxx/c"
    };
    public static final int[] QOS = {QOS_1, QOS_1, QOS_1};
    private static final String clientid = "subClient";

    public SubscribeSample() {
        // 构造函数中不再调用 subscribe
    }

    public static void subscribe() throws MqttException {
        MqttClient client = null;

        MqttConnectOptions connectOptions = new MqttConnectOptions();
        connectOptions.setAutomaticReconnect(true);
        connectOptions.setKeepAliveInterval(KEEP_ALIVE);
        connectOptions.setCleanSession(true);
        connectOptions.setUserName(USER_NAME);
        connectOptions.setPassword(PASSWORLD.toCharArray());

        try {
            client = new MqttClient(HOST, clientid, new MemoryPersistence());
            client.connect(connectOptions);
            MqttCallBackHandle callbackHandle = applicationContext.getBean(MqttCallBackHandle.class);
            client.setCallback(callbackHandle);
            client.subscribe(TOPICS, QOS);
        } catch (MqttException e) {
            log.warn("MQTT消息订阅异常{}", e);
            e.printStackTrace();
        }
    }
}

3.5 MQTT工具类发送消息

package com.mqttdemo.util;

import lombok.Data;
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 java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

public class MQTTUtil {
    private static String username = "xxx";

    private static String password = "xxx";
    //连接地址
    public static String broker = "tcp://xxxx:xxxx";
    //客户端唯一ID
    private static String clientId = "xxx";

    public MQTTUtil(String broker, String clientId, String username, String password) {
        this.broker = broker;
        this.clientId = clientId;
        this.username = username;
        this.password = password;
    }

    public static String sendMessage(String topic, String message, int qos) {
        MqttClient client = null;
        final String[] response = {null};
        final CountDownLatch latch = new CountDownLatch(1);

        try {
            client = new MqttClient(broker, clientId);
            MqttConnectOptions connOpts = new MqttConnectOptions();
            connOpts.setCleanSession(true);

            if (username != null && password != null) {
                connOpts.setUserName(username);
                connOpts.setPassword(password.toCharArray());
            }

            client.setCallback(new MqttCallback() {
                @Override
                public void connectionLost(Throwable cause) {
                    System.out.println("Connection lost: " + cause.getMessage());
                }

                @Override
                public void messageArrived(String topic, MqttMessage message) {
                    String result = new String(message.getPayload());
                    System.out.println("Message arrived. Topic: " + topic + " Message: " + result);
                    response[0] = result;
                    latch.countDown();  // Signal that a message has arrived
                }

                @Override
                public void deliveryComplete(IMqttDeliveryToken token) {
                    System.out.println("Delivery complete: " + token.getMessageId());
                }
            });

            System.out.println("Connecting to broker: " + broker);
            client.connect(connOpts);
            System.out.println("Connected");

            // Subscribe to the topic to receive replies
            String subTopic = topic.replaceAll("command", "getcommand");
            System.out.println("Subscribing to topic: " + subTopic);
            client.subscribe(subTopic);

            // Publish the message
            MqttMessage mqttMessage = new MqttMessage(message.getBytes());
            mqttMessage.setQos(qos);
            System.out.println("Publishing message: " + message + " to topic: " + topic);
            client.publish(topic, mqttMessage);
            System.out.println("Message published");

            // Wait for the response message
            latch.await(2, TimeUnit.MINUTES);  // Wait for up to 2 minutes for a response

            client.disconnect();
            System.out.println("Disconnected");
        } catch (MqttException | InterruptedException me) {
            me.printStackTrace();
        } finally {
            if (client != null) {
                try {
                    client.close();
                } catch (MqttException e) {
                    e.printStackTrace();
                }
            }
        }
        return response[0];  // Return the received message
    }
}

3.6 MQTT配置类

package com.mqttdemo.config;

import com.mqttdemo.service.EventDataDocService;
import com.mqttdemo.service.SensorDataHourService;
import com.mqttdemo.service.SensorDataMinService;
import com.mqttdemo.util.MqttCallBackHandle;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MqttConfig {

    private static final String BROKER_URL = "tcp://xxx";
    private static final String CLIENT_ID = "xxx";

    @Bean
    public MqttClient mqttClient(SensorDataMinService sensorDataMinService, SensorDataHourService sensorDataHourService, EventDataDocService eventDataDocService) throws MqttException {
        MqttClient client = new MqttClient(BROKER_URL, CLIENT_ID, new MemoryPersistence());
        MqttConnectOptions options = new MqttConnectOptions();
        options.setCleanSession(true);
        client.connect(options);

        // 创建 MqttCallBackHandle 实例并设置回调
        MqttCallBackHandle mqttCallBackHandle = new MqttCallBackHandle(sensorDataMinService, sensorDataHourService,eventDataDocService, client);
        client.setCallback(mqttCallBackHandle);

        return client;
    }
}

3.7 回调处理类

package com.mqttdemo.util;

import cn.hutool.core.lang.UUID;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.mqttdemo.entity.EventDataDocPo;
import com.mqttdemo.entity.Password;
import com.mqttdemo.entity.SensorDataHour;
import com.mqttdemo.entity.SensorDataMin;
import com.mqttdemo.service.EventDataDocService;
import com.mqttdemo.service.SensorDataHourService;
import com.mqttdemo.service.SensorDataMinService;
import com.mysql.cj.util.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;

@Slf4j
@Component
public class MqttCallBackHandle implements MqttCallback {

    private final ABService abService;
    private final MqttClient client;

    @Autowired
    public MqttCallBackHandle(ABService abService, MqttClient client) {
        this.abService= abService;
        this.client = client;
    }

    @Override
    public void connectionLost(Throwable cause) {
        log.warn("MQTT连接丢失", cause);
    }

    @Override
    public void messageArrived(String topic, MqttMessage message) throws Exception {
        String payload = new String(message.getPayload());

        switch (topic) {
            case "/xxx/xxx/xxx/xxx":
                // 收到消息后调用发布者发布消息
                String responseTopic = "/xxx/xxx/xxx/xxx";

                ObjectMapper objectMapper = new ObjectMapper();
                MqttClient publishClient = PublishSample.createMqtt();
                if (publishClient != null) {
                    PublishSample.publishMessage("{\"a\": \"123\"}", responseTopic, publishClient);
                    publishClient.disconnect();
                    publishClient.close();
                } else {
                    log.warn("无法创建发布者客户端");
                }
                break;

            case "/xxx/xxx/xxx/aaa":
                // 当收到 aaa 主题的消息时,打印消息
                JSONObject jsonObject = JSONUtil.parseObj(payload);
                Double temperatureMin = jsonObject.get("Temperature_min", Double.class);
                AB abData= parsePayloadToAB(payload);
                abService.save(abData);
                break;
            default:
                log.warn("收到未处理的主题: {}", topic);
                break;
        }
    }

    public static AB parsePayloadToAB(String payload) {
        JSONObject jsonObject = JSONUtil.parseObj(payload);
        AB abData= new AB();

        abData.setbbb(jsonObject.getDouble("bbb"));
        abData.setaaa(jsonObject.getDouble("aaa"));

        return abData;
    }

    @Override
    public void deliveryComplete(IMqttDeliveryToken token) {
        log.info("消息发送完成: {}", token.isComplete());
    }
}
  • connectionLost(Throwable cause): 连接丢失时被调用
  • messageArrived(String topic, MqttMessage message): 接收到消息时被调用
  • deliveryComplete(IMqttDeliveryToken token): 消息发送完成时被调用

4.需要nginx代理访问MQTT的情况

        nginx需要配置网页端以及服务器端,一个http,一个tcp

# MQTT 代理
stream {
    upstream mqtt_backend {
        server 192.168.0.11:1883;
    }

    server {
        listen 1883;
        proxy_pass mqtt_backend;
    }
}
# HTTP 代理
http {
    server {
        listen 18083;
        server_name localhost;

        location / {
            proxy_pass http://192.168.0.11:18083;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }
    }
}
Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐