专业的MQTT知识学习网站:http://mqtt.p2hp.com/mqtt311
专业的MQTT Java Client使用网站:https://www.eclipse.org/paho/index.php?page=clients/java/index.php
如果能读懂英文,直接去paho的官网看说明就可以使用mqtt client开发程序了
教程开始
引入坐标文件
<dependency>
<groupId>org.eclipse.paho</groupId>
<artifactId>org.eclipse.paho.client.mqttv3</artifactId>
<version>%VERSION%</version>
</dependency>
当前写文章的时候,官方推荐版本为:1.2.1
贴一个异步客户端MqttAsyncClient的代码demo,同步的比较简单,需要关注的问题少。
package com.;
import cn.hutool.core.lang.UUID;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.*;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import java.util.concurrent.TimeUnit;
/**
* @description: 多实例模式。
* @author:dsy
* @time: 2021/4/9 11:12
*/
@Slf4j
public class MQTTClient {
private String userName;
private String password;
private String topic;
private int defaultQos = 1;
// "tcp://mqtt.eclipse.org:1883";
private String broker;
//Every client instance that connects to an MQTT server must have a unique client identifier
private String clientId;
private boolean autoReconnect = true;
//数据持久性保存方案,这个是保存在内存里面
MemoryPersistence persistence = new MemoryPersistence();
private MqttAsyncClient mqttAsyncClient;
private MqttCallback mqttCallback;
public class MyMqttCallback implements MqttCallback {
// Notification that the connection to the server has broken: connectionLost.
@Override
public void connectionLost(Throwable throwable) {
log.debug("connectionLost");
log.debug(throwable.getMessage());
new Thread(() -> {
//开始重新连接
log.error("start reconnection");
try {
while (mqttAsyncClient != null && !mqttAsyncClient.isConnected()) {
mqttAsyncClient.reconnect();
if (mqttAsyncClient.isConnected()) {
log.info("ltm mqtt client reconnet success");
break;
} else {
log.info("ltm mqtt client reconnet fail,after 5 secend reconnect");
TimeUnit.SECONDS.sleep(5);
}
}
} catch (MqttException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
if (mqttCallback != null) {
mqttCallback.connectionLost(throwable);
}
}
// Notification that a new message has arrived: messageArrived.
@Override
public void messageArrived(String s, MqttMessage mqttMessage) throws Exception {
log.debug("messageArrived");
log.debug(s);
if (mqttCallback != null) {
mqttCallback.messageArrived(s, mqttMessage);
}
}
// Notification that a message has been delivered to the server: deliveryComplete.
@Override
public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) {
log.debug("deliveryComplete");
if (mqttCallback != null) {
mqttCallback.deliveryComplete(iMqttDeliveryToken);
}
}
}
public synchronized void connect() {
try {
if (mqttAsyncClient != null) {
if (mqttAsyncClient.isConnected()) {
mqttAsyncClient.disconnect();
mqttAsyncClient = null;
}
}
if (clientId == null) {
randomClientId();
}
mqttAsyncClient = new MqttAsyncClient(broker, clientId, persistence);
MqttConnectOptions connOpts = new MqttConnectOptions();
// everytime connect clean all message
connOpts.setCleanSession(true);
if (userName != null) {
connOpts.setUserName(userName);
}
if (password != null) {
connOpts.setPassword(password.toCharArray());
}
mqttAsyncClient.connect(connOpts, null, new IMqttActionListener() {
@Override
public void
mqttAsyncClient.setCallback(new MyMqttCallback());
//注意下面代码,如果是调用connect之后立马订阅或者发送,那么必须要处理异步连接问题,可能client还没有连接上,你就调用了发送或者订阅方法,这个时候一定报错!!!
while (!mqttToken.isComplete()) ;
} catch (MqttException e) {
e.printStackTrace();
}
}
/*
* @author:l
* @time: 2021/4/9 15:52
* @description:订阅某些主题,可以多次调用,订阅多个。
**/
public void subscribe(String topic, int qos) {
try {
mqttAsyncClient.subscribe(topic, qos);
} catch (MqttException e) {
e.printStackTrace();
}
}
public void sendMeaage(String msg, int qos) {
MqttMessage message = new MqttMessage(msg.getBytes());
message.setQos(qos);
try {
mqttAsyncClient.publish(topic, message);
} catch (MqttException e) {
e.printStackTrace();
}
}
public void sendMeaage(String msg) {
MqttMessage message = new MqttMessage(msg.getBytes());
message.setQos(defaultQos);
try {
mqttAsyncClient.publish(topic, message);
} catch (MqttException e) {
e.printStackTrace();
}
}
public void disconnect() {
try {
mqttAsyncClient.disconnect();
} catch (MqttException e) {
e.printStackTrace();
}
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getTopic() {
return topic;
}
public void setTopic(String topic) {
this.topic = topic;
}
public String getBroker() {
return broker;
}
public void setBroker(String broker) {
this.broker = broker;
}
public void randomClientId() {
clientId = UUID.fastUUID().toString();
}
}
贴一个同步的官方代码
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;
public class MqttPublishSample {
public static void main(String[] args) {
String topic = "MQTT Examples";
String content = "Message from MqttPublishSample";
int qos = 2;
String broker = "tcp://mqtt.eclipse.org:1883";
String clientId = "JavaSample";
MemoryPersistence persistence = new MemoryPersistence();
try {
MqttClient sampleClient = new MqttClient(broker, clientId, persistence);
MqttConnectOptions connOpts = new MqttConnectOptions();
connOpts.setCleanSession(true);
System.out.println("Connecting to broker: "+broker);
sampleClient.connect(connOpts);
System.out.println("Connected");
System.out.println("Publishing message: "+content);
MqttMessage message = new MqttMessage(content.getBytes());
message.setQos(qos);
sampleClient.publish(topic, message);
System.out.println("Message published");
sampleClient.disconnect();
System.out.println("Disconnected");
System.exit(0);
} catch(MqttException me) {
System.out.println("reason "+me.getReasonCode());
System.out.println("msg "+me.getMessage());
System.out.println("loc "+me.getLocalizedMessage());
System.out.println("cause "+me.getCause());
System.out.println("excep "+me);
me.printStackTrace();
}
}
}
使用MQTT注意事项
拿到mqttClient之后,你可以
- connect
- publish
- subscribe
- unsubscribe
- disconnect
MQTT Client有两种,分别为IMqttClient和IMqttAsyncClient
- IMqttClient提供了一组方法,这些方法会阻塞当前线程,直到MQTT操作完成后才会执行之后的代码;
- IMqttAsyncClient提供了一组非阻塞方法,这些方法在对参数和状态进行初始验证后将控制权返回给调用应用程序。 主要处理在后台执行,以免阻塞应用程序线程。
- 当应用程序希望在发生MQTT动作时继续进行其它任务处理时,这种非阻塞方法非常方便。 例如,连接到MQTT服务器可能要花费一些时间,使用非阻塞连接方法允许应用程序在发生连接操作时显示进度指示器。
- 非阻塞方法在面向事件的程序和图形程序中特别有用。如果这些程序和图形程序中的主线程或GUI线程上存在长时间的阻塞,那么主线程或GUI会出现线程问题。
- 非阻塞的方法其实也可以实现阻塞,但是使用阻塞方法就只能阻塞。
- 注意非阻塞的client的对象的方法有些是不会阻塞的,比如发送消息的时候,是异步发送的。一定要注意这个,可以进入源码看,比如publish的底层调用的是这个方法,这个方法只是把消息放到了内存保存之后就返回了:
三种消息质量
- at most once 0,最多一次,根据基础TCP / IP网络的最大努力在此处传递消息,消息丢失或重复可能会发生。 例如,该级别可以与环境传感器数据一起使用,在该级别中,丢失单个读数并不重要,因为下一个读数将很快发布。
- at least once 1,最少一次,保证消息最少让客户端收到一次,但是在保证最少收到一次的情况下,会重发消息,导致消息重复接收。
- exactly once 2,恰好一次,确保消息恰好到达一次。 例如,此级别可用于计费系统,在计费系统中,重复或丢失的消息可能导致收取不正确的费用。
- 上面三种消息质量级别,即使网络连接中断,或者在传递消息时客户端或服务器停止,也会严格按照设定的质量传递消息。
主题命名及通配符
主题层级分隔符为: /
比如:
产品/型号/设备型号
在订阅的时候,支持的通配符有两个,一个是:+,一个是#
+:单层通配符。比如:产品/+,这样只能收到“产品/只有一级”的主题,如果是“产品/a/b”三层结构是不匹配的
#:多层通配符。比如产品/#,这样订阅可以把产品下面的所有消息都订阅,不管下面有几个层级。
在发送消息的时候不能使用通配符
MQTT的消息模型是订阅和发布
只要订阅了,都可以收到消息,订阅的时候支持通配符匹配订阅
ClientId
这个要是唯一的,如果有人用重复了,那么之前注册的用户会被顶掉。clientid的长度是有限的,代码里面规定是不能大于65535个字符
消息可靠性与消息持久化
可选的配置有MemoryPersistence和MqttDefaultFilePersistence。看名字就知道,一个是存在内存里面,一个是存在文件里面。如果设置了clean session为true或者不在乎重启数据可能丢失的情况,那么就用内存的,速度快。否则就用文件保存。