mqtt java使用
本文是学习emqx系列文章的第一篇,主要讲下mqtt是什么,以及java中如何快速上手。
MQTT
MQTT(Message Queue Telemerty Transport)是一种二进制协议,主要用于服务器和那些低功耗的物联网设备(IoT)之间的通信。
它位于 TCP 协议的上层,除了提供发布-订阅这一基本功能外,也提供一些其它特性:不同的消息投递保障(delivery guarantee),“至少一次”和“最多一次”。通过存储最后一个被确认接受的消息来实现重连后的消息恢复。
它非常轻量级,并且从设计和实现层面都适合用于不稳定的网络环境中。
对比下我之前用过的RabbitMq实现的是AMQP高级消息队列协议(AMQP,Advanced Message Queuing Protocol). 功能强大可靠性强,但是不够轻量.
pom依赖
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-mqtt</artifactId>
</dependency>
<!-- 实际依赖的是
<dependency>
<groupId>org.eclipse.paho</groupId>
<artifactId>org.eclipse.paho.client.mqttv3</artifactId>
<version>1.2.5</version>
</dependency>
-->
创建client 建立链接
建立链接 , 使用的方式是新建client, 然后链接emq的broker地址
- 创建client, 配置emq的地址, clientId当前服务名(唯一),MqttClientPersistence 消息传输过程中,缓存内容
- 配置链接选项, 用户名, 密码,等...
- 建立链接
- 设置回调, 接收链接成功与否, 消息投递成功与否
- 订阅消息, 配置 topic以及对应消息监听器
@PostConstruct
public void createClient() throws MqttException {
try {
client = new MqttClient(mqtt.getBroker(), mqtt.getClientId(), new MemoryPersistence());
// MQTT 连接选项
MqttConnectOptions connOpts = new MqttConnectOptions();
connOpts.setUserName(mqtt.getUsername());
connOpts.setPassword(mqtt.getPassword().toCharArray());
// 清除会话
connOpts.setCleanSession(true);
// 心跳间隔
connOpts.setKeepAliveInterval(180);
// 建立链接
client.connect(connOpts);
// 设置回调
client.setCallback(new OnMessageCallback());
// 订阅消息
for (TopicListener topicListener : topicListeners) {
client.subscribe(topicListener.getTopic(), topicListener);
}
} catch (MqttException me) {
log.error("reason:{} ", me.getReasonCode());
log.error("msg {}", me.getMessage());
log.error("loc {}", me.getLocalizedMessage());
log.error("cause :{}", JSONUtil.toJsonStr(me.getCause()));
throw me;
}
}
@Component
static class OnMessageCallback implements MqttCallback {
@Override
public void connectionLost(Throwable cause) {
// 连接失败
log.error("连接断开,cause: {} ", cause.getMessage(), cause);
}
@Override
public void messageArrived(String topic, MqttMessage message) {
// subscribe后得到的消息会执行到这里面
}
@Override
public void deliveryComplete(IMqttDeliveryToken token) {
log.info("消息投递成功 deliveryComplete---------" + token.isComplete());
}
}
订阅消息
方法一
在订阅对应topic后 client.subscribe("test.topic"), 实现回调器client.setCallback(new OnMessageCallback())
即可在 MqttCallback.messageArrived(topic,message)中订阅到消息
方法二
使用client.subscribe("test.topic", IMqttMessageListener)
这种方式为每个 topic 配置监听器, 实现IMqttMessageListener.messageArrived(topic,message)得到订阅的消息
建议使用第二种, 业务逻辑复杂后,针对单个topic 实现监听器,更清晰
ps: callback和listener,分别对应 CommsCallback中的两处被调用
CommsCallback.java
protected boolean deliverMessage(String topicName, int messageId, MqttMessage aMessage) throws Exception{
boolean delivered = false;
Enumeration<String> keys = callbacks.keys(); // 获取topic订阅列表
while (keys.hasMoreElements()) {
String topicFilter = (String)keys.nextElement();
// callback may already have been removed in the meantime, so a null check is necessary
// topic 对应的 messageListener
IMqttMessageListener callback = callbacks.get(topicFilter);
if(callback == null) {
continue;
}
// 匹配 topic, topic匹配规则(通配符等...)
if (MqttTopic.isMatched(topicFilter, topicName)) {
aMessage.setId(messageId);
// 匹配成功就调用 IMqttMessageListener.messageArrived()
((IMqttMessageListener)callback).messageArrived(topicName, aMessage);
delivered = true;
}
}
/* if the message hasn't been delivered to a per subscription handler, give it to the default handler */
if (mqttCallback != null && !delivered) {
aMessage.setId(messageId);
// topic未匹配到监听器,就调用callback.messageArrived()
mqttCallback.messageArrived(topicName, aMessage);
delivered = true;
}
return delivered;
}
topic规则
主题名中可以包含通配符,单层通配符“+”和多层通配符“#”。使用包含通配符的主题名可以订阅满足匹配条件的所有主题。为了和 PUBLISH 中的主题区分,我们叫 SUBSCRIBE 中的主题名为主题过滤器(Topic Filter)。
-
单层通配符“+”:“+”可以用来指代任意一个层级。 如“sensor/+/tem”,
可以匹配:
- sensor/data/tem
- sensor/cmd/tem
不可以匹配
- sensor/data/01/tem
-
多层通配符“#”: “#”和“+”的区别在于,“#”可以用来指代任意多个层。**但是"#"必须是Topic Filter的最后一个字符,同时必须跟在“/“后面,除非Topic Filter只包含一个”#“这一个字符。**如“#”是一个合法的Topic Filter,而“sensor#”不是一个合法的Topic Filter。
如“sensor/data/#”,
可匹配:
- sensor/data
- sensor/data/tem
- sensor/data/tem/01
- ensor/data/tem/01/02
不可以匹配:
- sensor/cmd/tem
对应的源码就是上面提到的 MqttTopic.isMatched(topicFilter,topicName). 太长了,不贴了。
发送消息
@Resource
private MqttClient client;
public void send(@RequestParam String message) {
// 消息内容
MqttMessage mqttMessage = new MqttMessage(message.getBytes());
// 消息qos模式
mqttMessage.setQos(2);
try {
// 推送消息到指定 topic
client.publish("/test", mqttMessage);
} catch (MqttException e) {
e.printStackTrace();
log.info(e.getMessage());
}
}
qos–quality of service 要使用的“服务质量”。设置为0、1、2。
qos:0—表示消息最多只能传递一次(零次或一次)。消息将不会持久化到磁盘,也不会通过网络进行确认。此QoS是最快的,但仅适用于没有价值的消息。如果服务器无法处理该消息(例如,存在授权问题),会直接显示发送完成,去回调这个接口MqttCallback.deliveryComplete(IMqttDeliveryToken)。这种模式也被称为“fire and forget”,即发送后就不管了。
qos:1—表示消息应至少传递一次(一次或多次)。只有能够持久化消息,才能安全地传递消息,因此应用程序必须使用MqttConnectOptions提供持久化方法。如果未指定持久性机制,则在客户端发生故障时不会传递消息。消息将通过网络得到确认。这是默认的QoS。
qos:2-表示消息应传递一次。消息将被保存到磁盘上,并将通过网络进行两阶段确认。只有能够持久化消息,才能安全地传递消息,因此应用程序必须使用MqttConnectOptions提供持久化方法。如果未指定持久性机制,则在客户端发生故障时不会传递消息。 如果没有配置持久性,当网络或服务器出现问题时,QoS 1和QoS 2消息仍将被传递,因为客户端将在内存中保持状态。如果MQTT客户机关闭或失败,并且未配置持久性,则无法维护QoS 1和2消息的传递,因为客户端状态将丢失。