SpringBoot整合MQTT (使用官方demo)
另一种方式(推荐):https://www.cnblogs.com/pxblog/p/17058417.html
依赖
<dependency> <groupId>org.eclipse.paho</groupId> <artifactId>org.eclipse.paho.client.mqttv3</artifactId> <version>1.2.3</version> </dependency>
配置
spring:
mqtt:
clientId: test1
url: tcp://192.168.1.24:1883
username: admin
password: 123456
配置类
MyMqttClient.java
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; import org.eclipse.paho.client.mqttv3.MqttClient; import org.eclipse.paho.client.mqttv3.MqttConnectOptions; import org.eclipse.paho.client.mqttv3.MqttDeliveryToken; import org.eclipse.paho.client.mqttv3.MqttException; import org.eclipse.paho.client.mqttv3.MqttMessage; import org.eclipse.paho.client.mqttv3.MqttPersistenceException; import org.eclipse.paho.client.mqttv3.MqttTopic; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; @Component public class MyMqttClient { public static MqttClient mqttClient = null; private static MemoryPersistence memoryPersistence = null; private static MqttConnectOptions mqttConnectOptions = null; @Autowired private MqttRecieveCallback mqttRecieveCallback; @Autowired private MqttTwoRecieveCallback mqttTwoRecieveCallback; @Value("${spring.mqtt.url}") private String serverURI; @Value("${spring.mqtt.clientId}") private String clientId; @Value("${spring.mqtt.username}") private String username; @Value("${spring.mqtt.password}") private String password; @PostConstruct public void init() { //初始化连接设置对象 mqttConnectOptions = new MqttConnectOptions(); //初始化MqttClient if (null != mqttConnectOptions) { // true可以安全地使用内存持久性作为客户端断开连接时清除的所有状态 mqttConnectOptions.setCleanSession(true); // 设置连接超时 mqttConnectOptions.setConnectionTimeout(10); //设置账号密码 // mqttConnectOptions.setUserName(username); // mqttConnectOptions.setPassword(password.toCharArray()); // 设置持久化方式 memoryPersistence = new MemoryPersistence(); if (null != memoryPersistence && null != clientId) { try { mqttClient = new MqttClient(serverURI, clientId, memoryPersistence); } catch (MqttException e) { // TODO Auto-generated catch block e.printStackTrace(); } } else { } } else { System.out.println("mqttConnectOptions对象为空"); } System.out.println(mqttClient.isConnected()); //设置连接和回调 if (null != mqttClient) { if (!mqttClient.isConnected()) { // 创建连接 try { System.out.println("创建连接"); mqttClient.connect(mqttConnectOptions); } catch (MqttException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } else { System.out.println("mqttClient为空"); } System.out.println(mqttClient.isConnected()); if (mqttClient.isConnected()) { try { //添加回调方法1 mqttClient.subscribe("topic/test1", 2, mqttRecieveCallback); //添加回调方法2 mqttClient.subscribe("topic/test2", 2, mqttTwoRecieveCallback); } catch (MqttException e) { e.printStackTrace(); } } } // 关闭连接 @PreDestroy public void closeConnect() { //关闭存储方式 if (null != memoryPersistence) { try { memoryPersistence.close(); } catch (MqttPersistenceException e) { // TODO Auto-generated catch block e.printStackTrace(); } } else { System.out.println("memoryPersistence is null"); } // 关闭连接 if (null != mqttClient) { if (mqttClient.isConnected()) { try { mqttClient.disconnect(); mqttClient.close(); } catch (MqttException e) { // TODO Auto-generated catch block e.printStackTrace(); } } else { System.out.println("mqttClient is not connect"); } } else { System.out.println("mqttClient is null"); } } // 发布消息 public void publishMessage(String pubTopic, String message, int qos,Boolean retained) { if (null != mqttClient && mqttClient.isConnected()) { System.out.println("发布消息 " + mqttClient.isConnected()); System.out.println("id:" + mqttClient.getClientId()); MqttMessage mqttMessage = new MqttMessage(); mqttMessage.setQos(qos); mqttMessage.setPayload(message.getBytes()); mqttMessage.setRetained(retained); MqttTopic topic = mqttClient.getTopic(pubTopic); if (null != topic) { try { MqttDeliveryToken publish = topic.publish(mqttMessage); if (!publish.isComplete()) { System.out.println("消息发布成功"); } } catch (MqttException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } else { reConnect(); } } // 重新连接 public void reConnect() { if (null != mqttClient) { if (!mqttClient.isConnected()) { if (null != mqttConnectOptions) { try { mqttClient.connect(mqttConnectOptions); } catch (MqttException e) { // TODO Auto-generated catch block e.printStackTrace(); } } else { System.out.println("mqttConnectOptions is null"); } } else { System.out.println("mqttClient is null or connect"); } } else { init(); } } // 订阅主题 public void subTopic(String topic) { if (null != mqttClient && mqttClient.isConnected()) { try { mqttClient.subscribe(topic, 1); } catch (MqttException e) { // TODO Auto-generated catch block e.printStackTrace(); } } else { System.out.println("mqttClient is error"); } } // 清空主题 public void cleanTopic(String topic) { if (null != mqttClient && !mqttClient.isConnected()) { try { mqttClient.unsubscribe(topic); } catch (MqttException e) { // TODO Auto-generated catch block e.printStackTrace(); } } else { System.out.println("mqttClient is error"); } } }
配置参数说明:
- cleanSession :把配置里的 cleanSession 设为false,客户端掉线后 服务器端不会清除session,当重连后可以接收之前订阅主题的消息。当客户端上线后继续订阅会接收到它离线的这段时间的消息(注意:clientId 是不能修改)
- Retained:如果PUBLISH消息的RETAIN标记位被设置为1,则称该消息为“保留消息”(只会保存一条);Broker会存储每个Topic的最后一条保留消息及其Qos,当订阅该Topic的客户端上线后,Broker需要将该消息投递给它。这可以让新订阅的客户端马上得到发布方的最新的状态值,而不必要等待。
保留消息的删除
- 方式1:发送空消息体的保留消息;
- 方式2:发送最新的保留消息覆盖之前的(推荐);
- setUserName: 设置用户名
- setPassword: 设置密码
- setCleanSession: 设置是否清除会话
- setKeepAliveInterval: 设置心跳间隔
- setConnectionTimeout: 设置连接超时时间
- setAutomaticReconnect: 设置是否自动重连
回调类一
MqttRecieveCallback.java
import org.eclipse.paho.client.mqttv3.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class MqttRecieveCallback implements MqttCallback, IMqttMessageListener { @Autowired private MyMqttClient client; @Override public void connectionLost(Throwable cause) { } @Override public void messageArrived(String topic, MqttMessage message) { System.out.println("Client 接收消息主题 : " + topic); System.out.println("Client 接收消息Qos : " + message.getQos()); System.out.println("Client 接收消息内容 : " + new String(message.getPayload())); /** * 发送消息 */ client.publishMessage("topic/test2","2",2,false); } @Override public void deliveryComplete(IMqttDeliveryToken token) { } }
回调类2
MqttTwoRecieveCallback.java
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; import org.eclipse.paho.client.mqttv3.IMqttMessageListener; import org.eclipse.paho.client.mqttv3.MqttCallback; import org.eclipse.paho.client.mqttv3.MqttMessage; import org.springframework.stereotype.Component; @Component public class MqttTwoRecieveCallback implements MqttCallback, IMqttMessageListener { @Override public void connectionLost(Throwable cause) { } @Override public void messageArrived(String topic, MqttMessage message) throws Exception { System.out.println("Client2 接收消息主题 : " + topic); System.out.println("Client2 接收消息Qos : " + message.getQos()); System.out.println("Client2 接收消息内容 : " + new String(message.getPayload())); } @Override public void deliveryComplete(IMqttDeliveryToken token) { } }
使用
@Autowired private MyMqttClient myMqttClient; myMqttClient.publishMessage("tra_topic",text,2,false);
如果出现报错:MQTT(32202): 正在发布过多的消息
增加配置
mqttConnectOptions.setMaxInflight(1000);
如果报错:
Action:
Relying upon circular references is discouraged and they are prohibited by default. Update your application to remove the dependency cycle between beans. As a last resort, it may be possible to break the cycle automatically by setting spring.main.allow-circular-references to true.
这是因为我们循环依赖了,可以自己改下代码
或者增加配置
spring: main: allow-circular-references: true
SSL连接方式
<!-- https://mvnrepository.com/artifact/org.bouncycastle/bcpkix-jdk15on --> <dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcpkix-jdk15on</artifactId> <version>1.70</version> </dependency>
SSLUtils.java
import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.openssl.PEMKeyPair; import org.bouncycastle.openssl.PEMParser; import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManagerFactory; import java.io.BufferedInputStream; import java.io.FileInputStream; import java.io.FileReader; import java.security.KeyPair; import java.security.KeyStore; import java.security.Security; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; public class SSLUtils { public static SSLSocketFactory getSocketFactory(final String caCrtFile, final String crtFile, final String keyFile, final String password) throws Exception { Security.addProvider(new BouncyCastleProvider()); // load CA certificate X509Certificate caCert = null; FileInputStream fis = new FileInputStream(caCrtFile); BufferedInputStream bis = new BufferedInputStream(fis); CertificateFactory cf = CertificateFactory.getInstance("X.509"); while (bis.available() > 0) { caCert = (X509Certificate) cf.generateCertificate(bis); } // load client certificate bis = new BufferedInputStream(new FileInputStream(crtFile)); X509Certificate cert = null; while (bis.available() > 0) { cert = (X509Certificate) cf.generateCertificate(bis); } // load client private key PEMParser pemParser = new PEMParser(new FileReader(keyFile)); Object object = pemParser.readObject(); JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC"); KeyPair key = converter.getKeyPair((PEMKeyPair) object); pemParser.close(); // CA certificate is used to authenticate server KeyStore caKs = KeyStore.getInstance(KeyStore.getDefaultType()); caKs.load(null, null); caKs.setCertificateEntry("ca-certificate", caCert); TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509"); tmf.init(caKs); // client key and certificates are sent to server so it can authenticate KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); ks.load(null, null); ks.setCertificateEntry("certificate", cert); ks.setKeyEntry("private-key", key.getPrivate(), password.toCharArray(), new java.security.cert.Certificate[]{cert}); KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory .getDefaultAlgorithm()); kmf.init(ks, password.toCharArray()); // finally, create SSL socket factory SSLContext context = SSLContext.getInstance("TLSv1.2"); context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); return context.getSocketFactory(); } }
使用增加
// 设置 socket factory String caFilePath = "/cacert.pem"; String clientCrtFilePath = "/client.pem"; String clientKeyFilePath = "/client.key"; SSLSocketFactory socketFactory = null; try { socketFactory = SSLUtils.getSocketFactory(caFilePath, clientCrtFilePath, clientKeyFilePath, ""); } catch (Exception e) { e.printStackTrace(); } mqttConnectOptions.setSocketFactory(socketFactory);
-----------------------有任何问题可以在评论区评论,也可以私信我,我看到的话会进行回复,欢迎大家指教------------------------
(蓝奏云官网有些地址失效了,需要把请求地址lanzous改成lanzoux才可以)