MQTT客户端在HarmonyOS应用中的搭建

本文介绍了MQTT服务端部署,测试到MQTT客户端搭建的全过程,全文篇幅有点长,希望各位大佬能耐心查阅,多多指教。

什么是MQTT?

在上个项目的开发中,遇到一个很特殊的需求,在鸿蒙中搭建MQTT的客户端,刚接到的需求的时候也是一脸懵,MQTT是啥东西,都没接触过,然后经过一系列的查询,终于搞清楚了什么是MQTT:

MQTT(Message Queuing Telemetry Transport,消息队列遥测传输协议),是一种基于发布/订阅(publish/subscribe)模式的“轻量级”通讯协议,该协议构建于TCP/IP协议上,由IBM在1999年发布。
MQTT最大优点在于,用极少的代码和有限的带宽,为连接远程设备提供实时可靠的消息服务

于是下个难题出现了,这个协议在鸿蒙中该如何搭建,于是我去 鸿蒙开发者论坛 一搜索,发现鸿蒙中好像根本不支持这协议:
在这里插入图片描述
于是我寻思在Android和IOS中竟然能实现,那在鸿蒙中应该也是能实现,经过了大量的资料查询,在鸿蒙开源三方库资源汇总 中终于找到了基于鸿蒙的MQTT客户端第三方库 : @ohos/mqtt

鸿蒙开源三方库资源汇总:https://gitee.com/openharmony-tpc/tpc_resource
@ohos/mqtt:https://gitee.com/openharmony-tpc/ohos_mqtt

搭建MQTT服务端并测试

由于 @ohos/mqtt 只能应用于鸿蒙客户端的搭建,对于初次使用且没有MQTT服务端环境的同学不是特别友好,所以本文会在开始前介绍如何部署一个简易MQTT服务器用来生产消息,且用可视化客户端工具连接服务器,测试服务器是否部署成功 ,若是有服务端环境的同学则可以跳过本节。

一、开通云服务(免费)

笔者这边推荐一个免费的MQTT云服务,EMQX Cloud,这个网站新人注册会有免费开通云服务的资格,直接注册即可

EMQX Cloudhttps://www.emqx.com/zh/cloud

步骤如下:
1.点击免费试用
在这里插入图片描述
2.点击购买专有版,输入部署名称,其他选项默认,首次注册购买会有14天免费使用期限,到期删除部署即可
在这里插入图片描述
3.查看连接信息
云服务开通后会进入如何页面,点击 概览,下图红圈中的连接信息即使我们客户端需要的信息,如图所示可以使用各种连接协议,我们本次使用的是 MQTT over TCP 端口为 1883
在这里插入图片描述

4.添加客户端认证
如下图所示,点击添加,输入所需要登录的客户端账号密码,这个后面会用到,写个自己熟悉的就好
在这里插入图片描述
至此,MQTT云服务已部署完毕,很简单吧,哈哈哈哈
接下来我们来测试云服务是否能正常发送消息

二、在MQTTX中连接服务器

首先我们需要下载一个可视化客户端工具:MQTTX

MQTTX :https://mqttx.app/zh

下载完成之后打开工具,先设置成中文显示,如下图所示
在这里插入图片描述

然后点击配置进入配置页面,配置连接地址,用户名,密码等
在这里插入图片描述
名称随意设置一个,ClientID随机即可,协议选择mqtt://,ip在刚刚云服务中的连接信息可以查看,端口为:1883,用户名和密码即我们刚刚设置的,至此连接信息配置完毕,点击右上角连接
在这里插入图片描述
连接成功之后,我们点击添加订阅,设置订阅主题,主题名为随意设置,记住即可,我这边设置的是:topic1,qos设置为1,至少一次
在这里插入图片描述

回到主页面,点击左侧主题,变色即表示订阅成功,然后点击右下角发送消息,在发送视图中如果我们能看到接收到的服务端的消息,则表示测试成功,我们部署的云服务可以成功连接。
在这里插入图片描述

在鸿蒙应用中搭建MQTT客户端

好了,铺垫了这么多,终于到了写代码的阶段了

一、安装@ohos/mqtt

1.安装

ohpm install @ohos/mqtt

2.开启网络权限
module.json5 中开启网络服务权限

"requestPermissions": [
   {
      "name": "ohos.permission.INTERNET",
    }
]

二、封装MQTTClient方法

根据 @ohos/mqtt文档,搭建MQTT客户端的第一步是创建一个MQTT实例

1.创建实例

import {
  MqttAsync,
  MqttClient,
  MqttMessage,
  MqttQos,
  MqttResponse,
  MqttSubscribeOptions,
} from '@ohos/mqtt';
import emitter from '@ohos.events.emitter';

 public createMqttClient() {
   this.mqttClient = MqttAsync.createMqtt({
     url: this.url,
     clientId: this.clientId,
     persistenceType: 1 // 客户端使用的持久性类型,0=默认值:使用默认(基于文件系统)持久性机制。1=在内存持久性中使用。2=使用特定于应用程序的持久性实现
   })
 }

2.连接服务器

  public async connectMqtt() {
    if (!this.mqttClient) {
      return
    }
    await this.mqttClient.connect({
      userName: this.userName,
      password: this.password,
      connectTimeout: 30, // 设置连接超时时间,默认
      automaticReconnect: true, // 是否在连接丢失的情况下自动重新连接
      MQTTVersion: 0, // 设置要在连接上使用的MQTT版本, 0=默认值:从3.1.1开始,如果失败,则返回到3.1,3=仅尝试版本3.1,4=仅尝试3.1.1版本
      // sslOptions:{
      //   enableServerCertAuth: true, // 是否开启服务端证书认证,默认为false
      //    trustStore: fileDir + "/ca.crt" // CA证书的沙箱路径
      // }
    }).then((res: MqttResponse) => {
      console.info('MQTT服务器连接连接成功:' + JSON.stringify(res.message))
    }).catch((err: Error) => {
      console.error('MQTT服务器连接连接失败:' + JSON.stringify(err))
    })
  }

3.订阅及监听主题

public async subscribe() {
    if (!this.mqttClient) {
      return
    }
    let subscribeOption: MqttSubscribeOptions = {
      topic: this.topic,
      qos: this.qos as MqttQos
    }
    await this.mqttClient.subscribe(subscribeOption).then((res: MqttResponse) => {
      console.info('MQTT订阅主题成功:', JSON.stringify(res.message))
    }).catch((err: Error) => {
      console.error('MQTT订阅主题失败:', JSON.stringify(err))
    })
    // 监听订阅主题
    this.messageArrived()
  }

  // 监听订阅主题
  public messageArrived() {
    if (!this.mqttClient) {
      return
    }
    // 监听主题发送消息
    this.mqttClient.messageArrived((err: Error, data: MqttMessage) => {
      if (err) {
        console.error('MQTT接收消息失败:', JSON.stringify(err))
      } else {
        console.info('MQTT接收消息成功:', JSON.stringify(data))
        // 发送消息至线程
        emitter.emit({
          eventId: EVENTID,
          priority: emitter.EventPriority.LOW // 表示事件优于IDLE优先级投递,事件的默认优先级是LOW。
        }, {
          data: {
            content: data.payload,
          }
        })
      }
    })
  }

4.发送消息

public async pushMessage(msg: string, pic: string = this.topic, qo: MqttQos = this.qos as MqttQos) {
    if (!this.mqttClient) {
      return
    }
    await this.mqttClient.publish({
      topic: pic,
      qos: qo,
      payload: msg
    }).then((data: MqttResponse) => {
      console.info("MQTT消息发送成功:", JSON.stringify(data))
    }).catch((err: Error) => {
      console.error("MQTT消息发送失败:", JSON.stringify(err))
    })
  }

理解了这四步的代码即可搭建一个简单的客户端了,但实际项目中,仅仅这样写是不够的,我们需要把以上代码封装成一个类,便于页面的调用及复用,借助与ts的特性,我们还可以把这个类优化一下,封装成一个单例,这样的好处在于,应用有且仅有一个实例,在应用入口一次声明,在应用出口一次销毁即可,防止开发者多次声明的情况发生,好了,让我们结合上面的代码封装一个 MQTTClient

import {
  MqttAsync,
  MqttClient,
  MqttMessage,
  MqttQos,
  MqttResponse,
  MqttSubscribeOptions,
} from '@ohos/mqtt';
import emitter from '@ohos.events.emitter';


// 事件id
const EVENTID = 1001


interface MQTTOptionsType {
  url?: string
  clientId?: string
  userName?: string
  password?: string
  topic?: string
  qos?: MqttQos | undefined
}

class MQTTClient {
  private static instance: MQTTClient
  private mqttClient: MqttClient | null = null
  private url: string =
    '' // 以null结尾的字符串,指定客户端将连接到的服务器。它采取的形式protocol://host:port.protocol必须是tcp、ssl、ws或wss。对于主机,可以指定IP地址或主机名。例如,tcp://localhost:1883
  private clientId: string = '' // 客户端连接到服务器时传递给服务器的客户端标识符。它是一个以空结尾的UTF-8编码字符串
  private userName: string = '' // 客户端用户名
  private password: string = '' // 客户端密码
  private topic: string = '' // 主题名称
  private qos: MqttQos | undefined = undefined // 消息的服务质量设置 0 最多一次 1 至少一次 2 近一次

  constructor(mqttOptions: MQTTOptionsType) {
    mqttOptions.url && (this.url = mqttOptions.url)
    mqttOptions.clientId && (this.clientId = mqttOptions.clientId)
    mqttOptions.userName && (this.userName = mqttOptions.userName)
    mqttOptions.password && (this.password = mqttOptions.password)
    mqttOptions.topic && (this.topic = mqttOptions.topic)
    mqttOptions.qos && (this.qos = mqttOptions.qos)

    this.init()
  }

  // 静态方法获取唯一实例
  public static getInstance(mqttOptions: MQTTOptionsType): MQTTClient {
    if (!MQTTClient.instance) {
      MQTTClient.instance = new MQTTClient(mqttOptions)
    }
    return MQTTClient.instance
  }

  // 初始化方法
  async init() {
    this.createMqttClient() // 创建mqtt实例
    await this.connectMqtt() // 连接服务器
    await this.subscribe() // 订阅主题
    this.messageArrived() // 监听订阅主题
  }

  // 创建mqtt实例
  public createMqttClient() {
    this.mqttClient = MqttAsync.createMqtt({
      url: this.url,
      clientId: this.clientId,
      persistenceType: 1 // 客户端使用的持久性类型,0=默认值:使用默认(基于文件系统)持久性机制。1=在内存持久性中使用。2=使用特定于应用程序的持久性实现
    })
  }

  // 连接服务器
  public async connectMqtt() {
    if (!this.mqttClient) {
      return
    }
    await this.mqttClient.connect({
      userName: this.userName,
      password: this.password,
      connectTimeout: 30, // 设置连接超时时间,默认
      automaticReconnect: true, // 是否在连接丢失的情况下自动重新连接
      MQTTVersion: 0, // 设置要在连接上使用的MQTT版本, 0=默认值:从3.1.1开始,如果失败,则返回到3.1,3=仅尝试版本3.1,4=仅尝试3.1.1版本
      // sslOptions:{
      //   enableServerCertAuth: true, // 是否开启服务端证书认证,默认为false
      //    trustStore: fileDir + "/ca.crt" // CA证书的沙箱路径
      // }
    }).then((res: MqttResponse) => {
      console.info('MQTT服务器连接连接成功:' + JSON.stringify(res.message))
    }).catch((err: Error) => {
      console.error('MQTT服务器连接连接失败:' + JSON.stringify(err))
    })
  }

  // 订阅主题
  public async subscribe() {
    if (!this.mqttClient) {
      return
    }
    let subscribeOption: MqttSubscribeOptions = {
      topic: this.topic,
      qos: this.qos as MqttQos
    }
    await this.mqttClient.subscribe(subscribeOption).then((res: MqttResponse) => {
      console.info('MQTT订阅主题成功:', JSON.stringify(res.message))
    }).catch((err: Error) => {
      console.error('MQTT订阅主题失败:', JSON.stringify(err))
    })
    // 监听订阅主题
    this.messageArrived()
  }

  // 监听订阅主题
  public messageArrived() {
    if (!this.mqttClient) {
      return
    }
    // 监听主题发送消息
    this.mqttClient.messageArrived((err: Error, data: MqttMessage) => {
      if (err) {
        console.error('MQTT接收消息失败:', JSON.stringify(err))
      } else {
        console.info('MQTT接收消息成功:', JSON.stringify(data))
        // 发送消息至线程
        emitter.emit({
          eventId: EVENTID,
          priority: emitter.EventPriority.LOW // 表示事件优于IDLE优先级投递,事件的默认优先级是LOW。
        }, {
          data: {
            content: data.payload,
          }
        })
      }
    })
  }

  /**
   * 发送消息
   * @param pic 订阅的主题
   * @param msg 消息内容
   * @param qo qos
   */
  public async pushMessage(msg: string, pic: string = this.topic, qo: MqttQos = this.qos as MqttQos) {
    if (!this.mqttClient) {
      return
    }
    await this.mqttClient.publish({
      topic: pic,
      qos: qo,
      payload: msg
    }).then((data: MqttResponse) => {
      console.info("MQTT消息发送成功:", JSON.stringify(data))
    }).catch((err: Error) => {
      console.error("MQTT消息发送失败:", JSON.stringify(err))
    })
  }

  // 销毁客户端实例
  public async destroy() {
    if (!this.mqttClient) {
      return
    }
    await this.mqttClient.destroy().then((data: boolean) => {
      if (data) {
        console.info('MQTT实例销毁成功')
        emitter.off(EVENTID)
      } else {
        console.error('MQTT实例销毁失败')
      }
    })

  }
}

export {
  MQTTClient, EVENTID, MQTTOptionsType
}

在上面的代码中,我们还用到了一个ArkTS中的内置模块 @ohos.events.emitter 线程通信,它可以在应用的任何地方分发事件,在页面中监听事件并接收数据,稍后的页面中我们会用到

三、使用MQTTClient方法

首先我们需要构建一个页面视图,视图中可以显示发送的消息,接收到的消息等,因封装的方法中声明实例即自动连接服务器,所以不需要连接按钮

@Entry
@Component
struct Index {
  @State receiveMsg: string = ''
  @State sendMsg: string = ''

  build() {
    Column({ space: 24 }) {

      Text('MQTT客户端')
        .fontSize(32)
        .fontWeight(500)
        .margin({ bottom: 20 })


      Row() {
        Text('接收到的消息:')
        TextInput({ text: $$this.receiveMsg, placeholder: '接收到的消息' })
          .layoutWeight(1)
      }

      Row({ space: 16 }) {
        Button('发送消息')
        TextInput({ text: $$this.sendMsg, placeholder: '请填写需要发送的消息' })
          .layoutWeight(1)

      }
      .width('100%')

      Button('销毁客户端')
        .width('100%')

    }
    .padding(20)
    .width('100%')
  }
}

在这里插入图片描述
然后我们引入并声明刚刚封装的 MQTTClient 类,并配置连接信息,与MQTTX工具中配置的一样即可,需要注意的是url,必须是 mqtt协议+ip + 端口的格式,如:mqtt://0.0.0.0:1883

import { MQTTClient, MQTTOptionsType, EVENTID } from '../utils/MQTTClientInstance'
import { emitter } from '@kit.BasicServicesKit'

const MQTTOption: MQTTOptionsType = {
  url: 'mqtt:/${ip}:1883',  // 注意:填写自己部署的连接地址
  clientId: 'mqtt123456@', // 客户端连接到服务器时传递给服务器的客户端标识符。它是一个以空结尾的UTF-8编码字符串
  userName: 'root', // 客户端用户名
  password: 'admin', // 客户端密码
  topic: 'topic1', // 主题名称
  qos: 1 // 消息的服务质量设置 0 最多一次 1 至少一次 2 近一次
}

const MQTTInstance: MQTTClient = MQTTClient.getInstance(MQTTOption)

引入之后即可给视图绑定事件,并在声明周期中监听服务端发送的消息,完整代码如下:

import { MQTTClient, MQTTOptionsType, EVENTID } from '../utils/MQTTClientInstance'
import { emitter } from '@kit.BasicServicesKit'


const MQTTOption: MQTTOptionsType = {
  url: 'mqtt://k178b5e8.ala.dedicated.aliyun.emqxcloud.cn:1883',
  clientId: 'mqtt123456@',
  userName: 'root',
  password: 'admin',
  topic: 'topic1',
  qos: 1
}

const MQTTInstance: MQTTClient = MQTTClient.getInstance(MQTTOption)


@Entry
@Component
struct Index {
  @State receiveMsg: string = ''
  @State sendMsg: string = ''

  onPageShow(): void {
    emitter.on({
      eventId: EVENTID
    }, (data) => {
      this.receiveMsg = JSON.stringify(data)
    });
  }

  build() {
    Column({ space: 24 }) {

      Text('MQTT客户端')
        .fontSize(32)
        .fontWeight(500)
        .margin({ bottom: 20 })


      Row() {
        Text('接收到的消息:')
        TextInput({ text: $$this.receiveMsg, placeholder: '接收到的消息' })
          .layoutWeight(1)
      }

      Row({ space: 16 }) {
        Button('发送消息')
          .onClick(() => {
            if (this.sendMsg) {
              MQTTInstance.pushMessage(this.sendMsg)
              this.sendMsg = ''
            }
          })
        TextInput({ text: $$this.sendMsg, placeholder: '请填写需要发送的消息' })
          .layoutWeight(1)

      }
      .width('100%')

      Button('销毁客户端')
        .width('100%')
        .onClick(() => {
          MQTTInstance.destroy()
        })

    }
    .padding(20)
    .width('100%')
  }
}

在模拟器中运行视图代码,然后打开日志就可以看到自己的打印信息,如连接成功,订阅主题成功等
在这里插入图片描述

在输入框中填写需要发送给服务端的信息,点击发送,即可在接收信息框中看到服务端发送到客户端的信息

在这里插入图片描述
点击销毁客户端可销毁当前实例,这个步骤可以推出应用时触发,至此,MQTT客户端搭建完毕。
在这里插入图片描述

总结

第一次写文章,全文总计一万多字,才知道原创码字原来这么累,希望能得到各位大佬的一个点赞。
在这里插入图片描述

### ArkTS与MQTT的集成方案 #### 1. MQTT 协议简介 MQTT 是一种轻量级的消息传输协议,专为低带宽、不可靠网络环境设计。它采用发布/订阅模式,允许设备通过主题(Topic)发送和接收消息[^2]。 #### 2. ArkTS 的特性 ArkTS 是华为 HarmonyOS 提供的一种现代化编程语言,旨在简化跨平台应用开发过程。其语法类似于 TypeScript,支持声明式 UI 开发,并提供了丰富的 API 来处理各种应用场景[^3]。 #### 3. 在 ArkTS 中实现 MQTT 客户端的功能 为了在 ArkTS 应用程序中使用 MQTT 协议,可以按照以下方式构建客户端: ##### (a) 创建 MQTT 连接 首先需要初始化一个 MQTT 客户端实例,并设置连接参数,例如代理地址、端口号以及客户端 ID 等。 ```typescript import mqtt from '@ohos/mqtt'; const client = mqtt.createClient({ uri: 'mqtt://broker.hivemq.com', // 替换为目标 MQTT Broker 地址 clientId: 'arkts_client_' + Math.random().toString(16).substring(2, 8), }); ``` ##### (b) 处理连接事件 定义回调函数来监听连接状态的变化,比如成功建立连接或者断开连接时触发的操作。 ```typescript client.on('connect', () => { console.log('Connected to MQTT broker'); }); client.on('close', () => { console.warn('Disconnected from MQTT broker'); }); client.on('error', (err) => { console.error(`Error occurred: ${JSON.stringify(err)}`); }); ``` ##### (c) 订阅主题 调用 `subscribe` 方法指定要关注的一个或多个主题,并提供相应的 QoS 级别。 ```typescript client.subscribe('test/topic', { qos: 0 }, (err, granted) => { if (!err && granted.length > 0) { console.info(`Subscribed successfully with QoS=${granted[0].qos}`); } else { console.error('Failed to subscribe:', err); } }); ``` ##### (d) 发布消息 当有新数据需要共享给其他订阅者时,可以通过 `publish` 函数广播出去。 ```typescript function sendMessage(topic: string, payload: any): void { const message = JSON.stringify(payload); client.publish(topic, message, { qos: 1 }, (err) => { if (err) { console.error('Publish failed:', err); } else { console.debug(`Message sent on topic "${topic}":`, payload); } }); } ``` ##### (e) 断开连接 完成通信之后记得释放资源,关闭会话。 ```typescript client.end(true, undefined, () => { console.log('Connection closed gracefully.'); }); ``` 以上代码片段展示了如何利用 ArkTS 和内置的 ohos.mqtt 模块快速搭建起基本的 MQTT 功能框架[^1]。 --- #### 4. 集成注意事项 - **依赖管理**:确认项目已引入必要的 MQTT SDK 或库文件。 - **安全性考量**:对于生产环境中使用的解决方案,建议启用 TLS 加密机制保护敏感信息交换的安全性。 - **性能优化**:合理配置 Keep Alive 时间间隔以及其他高级选项提升整体效率。 ---
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值