在Unity中开发MQTT客户端

概述:

        在Unity环境中使用MQTTnet库(一个流行的.NET库,用于实现MQTT客户端和服务器。它支持.NET Core和.NET Framework,并提供了灵活的API以及高性能的实现)搭建自己的MQTT客户端.我使用的版本:Version=4.3.6.1152

        但是在开发客户端之前,你需要有一个MQTT服务器(https://www.emqx.com/zh/cloud,你可以免费试用该云服务器EMQX Cloud,当然也可以自己开发一个服务器自行部署,本篇主要讲述客户端开发,所以不讨论此问题),并且我建议使用其他MQTT客户端工具(如MQTTX客户端,https://mqttx.app/zh)来提前进行调试,确保你足够了解当前的环境(如ip,端口,协议)

资源准备

        首先在nuget中下载(点击页面右侧download package)该nuget包(下载下来后将后缀改为7z或者zip等你能解压的格式,因为nuget本质是一个压缩包),解压后在lib目录下找到你需要的版本(推荐.netstandard2.0/2.1),将DLL放入Unity的Plugins目录下.

世界观

        但在开发之前,我们需要知道一些基本的概念

什么是MQTT

        MQTT(Message Queuing Telemetry Transport)是一种轻量级的基于发布-订阅模式的消息传递协议(处于应用层),它特别设计用于网络带宽和设备资源都非常受限的情况。MQTT定义了一种发布/订阅的消息模式,它支持一对多的消息分发,使得网络通信更加高效。在物联网应用中广受欢迎,能够实现传感器、执行器和其它设备之间的高效通信。        

        基于TCP:MQTT通常运行在TCP之上,利用TCP提供的可靠性和有序性来确保消息的正确传递。TCP作为传输层协议,为MQTT提供了稳定的连接基础,使MQTT能够专注于消息的格式和流程而不用处理底层的数据传输问题。

        会话和状态管理:MQTT通过TCP连接管理客户端和服务器之间的会话,保持长时间的连接状态,这使得设备可以随时发送或接收消息。

        因此,选择TCP/IP模型作为MQTT的基础是因为TCP提供了一种可靠的方式来确保数据按顺序且不重复地传达,这是网络通信中非常重要的特性,尤其是在要求高可靠性的应用场景中。同时,使用TCP/IP模型也意味着MQTT可以利用现有的广泛部署的网络基础设施,从而使得实现和部署变得更加简便和成本效率高。

基本概念

下面是MQTT的基本概念

一 客户端(Client)

        MQTT网络中的任何设备或应用程序,可以发布消息到服务器,也可以从服务器订阅消息。往往我们的Unity程序作为一个客户端,发消息给服务器从而控制其他客户端,笔者面对的环境是我开发的Unity客户端通过给服务器发送消息,服务器帮我转发给一个蓝牙网关(那么这个蓝牙网关就是另一个客户端,且这个蓝牙网关订阅了特定主题),蓝牙网关通过蓝牙管理很多蓝牙设备,实现了通过Unity操纵这些蓝牙设备.

二 服务器(Broker)

        MQTT服务器(通常称为MQTT代理(broker)),在MQTT通信中扮演着中心角色,处理所有客户端的消息转发。客户端向服务器发布消息,服务器则负责将这些消息转发给订阅了相应主题的其他客户端。在转发消息时,MQTT代理通常不会修改消息的内容。它的任务是根据订阅有效地分发消息。

        MQTT代理不仅仅是简单地转发消息,它依靠主题来决定如何将消息从发布者路由到正确的订阅者。这个过程是高度依赖主题的,代理需要维护主题订阅的记录,并根据这些记录进行消息的分发。

主题 (Topic)

        消息的标签或分类,客户端可以发布消息到特定主题,也可以订阅特定主题来接收消息。主题通常具有层次结构,主题通过/来区分层级,类似URL 路径,例如 /{MAC}/connect_packet/connect1_publish。

主题的处理,路由消息:

        MQTT代理的核心功能之一是根据主题来路由消息。当一个客户端发布消息到特定主题时,代理会检查所有订阅了该主题的客户端,并将消息转发给这些客户端。

        代理使用主题过滤器来确定哪些客户端应接收某个特定消息。这涉及到匹配发布的主题与客户端订阅的主题模式。

主题的层次结构和通配符:

MQTT主题可以具有层次结构,如 /home/livingroom/temperature。

        客户端可以使用通配符来订阅多个主题。例如,使用 + 作为单层通配符(如 /home/+/temperature),或使用 # 作为多层通配符(如 /home/#)来订阅所有在 /home 下的主题。注意:通配符主题只能用于订阅,不能用于发布。

四 发布 (Publish)/订阅 (Subscribe)模式

        发布-订阅模式与客户端-服务器模式的不同之处在于,它将发送消息的客户端(发布者)和接收消息的客户端(订阅者)进行了解耦。发布者和订阅者之间无需建立直接连接,而是通过 MQTT Broker 来负责消息的路由和分发。

五 质量服务等级 (QoS)

MQTT 提供了三种服务质量(QoS),在不同网络环境下保证消息的可靠性。

QoS 0:消息最多传送一次。如果当前客户端不可用,它将丢失这条消息。

QoS 1:消息至少传送一次。

QoS 2:消息只传送一次。

        代理负责根据发布者和订阅者的QoS要求来确保消息传递。例如,如果一个客户端以QoS 1发布消息,代理需要确保所有QoS 1或更高级别的订阅者至少接收到这条消息一次。

例子

Unity客户端作为MQTT客户端之一,向MQTT服务器发布(publish)消息。

消息发布到特定的主题,例如 /{MAC}/connect_packet/connect1_subscribe。

这个消息是一个控制命令,如“开启”、“关闭”或“调整参数”。

MQTT服务器接收来自Unity客户端的消息,并根据订阅这个主题的其他客户端(如蓝牙网关)转发这些消息。

蓝牙网关订阅了这个主题,因此它会接收到服务器转发的控制命令。

收到控制指令后,蓝牙网关将这些指令下发到具体的蓝牙设备。

        通过这样的过程,Unity客户端可以远程控制连接到蓝牙网关的设备,而整个流程是通过MQTT的发布/订阅机制实现的。这种机制使得添加新的设备或修改控制逻辑变得相对简单,只需调整相应的主题和消息内容即可。

方法论

下面我们上代码

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using MQTTnet;
using MQTTnet.Client;
using System.Threading;
using System;
using System.Threading.Tasks;
using MQTTnet.Protocol;
using System.Text;
using Newtonsoft.Json;
using System.IO;
public class GetawayMqttClient : MonoBehaviour
{ 
        //该库大量使用工厂模式,譬如先声明一个MQTT工厂,使用该工厂
        //生产MQTT客户端IMqttClient 
        MqttFactory mqttFactory = new MqttFactory();
        IMqttClient mqttClient;
        MQTTInitData mqttInitData;
       private Queue<string> reciveDatas = new Queue<string>();
      
        async void Start()
        { 
            //这里先创建一个客户端对象,但是没进行配置                   
            mqttClient = mqttFactory.CreateMqttClient();
            await StartConnect();
        }

        private async Task StartConnect()
        {
            try
            {
                //读取配置
                var path = Path.Combine(Application.streamingAssetsPath,                 
                "StateTrendConfig/mqttConfig.json");
                /*
                   这是我读取的配置
                  {
                  "ip": "172.16.1.249",
                  "port": 54232,
                  "userName": "server1234",
                  "passwd": "123456",
                  "macAddress": "ECD99AC58216"
                  }
                */
                if (File.Exists(path))
                {
                    var jsonStr = File.ReadAllText(path);
                    var config = JsonConvert.DeserializeObject<MQTTInitData>(jsonStr);
                    mqttInitData = config;

                    //构建客户端配置对象
                    MqttClientOptions mqttClientOptions = new MqttClientOptionsBuilder()
                    .WithTcpServer(config.Ip, config.Port) // 指定服务器地址和端口
                    .WithCredentials(config.UserName, config.Passwd) // 指定用户名和密码
                    .WithClientId(Guid.NewGuid().ToString("N"))//配置客户端的ID
                    .WithCleanSession() // 使用清除会话标志
                    .WithProtocolVersion(MQTTnet.Formatter.MqttProtocolVersion.V311)
                    //配置MQTT版本
                    .Build();
                    //MQTT客户端异步连接,上面构建的配置对象给到了客户端对象
                    var response = await mqttClient.ConnectAsync(mqttClientOptions,             
                    CancellationToken.None);
                    UnityEngine.Debug.Log(("The MQTT client is connected.") + 
                    response.ResultCode);
                    //当await连接完成后,构建主题对象
                    string topic = "/" + config.MacAddress + 
                    "/connect_packet/connect1_publish";//一般参照你的连接协议
                    var topicFilter = new MqttTopicFilterBuilder()//主题构建对象构建一个主    
                    //题对象
                    .WithTopic(topic)                 
       .WithQualityOfServiceLevel(MQTTnet.Protocol.MqttQualityOfServiceLevel.ExactlyOnce)
                    //设置质量服务等级
                    .Build();//最终构建
                    //订阅topic
                    await mqttClient.SubscribeAsync(topicFilter);//订阅该主题,接收通过该主 
                   // 题转发的消息

                    //事件订阅mqtt消息
                    mqttClient.ApplicationMessageReceivedAsync += (e =>
                    {
                        string topic1 = e.ApplicationMessage.Topic;//根据该属性分辨是哪个 
                        //主题传递的消息,你可以根据不同的主题传来的消息做出不同的反应
                        //将消息转为字符串
                        string payload = System.Text.Encoding.UTF8.GetString(e.ApplicationMessage.PayloadSegment.Array);
                        //下面一般是你自己的业务逻辑,如何处理传递过来的消息
                        //reciveDatas.Enqueue(payload);通常会使用队列先存起来,后续会在Update 
                        //中处理
                        // UnityEngine.Debug.Log($"Topic is {topic}, Payload is {payload}");
                        return Task.CompletedTask;
                    });
                }

            }
            catch (Exception e)
            {
                Debug.LogError(e.Message);
            }
        }

//发送消息,你可以使用按钮绑定该方法,发送消息,或者其他方法
 async void SendMes(string cmd)
        {
            var result = mqttClient.PublishAsync(new MqttApplicationMessage
            {
                Topic = "/" + mqttInitData.MacAddress + "/connect_packet/connect1_subscribe",//发送到那个主题
                QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce,
                Retain = false, //指示消息是否要被服务器保留。保留的消息会存储在服务器上,当
              //新客户端订阅该主题时会立即收到该消息。
                PayloadSegment = new ArraySegment<byte>(Encoding.UTF8.GetBytes(cmd)),//承 
                //载消息
            }, CancellationToken.None);
            await result;
            Debug.Log("发布命令结果为" + result.IsCompleted);
        }


     //断开连接释放资源
     void OnDestroy()
        {
            var mqttClientDisconnectOptionsBuilder =         
            mqttFactory.CreateClientDisconnectOptionsBuilder();
            var mqttClientDisconnectOptions = mqttClientDisconnectOptionsBuilder.Build();
            mqttClient.DisconnectAsync(mqttClientDisconnectOptions, 
            CancellationToken.None);
        }
}

 public class MQTTInitData//使用了Newtonjson库
    {
        [JsonProperty("ip")]
        public string Ip { get; set; }

        [JsonProperty("port")]
        public int Port { get; set; }

        [JsonProperty("userName")]
        public string UserName { get; set; }

        [JsonProperty("passwd")]
        public string Passwd { get; set; }
        [JsonProperty("macAddress")]
        public string MacAddress { get; set; }
    }

  • 25
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值