.Net快速掌握完整的MQTT客户端对接

1 引用NugetMQTTnet.Extensions.ManagedClient

image 

2 整体客户端大致结构

image

3 定义日志基类 ModuleLogBase 以及实现基类的MqttClientLog

using System;

namespace LinseerCopilot.Service.MqttConnectService
{
    /// <summary>
    /// 各个模块的日志基类
    /// </summary>
    public abstract class ModuleLogBase
    {
        private readonly string _preFix;
        private readonly bool _consoleEnable;

        protected ModuleLogBase(string preFix, bool consoleEnable = false)
        {
            _preFix = preFix;
            _consoleEnable = consoleEnable;
        }
        public void Error(string exMsg)
        {
            System.Diagnostics.Debug.WriteLine($"[{_preFix}] [{DateTime.Now:yyyyMMddHH:mm:ss:fff}]{exMsg}");
            CommService.Logger?.Error($"[{_preFix}] {exMsg}");
        }
        public void Error(Exception ex)
        {
            System.Diagnostics.Debug.WriteLine($"[{_preFix}] [{DateTime.Now:yyyyMMddHH:mm:ss:fff}]{ex}");
            CommService.Logger?.Error($"[{_preFix}] ", ex);
        }

        public void Info(string msg)
        {
            if (_consoleEnable)
            {
                Console.WriteLine($"[{_preFix}] [{DateTime.Now:yyyyMMddHH:mm:ss:fff}]{msg}");
            }
            System.Diagnostics.Debug.WriteLine($"[{_preFix}] [{DateTime.Now:yyyyMMddHH:mm:ss:fff}]{msg}");
            CommService.Logger?.Info($"[{_preFix}] {msg}");
        }

        public void Debug(string msg)
        {
            CommService.Logger?.Debug($"[{_preFix}] {msg}");
        }
    }
}
namespace LinseerCopilot.Service.MqttConnectService
{
    internal class MqttClientLog : ModuleLogBase
    {
        public MqttClientLog() : base("MQTT")
        {
        }
        internal static ModuleLogBase Log = new MqttClientLog();
    }
}

4 实现IMqttNetLogger日志接口 MqttLogger

using MQTTnet.Diagnostics;
using System;

namespace LinseerCopilot.Service.MqttConnectService
{
    /// <summary>
    /// 日志
    /// </summary>
    internal class MqttLogger : IMqttNetLogger
    {
        /// <summary>
        /// 现在最大异常次数,避免网络问题一直连不上日志打满
        /// </summary>
        private int ErrorCount;
        private readonly ModuleLogBase _logger;
        public MqttLogger(ModuleLogBase logger)
        {
            _logger = logger;
        }
        /// <summary>
        /// 记录日志
        /// </summary>
        /// <param name="logLevel"></param>
        /// <param name="source"></param>
        /// <param name="message"></param>
        /// <param name="parameters"></param>
        /// <param name="exception"></param>
        public void Publish(MqttNetLogLevel logLevel, string source, string message, object[] parameters, Exception exception)
        {
            if (parameters?.Length > 0)
            {
                message = string.Format(message, parameters);
            }
            switch (logLevel)
            {
                case MqttNetLogLevel.Verbose:
                    _logger?.Debug(message);
                    return;

                case MqttNetLogLevel.Info:
                    ErrorCount = 0;
                    _logger?.Info(message);
                    return;

                case MqttNetLogLevel.Warning:
                case MqttNetLogLevel.Error:
                    ErrorCount = 0;
                    _logger?.Info(message);
                    return;
            }

            if (exception != null && ErrorCount < 10)
            {
                ErrorCount++;
                _logger?.Error(exception);
            }
        }

        /// <summary>
        /// 发布日志
        /// </summary>
        public bool IsEnabled => true;
    }
}

5 实现通信设备客户端DeviceMqttClient

using MQTTnet;
using MQTTnet.Client;
using MQTTnet.Extensions.ManagedClient;
using MQTTnet.Packets;
using MQTTnet.Protocol;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace LinseerCopilot.Service.MqttConnectService
{
    /// <summary>
    /// mqtt 通信客户端
    /// </summary>
    internal class DeviceMqttClient
    {

        public DeviceMqttClient(ModuleLogBase logger)
        {
            _client = new MqttFactory().CreateManagedMqttClient(new MqttLogger(logger));
        }

        #region Public Methods

        /// <summary>
        /// 建立连接
        /// </summary>
        /// <returns></returns>
        public async Task StartAsync(string ip, int port)
        {
            var tls = new MqttClientOptionsBuilderTlsParameters()
            {
                AllowUntrustedCertificates = false,
                UseTls = false,
                CertificateValidationHandler = (args) => true,
                IgnoreCertificateChainErrors = false,
                IgnoreCertificateRevocationErrors = false
            };
            var options = new ManagedMqttClientOptionsBuilder()
                .WithAutoReconnectDelay(TimeSpan.FromSeconds(60)) //断线重连iot平台建议。原先15s有点频繁,目前设置为60s
                .WithClientOptions(new MqttClientOptionsBuilder()
                .WithTcpServer(ip, port)
                .WithTls(tls)
                .WithCleanSession()
                .Build())
                .Build();

            await _client.StartAsync(options);
        }

        /// <summary>
        /// 建立连接
        /// </summary>
        /// <param name="server"> mqtt 实例地址</param>
        /// <param name="clientId">用户名</param>
        /// <param name="username">接入client_id</param>
        /// <param name="password">密码</param>
        /// <param name="storagePath"></param>
        /// <returns></returns>
        public async Task StartAsync(string server, string clientId, string username, string password, string storagePath)
        {
            var tls = new MqttClientOptionsBuilderTlsParameters()
            {
                AllowUntrustedCertificates = false,
                UseTls = true,
                CertificateValidationHandler = (args) => true,
                IgnoreCertificateChainErrors = false,
                IgnoreCertificateRevocationErrors = false
            };
            if (server.StartsWith("mqtt:", StringComparison.CurrentCultureIgnoreCase))
                tls.UseTls = false;

            var storage = new ClientRetainedMessageHandler(storagePath);
            var options = new ManagedMqttClientOptionsBuilder()
                .WithAutoReconnectDelay(TimeSpan.FromSeconds(30)) //断线重连iot平台建议。原先15s有点频繁,目前设置为30s
                .WithStorage(storage)
                .WithClientOptions(new MqttClientOptionsBuilder()
                    .WithConnectionUri(server)
                    .WithClientId(clientId)
                    .WithCredentials(username, password)
                    .WithTls(tls)
                    .WithCleanSession()
                    .Build())
                .Build();

            await _client.StartAsync(options);
        }

        /// <summary>
        /// 断开连接
        /// </summary>
        /// <returns></returns>
        public async Task StopAsync()
        {
            await _client.StopAsync();
        }

        /// <summary>
        /// 发布数据
        /// </summary>
        /// <param name="topic">主题</param>
        /// <param name="payload">内容体</param>
        /// <param name="exactlyOnce">确保只有一次</param>
        /// <returns></returns>
        public async Task PublicAsync(string topic, string payload, bool exactlyOnce)
        {
            if (!_client.IsConnected)
            {
                MqttClientLog.Log.Info("未连接,无法发送");
                return;
            }

            var message = new MqttApplicationMessageBuilder()
                .WithTopic(topic)
                .WithPayload(payload)
                .WithQualityOfServiceLevel(exactlyOnce ? MqttQualityOfServiceLevel.ExactlyOnce : MqttQualityOfServiceLevel.AtMostOnce)
                .Build();

            if (_client.IsConnected)
                await _client.EnqueueAsync(message);
            else
                MqttClientLog.Log.Info($"MQTT未连接服务,消息无法发送:topic {topic} -- {payload}  ");
        }

        /// <summary>
        /// 订阅消息
        /// </summary>
        /// <param name="topic">主题</param>
        /// <returns></returns>
        public async Task SubscribeAsync(string topic)
        {
            var topics = new List<MqttTopicFilter>() {
                new MqttTopicFilterBuilder().WithTopic(topic).Build()
            };
            await _client.SubscribeAsync(topics);
        }

        /// <summary>
        /// 取消订阅消息
        /// </summary>
        /// <param name="topic">主题</param>
        /// <returns></returns>
        public async Task UnsubscribeAsync(string topic)
        {
            var topics = new List<string>() { topic };
            await _client.UnsubscribeAsync(topics);
        }

        #endregion

        #region Fileds

        private readonly IManagedMqttClient _client;

        public IManagedMqttClient Client => _client;
        #endregion
    }
}

6 推送发布消息持久化,实现IManagedMqttClientStorage接口,用于重连推动发布失败的消息

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using MQTTnet.Extensions.ManagedClient;
using MQTTnet.Protocol;

namespace LinseerCopilot.Service.MqttConnectService
{
    /// <summary>
    /// 消息发送失败存储
    /// </summary>
    internal class ClientRetainedMessageHandler : IManagedMqttClientStorage
    {
        public ClientRetainedMessageHandler(string storagePath,bool resendStorageMessage = false)
        {
            _resendStorageMessage = resendStorageMessage;
            _retainedMessagesSavePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), @"RetainedMessages.json");
            _retainedMessagesSavePath = storagePath ?? _retainedMessagesSavePath;
        }

        /// <summary>
        /// 保存发送失败的消息到本地存储(仅限QoS≥1的消息)
        /// 当MQTT客户端断线或消息未送达时自动触发
        /// </summary>
        public Task SaveQueuedMessagesAsync(IList<ManagedMqttApplicationMessage> messages)
        {
            if (!_resendStorageMessage) return Task.CompletedTask;

            if (!File.Exists(_retainedMessagesSavePath)) return Task.CompletedTask;

            //过滤非必须消息,比如 MqttQualityOfServiceLevel 为0
            //同时做好比对,减少文件IO
            if (!messages.Any()) return Task.CompletedTask;
            var msg = messages.Where(i => i.ApplicationMessage.QualityOfServiceLevel != MqttQualityOfServiceLevel.AtMostOnce).ToList();
            var currentMsg = msg.ToJson();
            File.WriteAllText(_retainedMessagesSavePath, currentMsg);
            return Task.FromResult(0);
        }

        /// <summary>
        /// 加载推送失败存储的消息
        /// 开始启动Mqtt时调用
        /// </summary>
        /// <returns></returns>
        public async Task<IList<ManagedMqttApplicationMessage>> LoadQueuedMessagesAsync()
        {
            IList<ManagedMqttApplicationMessage> retainedMessages = new List<ManagedMqttApplicationMessage>();
            if (!_resendStorageMessage)
            {
                DeleteRetainedMessages(_retainedMessagesSavePath);
                return await Task.FromResult(retainedMessages);
            }

            try
            {
                retainedMessages = await LoadQueuedMessagesAsync(_retainedMessagesSavePath);
            }
            catch (FileLoadException ex)
            {
                DeleteRetainedMessages(_retainedMessagesSavePath);
                MqttClientLog.Log.Error(ex);
            }
            catch (Exception e)
            {
                MqttClientLog.Log.Error(e);
            }

            retainedMessages ??= new List<ManagedMqttApplicationMessage>();
            return await Task.FromResult(retainedMessages);
        }

        /// <summary>
        /// 加载推送消息的文件
        /// </summary>
        /// <param name="retainedMessagesSavePath"></param>
        /// <returns></returns>
        private async Task<IList<ManagedMqttApplicationMessage>> LoadQueuedMessagesAsync(string retainedMessagesSavePath)
        {
            IList<ManagedMqttApplicationMessage> retainedMessages = null;
            try
            {
                if (File.Exists(_retainedMessagesSavePath))
                {
                    var json = File.ReadAllText(_retainedMessagesSavePath);
                    if (!string.IsNullOrEmpty(json))
                    {
                        retainedMessages = json.ToObject<List<ManagedMqttApplicationMessage>>();
                    }
                }
            }
            catch
            {
                throw new FileLoadException("read RetainedMessages.json error");
            }
            return await Task.FromResult(retainedMessages); 
        }

        /// <summary>
        /// 删除存储的推送内容
        /// </summary>
        /// <param name="retainedMessagesSavePath">存储路径</param>
        private void DeleteRetainedMessages(string retainedMessagesSavePath)
        {
            try
            {
                if (!File.Exists(retainedMessagesSavePath)) return;

                File.SetAttributes(retainedMessagesSavePath, FileAttributes.Normal);  //去除文件的只读属性
                File.Delete(retainedMessagesSavePath);
            }
            catch(Exception ex)
            {
                MqttClientLog.Log.Error(ex);
            }
        }

        /// <summary>重复消息保存的文件路径</summary>
        private readonly string _retainedMessagesSavePath;
        /// <summary>重发存储的消失</summary>
        private readonly  bool _resendStorageMessage = false;
    }
}

7 连接、重连、订阅消息、发布消息等MqttConnectService

using MQTTnet.Exceptions;
using System;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Timer = System.Timers.Timer;

namespace LinseerCopilot.Service.MqttConnectService
{
    public class MqttConnectService
    {
        private readonly DeviceMqttClient _deviceMqttClient;
        private readonly SemaphoreSlim _mqttStarterLock = new SemaphoreSlim(1);
        public MqttConnectService()
        {
            _deviceMqttClient = new DeviceMqttClient(MqttClientLog.Log);
        }

        /// <summary>
        /// 初始化
        /// </summary>
        /// <returns></returns>
        public async Task InitAsync()
        {
            BindMqttClientEvent();
            await SubscribeTopic();
            await StartAsync();
        }

        private async Task SubscribeTopic()
        {
            await _deviceMqttClient.SubscribeAsync(@"linseer/topic");
        }

        /// <summary>
        /// 按照流程初始化
        /// </summary>
        private async Task StartAsync()
        {
            if (await InternalStartAsync()) return;
            var timer = new Timer() { Interval = 60 * 1000 };
            timer.Elapsed += async (sender, e) =>
            {
                var result = await InternalStartAsync();
                if (result)
                {
                    timer.Stop();
                }
            };
            timer.Start();
        }

        /// <summary>
        /// 具体启动流程
        /// </summary>
        /// <returns></returns>
        private async Task<bool> InternalStartAsync()
        {
            try
            {
                await _mqttStarterLock.WaitAsync();
                return await StartMqttConnectAsync();
            }
            catch (MqttCommunicationException e)
            {
                MqttClientLog.Log.Info(e.Message);
                return false;
            }
            catch (Exception ex)
            {
                MqttClientLog.Log.Error(ex);
                return false;
            }
            finally
            {
                _mqttStarterLock.Release();
            }
        }

        /// <summary>
        /// 开启mqtt连接
        /// </summary>
        /// <returns></returns>
        private async Task<bool> StartMqttConnectAsync()
        {
            MqttClientLog.Log.Info($"mqtt开始连接:{AppConfig.Ip}  {AppConfig.MqttPort}");
            await _deviceMqttClient.StartAsync(AppConfig.Ip, AppConfig.MqttPort);
            return true;
        }

        /// <summary>
        /// 建立mqtt 的事件绑定,已连接/断开连接/消息到达
        /// </summary>
        private void BindMqttClientEvent()
        {
            _deviceMqttClient.Client.ConnectedAsync += Client_ConnectedAsync;
            _deviceMqttClient.Client.DisconnectedAsync += Client_DisconnectedAsync;
            _deviceMqttClient.Client.ApplicationMessageReceivedAsync += Client_ApplicationMessageReceivedAsync;
        }

        private Task Client_DisconnectedAsync(MQTTnet.Client.MqttClientDisconnectedEventArgs arg)
        {
            MqttClientLog.Log.Info($"连接 {AppConfig.Ip}  {AppConfig.MqttPort} 断开");
            return Task.FromResult(false);
        }

        private Task Client_ConnectedAsync(MQTTnet.Client.MqttClientConnectedEventArgs arg)
        {
            MqttClientLog.Log.Info($"连接 {AppConfig.Ip}  {AppConfig.MqttPort} 成功");
            return Task.CompletedTask;
        }

        private Task Client_ApplicationMessageReceivedAsync(MQTTnet.Client.MqttApplicationMessageReceivedEventArgs arg)
        {
            //处理消息,分发到对应模块去响应
            MqttClientLog.Log.Info($"接收主题消息: {Environment.NewLine}     [topic]: {arg.ApplicationMessage.Topic}" + $" {Environment.NewLine}      [payload]:{Encoding.UTF8.GetString(arg.ApplicationMessage.Payload)}");

            MessageHandler(arg.ApplicationMessage.Topic, Encoding.UTF8.GetString(arg.ApplicationMessage.Payload));
            return Task.FromResult(true);
        }

        private void MessageHandler(string topic, string payload)
        {
            try
            {
                if (string.IsNullOrWhiteSpace(topic) || string.IsNullOrWhiteSpace(payload)) return;
                switch (topic)
                {
                    case @"linseer/topic":
                        {
                            var audioProcessingResult = JsonConvert.DeserializeObject<AudioProcessingResult>(payload);
                            if (audioProcessingResult is { Status: 1 })
                                CommService.MeetingReadManager.OnMeetingAliveChanged(true);
                            break;
                        }
                }
            }
            catch (Exception e)
            {
                MqttClientLog.Log.Error(e.Message);
            }
        }
    }
}

8 MqttService客户端调用

using System.Threading.Tasks;

namespace LinseerCopilot.Service.MqttConnectService
{
    public class MqttService
    {
        /// <summary>
        /// 连接器
        /// </summary>
        private readonly MqttConnectService _mqttConnectService;
        public MqttService()
        {
            //连接器
            _mqttConnectService = new MqttConnectService();
        }

        public async Task InitAsync()
        {
            await Task.Delay(5000);
            await _mqttConnectService.InitAsync();
        }
    }
}

 

posted on 2025-08-15 11:14  TanZhiWei  阅读(74)  评论(0)    收藏  举报