.Net快速掌握完整的MQTT客户端对接
1 引用Nuget包 MQTTnet.Extensions.ManagedClient
2 整体客户端大致结构

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(); } } }
浙公网安备 33010602011771号