├── README.md
├── pom.xml
└── src
└── main
├── java
└── com
│ └── gaoyf
│ └── mqtt
│ ├── SpringbootMqttApplication.java
│ ├── controller
│ └── DemoRestController.java
│ └── core
│ ├── IMQTTPublisher.java
│ ├── IMQTTSubscriber.java
│ ├── MQTTConfig.java
│ ├── MQTTPublisher.java
│ └── MQTTSubscriber.java
└── resources
└── application.properties
/README.md:
--------------------------------------------------------------------------------
1 | ## 结构
2 |
3 | Server side 构成
4 |
5 | - broker (mqtt核心:用于消息的发送管理)
6 | - Application Server用于处理RestFul的请求,转发为Mqtt消息
7 | - Publisher **本质是Mqtt client**用于发布server端消息
8 | - Subscriber **本质是Mqtt client**用于订阅client端消息,并显示
9 | - Client side
10 | - Publisher用于发布client端消息
11 | - Subscriber用于订阅server端的消息
12 | - Client 用于发送RestFul 请求给Application Server触发消息pub/sub
13 |
14 | **总结**:从结构上Broker算是Mqtt的本质上的Server端,从业务上讲封装了Mqtt Client pub/sub的Application server和Broker共同构成了业务上的Server端
15 |
16 | ### 构建springboot项目
17 |
18 | #### 1. 使用idea springboot initializer 初始化springboot工程
19 |
20 | 使用springboot版本**2.1.5.RELEASE**
21 |
22 | #### 2. pom中添加
23 |
24 | ```xml
25 |
26 |
493 | * 测试controller
494 | */
495 | @RestController
496 | public class DemoRestController {
497 | public static String TOPIC_LOOP_TEST = "mqtt/loop/message";
498 |
499 | @Autowired
500 | IMQTTPublisher publisher;
501 |
502 | @Autowired
503 | IMQTTSubscriber subscriber;
504 |
505 | /**
506 | * 被@PostConstruct修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器执行一次。
507 | * PostConstruct在构造函数之后执行,init()方法之前执行。PreDestroy()方法在destroy()方法知性之后执行
508 | * 这里初始化订阅一个主题
509 | */
510 | @PostConstruct
511 | public void init() {
512 | subscriber.subscribeMessage(TOPIC_LOOP_TEST);
513 | }
514 |
515 |
516 | /**
517 | * 向指定主题发送消息
518 | *
519 | * @param data 数据
520 | * @return 响应
521 | */
522 | @RequestMapping(value = "/mqtt/loop/message", method = RequestMethod.POST)
523 | public String index(@RequestBody String data) {
524 | publisher.publishMessage(TOPIC_LOOP_TEST, data);
525 | return "Success";
526 | }
527 |
528 | }
529 | ```
530 |
531 | #### 7. 使用postman 调用8080 端口调试或者使用MQTTX工具进行调试。
532 | [MQTTX 下载地址]( https://github.com/emqx/MQTTX/releases/tag/v1.3.0)
533 |
534 |
535 |
536 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 | * 测试controller 18 | */ 19 | @RestController 20 | public class DemoRestController { 21 | public static String TOPIC_LOOP_TEST = "mqtt/loop/message"; 22 | 23 | @Autowired 24 | IMQTTPublisher publisher; 25 | 26 | @Autowired 27 | IMQTTSubscriber subscriber; 28 | 29 | /** 30 | * 被@PostConstruct修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器执行一次。 31 | * PostConstruct在构造函数之后执行,init()方法之前执行。PreDestroy()方法在destroy()方法知性之后执行 32 | * 这里初始化订阅一个主题 33 | */ 34 | @PostConstruct 35 | public void init() { 36 | subscriber.subscribeMessage(TOPIC_LOOP_TEST); 37 | } 38 | 39 | 40 | /** 41 | * 向指定主题发送消息 42 | * 43 | * @param data 数据 44 | * @return 响应 45 | */ 46 | @RequestMapping(value = "/mqtt/loop/message", method = RequestMethod.POST) 47 | public String index(@RequestBody String data) { 48 | publisher.publishMessage(TOPIC_LOOP_TEST, data); 49 | return "Success"; 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/gaoyf/mqtt/core/IMQTTPublisher.java: -------------------------------------------------------------------------------- 1 | package com.gaoyf.mqtt.core; 2 | 3 | /** 4 | * @author gaoyf 5 | * @since 2020/4/9 0009 16:02 6 | *
7 | * 发布者接口 8 | */ 9 | public interface IMQTTPublisher { 10 | /** 11 | * 发布消息 12 | * 13 | * @param topic 主题 14 | * @param message 消息 15 | */ 16 | public void publishMessage(String topic, String message); 17 | 18 | /** 19 | * 断开MQTT客户端 20 | */ 21 | public void disconnect(); 22 | } 23 | 24 | -------------------------------------------------------------------------------- /src/main/java/com/gaoyf/mqtt/core/IMQTTSubscriber.java: -------------------------------------------------------------------------------- 1 | package com.gaoyf.mqtt.core; 2 | 3 | /** 4 | * @author gaoyf 5 | * @since 2020/4/9 0009 16:04 6 | *
7 | * 订阅者接口 8 | */ 9 | public interface IMQTTSubscriber { 10 | 11 | /** 12 | * 订阅消息 13 | * 14 | * @param topic 15 | */ 16 | public void subscribeMessage(String topic); 17 | 18 | /** 19 | * 断开MQTT客户端 20 | */ 21 | public void disconnect(); 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/gaoyf/mqtt/core/MQTTConfig.java: -------------------------------------------------------------------------------- 1 | package com.gaoyf.mqtt.core; 2 | 3 | /** 4 | * mqtt配置文件 5 | */ 6 | public abstract class MQTTConfig { 7 | 8 | protected String ip = "127.0.0.1"; 9 | 10 | /** 11 | * qos0 对于client而言,有且仅发一次publish包,对于broker而言,有且仅发一次publish,简而言之,就是仅发一次包,是否收到完全不管,适合那些不是很重要的数据。 12 | * qos1 这个交互就是多了一次ack的作用,但是会有个问题,尽管我们可以通过确认来保证一定收到客户端或服务器的message,但是我们却不能保证message仅有一次, 13 | * 也就是当client没收到service的puback或者service没有收到client的puback,那么就会一直发送publisher 14 | * qos2可以实现仅仅接受一次message,其主要原理(对于publisher而言), 15 | * publisher和broker进行了缓存,其中publisher缓存了message和msgID,而broker缓存了msgID,两方都做记录所以可以保证消息不重复, 16 | * 但是由于记录是需要删除的,这个删除流程同样多了一倍 17 | */ 18 | protected int qos = 2; 19 | 20 | protected Boolean hasSSL = false; /* By default SSL is disabled */ 21 | 22 | protected Integer port = 1883; /* Default port */ 23 | 24 | protected String username = "账号"; 25 | 26 | protected String password = "密码"; 27 | 28 | protected String TCP = "tcp://"; 29 | 30 | protected String SSL = "ssl://"; 31 | 32 | 33 | /** 34 | * Custom Configuration 35 | */ 36 | protected abstract void config(String ip, Integer port, Boolean ssl, Boolean withUserNamePass); 37 | 38 | /** 39 | * Default Configuration 40 | */ 41 | protected abstract void config(); 42 | 43 | 44 | public String getIp() { 45 | return ip; 46 | } 47 | 48 | public void setIp(String ip) { 49 | this.ip = ip; 50 | } 51 | 52 | public int getQos() { 53 | return qos; 54 | } 55 | 56 | public void setQos(int qos) { 57 | this.qos = qos; 58 | } 59 | 60 | public Boolean getHasSSL() { 61 | return hasSSL; 62 | } 63 | 64 | public void setHasSSL(Boolean hasSSL) { 65 | this.hasSSL = hasSSL; 66 | } 67 | 68 | public Integer getPort() { 69 | return port; 70 | } 71 | 72 | public void setPort(Integer port) { 73 | this.port = port; 74 | } 75 | 76 | public String getUsername() { 77 | return username; 78 | } 79 | 80 | public void setUsername(String username) { 81 | this.username = username; 82 | } 83 | 84 | public String getPassword() { 85 | return password; 86 | } 87 | 88 | public void setPassword(String password) { 89 | this.password = password; 90 | } 91 | 92 | public String getTCP() { 93 | return TCP; 94 | } 95 | 96 | public void setTCP(String TCP) { 97 | this.TCP = TCP; 98 | } 99 | 100 | public String getSSL() { 101 | return SSL; 102 | } 103 | 104 | public void setSSL(String SSL) { 105 | this.SSL = SSL; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/main/java/com/gaoyf/mqtt/core/MQTTPublisher.java: -------------------------------------------------------------------------------- 1 | package com.gaoyf.mqtt.core; 2 | 3 | import org.eclipse.paho.client.mqttv3.*; 4 | import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.stereotype.Component; 8 | 9 | /** 10 | * @author gaoyf 11 | * @since 2020/4/9 0009 16:02 12 | */ 13 | @Component 14 | public class MQTTPublisher extends MQTTConfig implements MqttCallback, IMQTTPublisher { 15 | 16 | private String ipUrl = null; 17 | 18 | final private String colon = ":";//冒号分隔符 19 | final private String clientId = "mqtt_server_pub";//客户端ID 这里可以随便定义 20 | 21 | private MqttClient mqttClient = null; 22 | private MqttConnectOptions connectionOptions = null; 23 | private MemoryPersistence persistence = null; 24 | 25 | private static final Logger logger = LoggerFactory.getLogger(MQTTPublisher.class); 26 | 27 | /** 28 | * Private default constructor 29 | */ 30 | private MQTTPublisher() { 31 | this.config(); 32 | } 33 | 34 | /** 35 | * Private constructor 36 | */ 37 | private MQTTPublisher(String ip, Integer port, Boolean ssl, Boolean withUserNamePass) { 38 | this.config(ip, port, ssl, withUserNamePass); 39 | } 40 | 41 | /** 42 | * Factory method to get instance of MQTTPublisher 43 | * 44 | * @return MQTTPublisher 45 | */ 46 | public static MQTTPublisher getInstance() { 47 | return new MQTTPublisher(); 48 | } 49 | 50 | /** 51 | * 获取MQTTPublisher实例的工厂方法 52 | * 53 | * @param ip ip地址 54 | * @param port 断开 55 | * @param ssl 是否ssl 56 | * @param withUserNamePass 用户名密码 57 | * @return MQTTPublisher 58 | */ 59 | public static MQTTPublisher getInstance(String ip, Integer port, Boolean ssl, Boolean withUserNamePass) { 60 | return new MQTTPublisher(ip, port, ssl, withUserNamePass); 61 | } 62 | 63 | 64 | protected void config() { 65 | 66 | this.ipUrl = this.TCP + this.ip + colon + this.port; 67 | this.persistence = new MemoryPersistence(); 68 | this.connectionOptions = new MqttConnectOptions(); 69 | try { 70 | this.mqttClient = new MqttClient(ipUrl, clientId, persistence); 71 | this.connectionOptions.setCleanSession(true); 72 | this.mqttClient.connect(this.connectionOptions); 73 | this.mqttClient.setCallback(this); 74 | } catch (MqttException me) { 75 | logger.error("ERROR", me); 76 | } 77 | } 78 | 79 | 80 | protected void config(String ip, Integer port, Boolean ssl, Boolean withUserNamePass) { 81 | String protocal = this.TCP; 82 | if (ssl) { 83 | protocal = this.SSL; 84 | } 85 | this.ipUrl = protocal + this.ip + colon + port; 86 | this.persistence = new MemoryPersistence(); 87 | this.connectionOptions = new MqttConnectOptions(); 88 | 89 | try { 90 | this.mqttClient = new MqttClient(ipUrl, clientId, persistence); 91 | this.connectionOptions.setCleanSession(true); 92 | if (withUserNamePass) { 93 | if (password != null) { 94 | this.connectionOptions.setPassword(this.password.toCharArray()); 95 | } 96 | if (username != null) { 97 | this.connectionOptions.setUserName(this.username); 98 | } 99 | } 100 | this.mqttClient.connect(this.connectionOptions); 101 | this.mqttClient.setCallback(this); 102 | } catch (MqttException me) { 103 | logger.error("ERROR", me); 104 | } 105 | } 106 | 107 | 108 | @Override 109 | public void publishMessage(String topic, String message) { 110 | 111 | try { 112 | MqttMessage mqttmessage = new MqttMessage(message.getBytes()); 113 | mqttmessage.setQos(this.qos); 114 | this.mqttClient.publish(topic, mqttmessage); 115 | } catch (MqttException me) { 116 | logger.error("ERROR", me); 117 | } 118 | 119 | } 120 | 121 | @Override 122 | public void connectionLost(Throwable arg0) { 123 | logger.info("Connection Lost"); 124 | 125 | } 126 | 127 | 128 | @Override 129 | public void deliveryComplete(IMqttDeliveryToken arg0) { 130 | logger.info("delivery completed"); 131 | 132 | } 133 | 134 | 135 | @Override 136 | public void messageArrived(String arg0, MqttMessage arg1) { 137 | // Leave it blank for Publisher 138 | 139 | } 140 | 141 | @Override 142 | public void disconnect() { 143 | try { 144 | this.mqttClient.disconnect(); 145 | } catch (MqttException me) { 146 | logger.error("ERROR", me); 147 | } 148 | } 149 | 150 | } -------------------------------------------------------------------------------- /src/main/java/com/gaoyf/mqtt/core/MQTTSubscriber.java: -------------------------------------------------------------------------------- 1 | package com.gaoyf.mqtt.core; 2 | 3 | import org.eclipse.paho.client.mqttv3.*; 4 | import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.stereotype.Component; 8 | 9 | import java.sql.Timestamp; 10 | 11 | /** 12 | * @author gaoyf 13 | * @since 2020/4/9 0009 16:05 14 | */ 15 | @Component 16 | public class MQTTSubscriber extends MQTTConfig implements MqttCallback, IMQTTSubscriber { 17 | 18 | private String brokerUrl = null; 19 | final private String colon = ":";//冒号分隔符 20 | final private String clientId = "mqtt_server_sub";//客户端ID 这里可以随便定义 21 | 22 | private MqttClient mqttClient = null; 23 | private MqttConnectOptions connectionOptions = null; 24 | private MemoryPersistence persistence = null; 25 | 26 | private static final Logger logger = LoggerFactory.getLogger(MQTTSubscriber.class); 27 | 28 | public MQTTSubscriber() { 29 | this.config(); 30 | } 31 | 32 | @Override 33 | public void connectionLost(Throwable cause) { 34 | logger.info("Connection Lost"); 35 | 36 | } 37 | 38 | @Override 39 | public void messageArrived(String topic, MqttMessage message) { 40 | // Called when a message arrives from the server that matches any subscription made by the client 41 | String time = new Timestamp(System.currentTimeMillis()).toString(); 42 | System.out.println(); 43 | System.out.println("***********************************************************************"); 44 | System.out.println("消息到达时间:" + time + " Topic: " + topic + " Message: " 45 | + new String(message.getPayload())); 46 | System.out.println("***********************************************************************"); 47 | System.out.println(); 48 | } 49 | 50 | @Override 51 | public void deliveryComplete(IMqttDeliveryToken token) { 52 | // Leave it blank for subscriber 53 | 54 | } 55 | 56 | @Override 57 | public void subscribeMessage(String topic) { 58 | try { 59 | this.mqttClient.subscribe(topic, this.qos); 60 | } catch (MqttException me) { 61 | me.printStackTrace(); 62 | } 63 | } 64 | 65 | public void disconnect() { 66 | try { 67 | this.mqttClient.disconnect(); 68 | } catch (MqttException me) { 69 | logger.error("ERROR", me); 70 | } 71 | } 72 | 73 | protected void config(String ip, Integer port, Boolean ssl, Boolean withUserNamePass) { 74 | String protocal = this.TCP; 75 | if (ssl) { 76 | protocal = this.SSL; 77 | } 78 | this.brokerUrl = protocal + this.ip + colon + port; 79 | this.persistence = new MemoryPersistence(); 80 | this.connectionOptions = new MqttConnectOptions(); 81 | try { 82 | this.mqttClient = new MqttClient(brokerUrl, clientId, persistence); 83 | this.connectionOptions.setCleanSession(true); 84 | if (withUserNamePass) { 85 | if (password != null) { 86 | this.connectionOptions.setPassword(this.password.toCharArray()); 87 | } 88 | if (username != null) { 89 | this.connectionOptions.setUserName(this.username); 90 | } 91 | } 92 | this.mqttClient.connect(this.connectionOptions); 93 | this.mqttClient.setCallback(this); 94 | } catch (MqttException me) { 95 | me.printStackTrace(); 96 | } 97 | 98 | } 99 | 100 | protected void config() { 101 | 102 | this.brokerUrl = this.TCP + this.ip + colon + this.port; 103 | this.persistence = new MemoryPersistence(); 104 | this.connectionOptions = new MqttConnectOptions(); 105 | try { 106 | this.mqttClient = new MqttClient(brokerUrl, clientId, persistence); 107 | this.connectionOptions.setCleanSession(true); 108 | this.mqttClient.connect(this.connectionOptions); 109 | this.mqttClient.setCallback(this); 110 | } catch (MqttException me) { 111 | me.printStackTrace(); 112 | } 113 | 114 | } 115 | 116 | } 117 | -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaoyf95/springboot-mqtt/69510ce09c6d85b75b10b9ed969abb6a7a5806e0/src/main/resources/application.properties --------------------------------------------------------------------------------