这是我参与8月更文挑战的第5天,活动详情查看:8月更文挑战
简介
MQTT(MQ Telemetry Transport)协议是物联网应用最广泛的协议,MQTT是指IBM的MQseries产品,与“消息队列”无关。当时需要开发一种最小电池损耗,最小宽带的协议,起初主要应用于嵌入式现在转变为开放物联网(IOT)用例。
发布/订阅模式
发布订阅模式广泛的应用于生活中,例如你订阅了微信公众号,作者发布了的公众号文章会推送给每个订阅者,公众号就是Publish ,读者就是 Subscriber ,文章就是 Message。
这个场景的特色在于:
- 空间解耦:Publish 和 Subscriber不需要相互知道他们的存在
- 时间解耦:Publish 和 Subscriber不需要同时运行
- 同步解耦:Publish 和 Subscriber不需要交互,在发布和接收期间不会相互锁定
总之,发布/订阅模型消除了消息发布者和接收者/订阅者之间的直接通信。代理的过滤活动可以控制哪个客户端/订阅者接收哪个消息。解耦包括三个维度:空间、时间和同步。
MQTT的应用
MQTT体现了发布/订阅模式的所有方面
- MQTT 在空间上分离了发布者和订阅者。要发布或接收消息,发布者和订阅者只需要知道代理的主机名/IP 和端口。
- MQTT 按时间解耦。尽管大多数 MQTT 用例都近乎实时地传递消息,但如果需要,代理可以为不在线的客户端存储消息。(必须满足两个条件才能存储消息:客户端已连接到持久会话并订阅了QoS大于 0 的主题)。
- 异步工作。大多数Client 基于异步或回调模型,因此等待消息和发布消息不会阻塞任务。当然某些用例是可以同步,所以某些实现会提供同步API。但流程是同步的
MQTT与消息队列的区别
- 消息队列的消息直到被消费都会被存储
- 消息队列一条消息只能被一个客户端消费,MQTT的订阅者都会受到消息
- 队列在使用之前要用命令显示创建队列,topic 可以实时创建。
实时创建topic
//java服务器 Vert.x MQTT 相关代码
endpoint.publishHandler(message -> {
System.out.println("Just received message [" + message.payload().toString(Charset.defaultCharset()) + "] with QoS [" + message.qosLevel() + "]");
if (message.qosLevel() == MqttQoS.AT_LEAST_ONCE) {
endpoint.publishAcknowledge(message.messageId());
} else if (message.qosLevel() == MqttQoS.EXACTLY_ONCE) {
endpoint.publishReceived(message.messageId());
}
}).publishReleaseHandler(messageId -> {
endpoint.publishComplete(messageId);
});
使用MQTTX发消息
Topic: test1QoS: 2
{
"msg": "这是test1"
}
2021-08-04 15:37:17
程序展示结果
使用MQTTX发消息
Topic: tes2QoS: 2
{
"msg": "这是test2"
}
2021-08-04 15:39:14
程序展示结果
Just received message [{ "msg": "这是test2"}] with QoS [EXACTLY_ONCE]
服务的建立与连接
MQTT 协议基于 TCP/IP。客户端和代理都需要有一个 TCP/IP 堆栈。
MQTT 连接始终位于一个客户端和代理之间。客户端从不直接相互连接。要发起连接,客户端向代理发送 CONNECT 消息。代理使用 CONNACK 消息和状态代码进行响应。建立连接后,代理将保持打开状态,直到客户端发送断开连接命令或连接中断。
MQTTClient
public static void main(String[] args) {
Vertx vertx = Vertx.vertx();
MqttClientOptions options = new MqttClientOptions()
.setUsername("Oneths").setPassword("123456")
.setWillFlag(true).setWillRetain(true).setWillQoS(1).setWillTopic("oneths").setWillMessage("ONETHS_IOT");
MqttClient client = MqttClient.create(vertx, options);
client.connect(40000, "127.0.0.1", s -> {
client.disconnect();
});
}
MQTTServer
public static void main(String[] args) {
Vertx vertx = Vertx.vertx();
MqttServerOptions options = new MqttServerOptions()
.setPort(40000);
MqttServer mqttServer = MqttServer.create(vertx,options);
//略
}
发布订阅和取消订阅
发布(Publish)
MQTT 客户端可以在连接到代理后立即发布消息。MQTT 在代理上使用基于主题的消息过滤。每条消息都必须包含一个主题,代理可以使用该主题将消息转发给感兴趣的客户端。通常,每条消息都有一个负载,其中包含要以字节格式传输的数据
Topic 名称
主题名称是一个简单的字符串 ,它以正斜杠作为分隔符进行分层结构 ,例如 Germany/Munich/Octoberfest/people/myhome/livingroom/temperature
使用工具MTQQX后可以图形化操作
QoS(服务质量)
有三个级别:0、1 和 2。服务级别决定了消息到达预期接收者(客户端或代理)的保证类型
QoS 0 - 最多一次:最低等级,此服务最大努力交付,不能保证交货。接收方 不会确认收到消息,发送方 不会存储和重新传输。
QoS 1 - 至少一次 : 发送方 消息可以多次发送,保证消息至少传递一次给接收者,发送方会存贮消息,直到接收到 接收方发送的 PUBACK 数据包。接收方接收到之后,可以立即处理,如果是代理接收,代理会回复PUBACK 并且给所有订阅者发送消息,如果重复发送信息 会设置重复 (DUP) 标志。
Qos 2 - 恰好一次: MQTT 中最高级别的服务。此级别保证每条消息仅被预期收件人接收一次。QoS 2 是最安全、最慢的服务质量级别。
发送方发送 QoS 2 PUBLISH数据包 给接受方 ,接受方回复PUBREC 数据包 让发送方确认。如果发送方没有收到会重复发送 QoS 2 PUBLISH数据包 。直到收到PUBREC 数据包
一旦发送方收到来自接收方的 PUBREC 数据包,发送方就可以安全地丢弃初始的 PUBLISH 数据包。发送方存储来自接收方的 PUBREC 数据包,并以PUBREL数据包进行响应 。
package io.netty.handler.codec.mqtt;
public enum MqttQoS {
AT_MOST_ONCE(0),
AT_LEAST_ONCE(1),
EXACTLY_ONCE(2),
FAILURE(0x80);
//略
}
订阅(Subscribe)
如果没有人收到消息,发布消息就没有意义。换句话说,如果没有客户端订阅消息的主题。要接收有关感兴趣主题的消息,客户端向MQTT 代理发送SUBSCRIBE消息。这个订阅消息非常简单,它包含一个唯一的数据包标识符和一个订阅列表。
订阅部分代码
endpoint.subscribeHandler(subscribe -> {
List<MqttQoS> grantedQosLevels = new ArrayList<>();
for (MqttTopicSubscription s: subscribe.topicSubscriptions()) {
System.out.println("Subscription for " + s.topicName() + " with QoS " + s.qualityOfService());
grantedQosLevels.add(s.qualityOfService());
}
// ack the subscriptions request
endpoint.subscribeAcknowledge(subscribe.messageId(), grantedQosLevels);
程序反馈的消息
Subscription for test1 with QoS AT_MOST_ONCE
Subscription for test2 with QoS EXACTLY_ONCE
Subscription for test3 with QoS AT_LEAST_ONCE
推荐
本文的理论部分主要参考 HiveMq编写的 mqtt入门文档
本文的 代码实现部分主要参考
工具