MQTT协议分析与应用

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回调。

相关推荐
好学人11 小时前
Kotlin object 关键字详解
kotlin
好学人11 小时前
Kotlin sealed 关键字介绍
kotlin
岸芷漫步12 小时前
Kotlin中的序列化应用
kotlin
zimoyin14 小时前
整活 kotlin + springboot3 + sqlite 配置一个 SQLiteCache
jvm·sqlite·kotlin
alexhilton1 天前
Jetpack Compose的性能优化建议
android·kotlin·android jetpack
天枢破军1 天前
【KMP】桌面端打包指南
kotlin
_一条咸鱼_1 天前
深度解析 Android MVI 架构原理
android·面试·kotlin
好学人1 天前
Android MVVM 架构中的重要概念
kotlin·mvvm
好学人1 天前
一文弄懂 repeatOnLifecycle
kotlin·mvvm
天枢破军1 天前
【KMP】解决桌面端打包异常无法运行
kotlin