搭建EMQX
实现MQTT协议,broker地址。附带JAVA案例
EMQX 是全球最具可扩展性的开源 MQTT 代理,高性能连接 100M+ 物联网设备,同时保持每秒 1M 消息吞吐量和亚毫秒级延迟。
安装EMQX
docker run -d --restart=always --name emqx -p 18083:18083 -p 1883:1883 emqx:latest
安装完成后,打开地址:http://192.168.1.95:18083/ (我本地是192.168.1.95)
默认的登录名是 admin 密码是 public,第一次会提示修改密码,进入网页后,右上角setting 可以设置简体中文。
界面:
最后再代码中实现时候配置。
String broker = "tcp://192.168.1.95:1883";
java实现:
maven工程:
<dependency>
<groupId>org.eclipse.paho</groupId>
<artifactId>org.eclipse.paho.client.mqttv3</artifactId>
<version>1.2.5</version>
</dependency>
gradle工程:
implementation 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.2.5'
消息发布者
package mqtt;
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.nio.charset.Charset;
/**
* JAVA实现MQTT消息发布者
*/
public class MqttTest1 {
public static void main(String[] args) {
String topic = "test2";
int qos = 1;
String broker = "tcp://192.168.1.95:1883";
String userName = "yujing";
String password = "123456";
String clientId = "pubClient";
// 内存存储
MemoryPersistence persistence = new MemoryPersistence();
try {
// 创建客户端
MqttClient sampleClient = new MqttClient(broker, clientId, persistence);
// 创建链接参数
MqttConnectOptions connOpts = new MqttConnectOptions();
// 在重新启动和重新连接时记住状态
connOpts.setCleanSession(false);
// 设置连接的用户名
connOpts.setUserName(userName);
connOpts.setPassword(password.toCharArray());
connOpts.setConnectionTimeout(30);
// 建立连接
System.out.println("连接到 broker: " + broker);
sampleClient.connect(connOpts);
System.out.println("连接成功.");
int i=1;
while (true){
String s="哈哈,第"+(i++)+"次发送消息。";
// 创建消息
MqttMessage mmg=new MqttMessage(s.getBytes(Charset.defaultCharset()));
// 设置消息的服务质量
mmg.setQos(qos);
// 发布消息
System.out.println("向" + topic + "发送消息:" + s);
sampleClient.publish(topic, mmg);
Thread.sleep(3000);
}
// // 断开连接
// sampleClient.disconnect();
// // 关闭客户端
// sampleClient.close();
} 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();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
消息订阅者
package mqtt;
import org.eclipse.paho.client.mqttv3.*;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
/**
* java实现MQTT,订阅者
*/
public class MqttTest2 {
public static void main(String[] args) {
//EMQ X 默认端口 1883
String broker = "tcp://192.168.1.95:1883";
String TOPIC = "test2";
int qos = 1;
String userName = "yujing";
String passWord = "123456";
String clientId = "subClient";
try {
// MQTT的连接设置
MqttConnectOptions options = new MqttConnectOptions();
// 设置是否清空session,这里如果设置为false表示服务器会保留客户端的连接记录,这里设置为true表示每次连接到服务器都以新的身份连接
options.setCleanSession(true);
// 设置连接的用户名
options.setUserName(userName);
// 设置连接的密码
options.setPassword(passWord.toCharArray());
// 设置超时时间 单位为秒
options.setConnectionTimeout(10);
// 设置会话心跳时间 单位为秒 服务器会每隔1.5*20秒的时间向客户端发送个消息判断客户端是否在线,但这个方法并没有重连的机制
options.setKeepAliveInterval(20);
// host为主机名,test为clientId即连接MQTT的客户端ID,一般以客户端唯一标识符表示,MemoryPersistence设置clientId的保存形式,默认为以内存保存
MqttClient client = new MqttClient(broker, clientId, new MemoryPersistence());
// 设置回调函数
client.setCallback(new MqttCallback() {
public void connectionLost(Throwable cause) {
System.out.println("connectionLost");
}
public void messageArrived(String topic, MqttMessage message) {
System.out.println("======监听到来自[" + topic + "]的消息======");
System.out.println("内容:"+new String(message.getPayload()));
}
public void deliveryComplete(IMqttDeliveryToken token) {
System.out.println("deliveryComplete---------"+ token.isComplete());
}
});
// 建立连接
System.out.println("连接到 broker: " + broker);
client.connect(options);
System.out.println("连接成功.");
//订阅消息
client.subscribe(TOPIC, qos);
System.out.println("开始监听" + TOPIC);
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行效果:
封装后使用
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.*;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeoutException;
/**
* mqtt协议,服务器端实现
* 当连接不上MQTT服务器,会每隔一定时间自动重新连接。
* 由于对于mqtt协议来说,本来是没有服务器概念,大家都是客户端,每个客户端订阅一个TOPIC(主题),只要其他客户端向这个TOPIC发送数据,所有订阅了该TOPIC的设备都能收到。
* 官方文档:<a href="https://www.emqx.com/zh/mqtt">快速入门</a>
* 官方文档:<a href="https://www.emqx.io/docs/zh/v5.0/">详细文档</a>
*
* @author yujing 2023年3月28日10:20:45
*/
/*
使用举例:
//订阅了三个主题server,offline,online
String id = "server/" + UUID.randomUUID().toString().replace("-", "").substring(18);
MQTT mqtt = new MQTT("tcp://192.168.1.95:1883", id, new String[]{"server", "offline", "online"});
mqtt.setMessageListener(this);
mqtt.init();
//发送消息
mqtt.send(topic, msg);
//接收消息
@Override
public void messageArrived(String topic, MqttMessage message) {
println("收到消息" + message.toString());
switch (topic) {
case "offline" ->println("有设备掉线了:" + message);
case "online" ->println("有设备上线了:" + message);
case "server" -> {
String json = message.toString();
//解析消息
try {
int command = mapper.readTree(json).get("command").asInt();
//指令分发
switch (command) {
case 1 -> command1(json);
case 2 -> command2(json);
case 3 -> command3(json);
case 4 -> command4(json);
case 5 -> command5(json);
case 6 -> command6(json);
}
} catch (JsonProcessingException e) {
System.err.println("json解析失败");
e.printStackTrace();
}
}
}
}
*/
public class MQTT {
private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(MQTT.class);
//EMQX默认端口1883举例:tcp://192.168.1.95:1883
String broker = "";
int qos = 2; //发送消息时候的qos,0:至多一次,1:至少一次,2:确保只有一次。
String userName = ""; //用户名,可以空
String passWord = ""; //用户密码,可以空
String clientId = ""; //自己唯一id
//要订阅的主题些
String[] subscribeTopics; // = new String[]{"server", "offline", "online"};
int[] subscribeQoSs; // = new int[]{qos, qos, qos};
String willTopic = "offline"; //遗嘱主题,当自己掉线时,会向这个主题通知消息,暂定消息内容为自己id
/**
* MQTT的连接设置
*/
MqttConnectOptions options;
/**
* MQTT的客户端,不采用MqttClient是因为MqttAsyncClient才能知道每一步状态成功与否
*/
MqttAsyncClient client;
/**
* 消息监听
*/
MessageListener messageListener;
//一个线程的线程池
ExecutorService executorService;
/**
* @param broker mqtt服务器地址
* @param clientId 自己的唯一id
*/
public MQTT(String broker, String clientId, String topic) {
this(broker, clientId, new String[]{topic}, null);
}
public MQTT(String broker, String clientId, String[] topics) {
this(broker, clientId, topics, null);
}
public MQTT(String broker, String clientId, String[] topics, int[] qoSs) {
this.broker = broker;
this.clientId = clientId;
this.subscribeTopics = topics;
this.subscribeQoSs = qoSs;
if (qoSs == null) {
this.subscribeQoSs = new int[topics.length];
Arrays.fill(this.subscribeQoSs, qos);
}
executorService = Executors.newSingleThreadExecutor();
}
/**
* 初始化
*/
public void init() {
try {
create();
connect();
} catch (MqttException | TimeoutException e) {
e.printStackTrace();
}
}
private void create() throws MqttException {
// MQTT的连接设置
options = new MqttConnectOptions();
// 设置是否清空session,这里如果设置为false表示服务器会保留客户端的连接记录,这里设置为true表示每次连接到服务器都以新的身份连接
options.setCleanSession(true);
// 设置连接的用户名
options.setUserName(userName);
// 设置连接的密码
options.setPassword(passWord.toCharArray());
// 设置连接超时
options.setConnectionTimeout(15);
// 设置超时时间 单位为秒
options.setConnectionTimeout(10);
// 设置会话心跳时间 单位为秒 服务器会每隔1.5*20秒的时间向客户端发送个消息判断客户端是否在线,但这个方法并没有重连的机制
options.setKeepAliveInterval(20);
//遗嘱消息,保留消息
options.setWill(willTopic, clientId.getBytes(), 1, true);
// host为主机名,test为clientId即连接MQTT的客户端ID,一般以客户端唯一标识符表示,MemoryPersistence设置clientId的保存形式,默认为以内存保存
client = new MqttAsyncClient(broker, clientId, new MemoryPersistence());
MqttCallback callback = new MqttCallback() {
public void connectionLost(Throwable cause) {
println("MQTT连接断开");
try {
connect();
} catch (MqttException | TimeoutException e) {
println("MQTT自动重连失败" + e.getMessage());
}
}
public void messageArrived(String topic, MqttMessage message) {
//System.out.println("MQTT接收消息内容 : " + message);
if (messageListener != null) {
//为什么要开线程?1.如果在这儿有耗时操作,那么就暂时收不到之后的数据。2.如果这儿有发送操作,发送里面有阻塞,那么发可能会发生死锁。因为messageArrived执行完毕后,才会触发发送是否成功的回调。
//为什么要线程池,且数量为1,因为上面第二个问题虽然可以开线程解决,但是同时收到多条消息时,由于处理时间不一致,可能导致队列顺序混乱。
executorService.execute(() -> {
try {
messageListener.messageArrived(topic, message);
} catch (Exception e) {
e.printStackTrace();
}
});
}
}
public void deliveryComplete(IMqttDeliveryToken token) {
//System.out.println("发送状态:" + token.isComplete() + ",消息id:" + token.getMessageId());
}
};
// 设置回调函数
client.setCallback(callback);
}
/**
* 连接
*/
private boolean connect() throws MqttException, TimeoutException {
// 建立连接
ActionListener listener = new ActionListener("MQTT连接", 1000 * 30);
client.connect(options, "Connect context", listener);
listener.lock();
//如果连接失败,自动重连
if (listener.isSuccess()) {
subscribe();
} else {
connect();
}
return listener.isSuccess();
}
/**
* 订阅
*/
private boolean subscribe() throws MqttException, TimeoutException {
ActionListener listener = new ActionListener("MQTT订阅", 1000 * 20);
//订阅主题
client.subscribe(subscribeTopics, subscribeQoSs, "Subscribe context", listener);
listener.lock();
if (listener.success) {
//通知在线了,保留消息
client.publish("online", clientId.getBytes(), 1, true);
}
return listener.isSuccess();
}
/**
* 发送消息
*/
//举例:send("recycling",msg)
public boolean send(String topic, String msg) {
try {
// 创建消息
MqttMessage message = new MqttMessage(msg.getBytes(Charset.defaultCharset()));
// 设置消息的服务质量
message.setQos(qos);
ActionListener listener = new ActionListener("MQTT发送", 1000 * 20);
client.publish(topic, message, null, listener);
listener.lock();
return listener.isSuccess();
} catch (MqttException | TimeoutException e) {
println(e.getMessage());
e.printStackTrace();
return false;
}
}
/**
* 断开连接并且关闭客户端
*/
public void disconnectAndClose() {
try {
// 断开连接
client.disconnect();
} catch (MqttException e) {
e.printStackTrace();
}
// 关闭客户端
try {
client.close();
} catch (MqttException e) {
e.printStackTrace();
}
broker = "";
userName = "";
passWord = "";
clientId = "";
options = null;
client = null;
messageListener = null;
executorService.shutdown();
}
private static void println(String str) {
//System.out.println(str);
log.info(str);
}
public String getBroker() {
return broker;
}
public void setBroker(String broker) {
this.broker = broker;
}
public int getQos() {
return qos;
}
public void setQos(int qos) {
this.qos = qos;
}
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 getClientId() {
return clientId;
}
public void setClientId(String clientId) {
this.clientId = clientId;
}
public MqttConnectOptions getOptions() {
return options;
}
public void setOptions(MqttConnectOptions options) {
this.options = options;
}
public MqttAsyncClient getClient() {
return client;
}
public void setClient(MqttAsyncClient client) {
this.client = client;
}
public MessageListener getMessageListener() {
return messageListener;
}
public void setMessageListener(MessageListener messageListener) {
this.messageListener = messageListener;
}
public String[] getSubscribeTopics() {
return subscribeTopics;
}
public void setSubscribeTopics(String[] subscribeTopics) {
this.subscribeTopics = subscribeTopics;
}
public int[] getSubscribeQoSs() {
return subscribeQoSs;
}
public void setSubscribeQoSs(int[] subscribeQoSs) {
this.subscribeQoSs = subscribeQoSs;
}
public String getWillTopic() {
return willTopic;
}
public void setWillTopic(String willTopic) {
this.willTopic = willTopic;
}
//-----------------------------------------------------内部类-----------------------------------------------------
/**
* 监听连接,订阅,发送状态,异步转同步
*
* @author yujing 2023年3月28日16:22:26
*/
static class ActionListener implements IMqttActionListener {
/**
* 监听器名称
*/
String name;
/**
* 是否成功
*/
boolean success;
/**
* 超时时间
*/
int timeOut = 5000;
/**
* 锁
*/
final Object waiter = new Object();
public ActionListener(String name) {
this.name = name;
}
public ActionListener(String name, int timeOut) {
this.name = name;
this.timeOut = timeOut;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public boolean isSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
@Override
public void onSuccess(IMqttToken iMqttToken) {
println(name + "成功");
success = true;
unLock();
}
@Override
public void onFailure(IMqttToken iMqttToken, Throwable throwable) {
println(name + "失败");
success = false;
unLock();
}
//异步转同步,加锁
public void lock() throws TimeoutException {
synchronized (waiter) {
try {
waiter.wait(timeOut);
} catch (InterruptedException e) {
println("超时");
e.printStackTrace();
throw new TimeoutException("超时");
}
}
}
//异步转同步,解锁
public void unLock() {
synchronized (waiter) {
waiter.notifyAll();
}
}
}
//-----------------------------------------------------监听器-----------------------------------------------------
/**
* 监听消息
*/
interface MessageListener {
void messageArrived(String topic, MqttMessage message) throws Exception;
}
}
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Arrays;
import java.util.UUID;
public class MqttService {
static ObjectMapper mapper = new ObjectMapper();
public static void main(String[] args) throws InterruptedException, JsonProcessingException {
String id = "server/" + UUID.randomUUID().toString().replace("-", "").substring(18);
MQTT mqtt = new MQTT("tcp://192.168.1.95:1883", id, new String[]{"server", "offline", "online"});
mqtt.setMessageListener((topic, message) -> {
System.out.println("收到消息,主题:" + topic + " 内容:" + message.toString());
switch (topic) {
case "offline" -> System.out.println("有设备掉线了:" + message);
case "online" -> System.out.println("有设备上线了:" + message);
case "server" -> {
String json = message.toString();
}
}
});
mqtt.init();
var mMsg = new MqttMsg<>(1, Arrays.asList("哈哈哈", "嘿嘿嘿"));
String json = mapper.writeValueAsString(mMsg);
Thread.sleep(5000);
mqtt.send("client/01", json);
Thread.sleep(5000);
mqtt.send("client/02", json);
}
}
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Arrays;
public class MqttTest1 {
static ObjectMapper mapper = new ObjectMapper();
public static void main(String[] args) throws InterruptedException {
String id = "client/01";//"client/" + UUID.randomUUID().toString().replace("-", "").substring(18);
MQTT mqtt = new MQTT("tcp://192.168.1.95:1883", id, "client/01");
mqtt.setMessageListener((topic, message) -> {
System.out.println("收到消息,主题:" + topic + " 内容:" + message.toString());
var mMsg = new MqttMsg<>(1, Arrays.asList("999999", "00000000000", 333, 123.45));
String json = mapper.writeValueAsString(mMsg);
//回复
mqtt.send("server", json);
});
mqtt.init();
}
}
运行结果 client/01
[2023-04-08 17:19:02 下午]:INFO mqtt.MQTT.println(MQTT.java:261)MQTT连接成功
[2023-04-08 17:19:02 下午]:INFO mqtt.MQTT.println(MQTT.java:261)MQTT订阅成功
收到消息,主题:client/01内容:{"command":1,"data":["哈哈哈","嘿嘿嘿"]}
[2023-04-08 17:19:12 下午]:INFO mqtt.MQTT.println(MQTT.java:261)MQTT发送成功
运行结果 server
[2023-04-08 17:19:07 下午]:INFO mqtt.MQTT.println(MQTT.java:261)MQTT连接成功
[2023-04-08 17:19:07 下午]:INFO mqtt.MQTT.println(MQTT.java:261)MQTT订阅成功
收到消息,主题:online 内容:client/01
有设备上线了:client/01
收到消息,主题:online 内容:server/34a6d98563b424
有设备上线了:server/34a6d98563b424
[2023-04-08 17:19:12 下午]:INFO mqtt.MQTT.println(MQTT.java:261)MQTT发送成功
收到消息,主题:server内容:{"command":1,"data":["999999","00000000000",333,123.45]}
[2023-04-08 17:19:17 下午]:INFO mqtt.MQTT.println(MQTT.java:261)MQTT发送成功