概述:
在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; }
}
标签:订阅,主题,MQTT,Unity,消息,服务器,客户端
From: https://blog.csdn.net/R3333355726856/article/details/141137369