MQTT(Message Queuing Telemetry Transport)是一种轻量级的消息传输协议,基于发布 / 订阅(Publish/Subscribe)模式,专为资源受限的设备和低带宽、高延迟或不可靠的网络设计,广泛应用于物联网(IoT)、工业监控、移动应用等领域。
核心概念
客户端(Client):使用 MQTT 协议与 MQTT 代理进行通信的设备或应用程序。客户端可以是发布者(Publisher),负责发布消息到特定主题;也可以是订阅者(Subscriber),订阅感兴趣的主题以接收消息。
代理(Broker):作为 MQTT 网络的核心,负责接收客户端发布的消息,并将消息转发给订阅了相应主题的客户端。
主题(Topic):是消息的分类标识,客户端通过主题来区分不同类型的消息。主题采用分层结构,使用斜杠(/)分隔不同层级,例如 home/livingroom/temperature。
服务质量(QoS):定义了消息传输的可靠性级别,有三个等级:
QoS 0:最多一次,消息可能会丢失,不保证送达。
QoS 1:至少一次,消息会被发送,但可能会重复。
QoS 2:恰好一次,保证消息只被送达一次,实现相对复杂,开销较大。
工作流程
连接:客户端向代理发送连接请求(CONNECT),包含客户端 ID、用户名、密码等信息。代理验证通过后,返回连接确认(CONNACK)。
订阅:客户端向代理发送订阅请求(SUBSCRIBE),指定要订阅的主题和 QoS 级别。代理接收请求后,返回订阅确认(SUBACK)。
发布:客户端向代理发送发布消息(PUBLISH),包含主题、QoS 级别和消息内容。代理根据主题将消息转发给订阅了该主题的客户端。
取消订阅:客户端向代理发送取消订阅请求(UNSUBSCRIBE),指定要取消订阅的主题。代理接收请求后,返回取消订阅确认(UNSUBACK)。
断开连接:客户端向代理发送断开连接请求(DISCONNECT),结束与代理的连接。
示例代码如下:
kotlin
//在build.gradle中添加依赖
dependencies {
implementation ("org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.2.5")
}
//在类文件中定义函数
fun checkMqtt() {
// MQTT 代理地址
val brokerUrl = "tcp://broker.hivemq.com:1883" // 公共测试 Broker
// 客户端 ID
val clientId = "KotlinClient"
// 持久化方式,使用 MemoryPersistence 作为消息持久化方式,意味着消息将临时存储在内存中。
val persistence = MemoryPersistence()
try {
// 创建 MQTT 客户端实例,指定代理地址、客户端 ID 和持久化方式。
val mqttClient = MqttClient(brokerUrl, clientId, persistence)
// 创建 MQTT 连接选项
// 创建连接选项对象,设置 isCleanSession 为 true,表示建立一个干净的会话,不保留之前的订阅和未处理的消息
val connOpts = MqttConnectOptions()
connOpts.isCleanSession = true
// 连接到 MQTT 代理
println("Connecting to broker: $brokerUrl")
// 调用 connect 方法连接到指定的 MQTT 代理
mqttClient.connect(connOpts)
println("Connected")
// 订阅主题
// 定义要订阅的主题为 "test/topic",调用 subscribe 方法订阅该主题
val topic = "test/topic"
mqttClient.subscribe(topic)
// 第二个参数 1 代表的是 MQTT 协议里的服务质量(Quality of Service,QoS)级别。MQTT 协议提供了三种不同的 QoS 级别,用于规定消息传输的可靠性程度。
//mqttClient.subscribe(topic, 1) { topic, message ->
// println("Received message on topic $topic: ${String(message.payload)}")
//}
println("Subscribed to topic: $topic")
// 设置消息回调
// 使用 setCallback 方法设置一个匿名内部类作为消息回调处理对象,实现了 MqttCallback 接口的三个方法
mqttClient.setCallback(object : MqttCallback {
override fun connectionLost(cause: Throwable?) {
println("connectionLost: ${cause?.message}")
}
override fun messageArrived(topic: String?, message: MqttMessage?) {
println("messageArrived...received message on topic '$topic': ${message?.toString()}")
}
override fun deliveryComplete(token: IMqttDeliveryToken?) {
println("deliveryComplete...")
}
})
// 发布消息
// 定义要发布的消息内容为 "Hello, MQTT!",创建 MqttMessage 对象并将消息内容转换为字节数组
val message = "Hello, MQTT!"
val mqttMessage = MqttMessage(message.toByteArray())
// 设置消息的 QoS 级别为 1(至少一次送达)
mqttMessage.qos = 1
// 调用 publish 方法将消息发布到指定主题
mqttClient.publish(topic, mqttMessage)
println("Message published: $message")
// 等待一段时间,确保消息接收
Thread.sleep(5000)
// 断开连接
mqttClient.disconnect()
println("Disconnected")
} catch (e: MqttException) {
println("MqttException: ${e.message}")
}
}
上面代码的输出如下:
csharp
Connecting to broker: tcp://broker.hivemq.com:1883
Connected
Subscribed to topic: test/topic
messageArrived...received message on topic 'test/topic': Hello from MQTTnet v4.1.2
messageArrived...received message on topic 'test/topic': Hello, MQTT! from kotlin
Message published: Hello, MQTT! from kotlin
deliveryComplete...
Disconnected
在上面的输出中,出现两次messageArrived是因为:
第一次messageArrived:当代码订阅test/topic主题后,可能有其他客户端在该主题上发布了消息Hello from MQTTnet v4.1.2,所以你的客户端收到了这条消息,触发了messageArrived回调。由于代码中没有对收到的消息进行额外处理,所以不清楚具体是哪个客户端发送的以及发送的时间。
第二次messageArrived:是因为代码本身向test/topic主题发布了消息Hello, MQTT! from kotlin,发布后,代理服务器会将该消息发送给所有订阅了test/topic主题的客户端,包括当前客户端自己,所以会再次触发messageArrived回调。