使用的第三方庫:MQTTnet
MQTT資料接收事件引數
namespace Demo { /// <summary> /// MQTT資料接收事件引數 /// </summary> public class MqttReceivedEventArgs : EventArgs { /// <summary> /// 主題 /// </summary> public string Topic { get; } /// <summary> /// 訊息 /// </summary> public string Message { get; } /// <summary> /// 建構函式 /// </summary> /// <param name="topic"></param> /// <param name="message"></param> public MqttReceivedEventArgs(string topic, string message) { Topic = topic ?? throw new ArgumentNullException(nameof(topic)); Message = message ?? throw new ArgumentNullException(nameof(message)); } } }
MQTT本地客戶端
using Microsoft.Extensions.Logging; using MQTTnet; using MQTTnet.Client; using MQTTnet.Protocol; using System.Text; namespace Demo { /// <summary> /// MQTT 本地客戶端 /// </summary> public class MqttToLocalClient { /// <summary> /// 客戶端ID /// </summary> private readonly string _clientId = "RcsMqttClient_" + Guid.NewGuid(); /// <summary> /// 伺服器地址 /// </summary> private string _host = "127.0.0.1"; /// <summary> /// 伺服器埠 /// </summary> private int _port = 1883; /// <summary> /// 是否需要重連 /// </summary> private bool _needToReconnect = true; /// <summary> /// 需要訂閱的主題 /// </summary> private List<string> _topics = new(); /// <summary> /// 日誌服務 /// </summary> private readonly ILogger? _logger; /// <summary> /// MQTT客戶端 /// </summary> private IMqttClient? _mqttClient; /// <summary> /// MQTT客戶端選項 /// </summary> private MqttClientOptions? _mqttClientOptions; /// <summary> /// 訊息接收事件 /// </summary> public event Func<MqttReceivedEventArgs, Task>? MessageReceivedEvent; /// <summary> /// 建構函式 /// </summary> /// <param name="logger"></param> public MqttToLocalClient(ILogger<MqttToLocalClient>? logger = null) { _logger = logger; } /// <summary> /// 初始化 /// </summary> /// <param name="host"></param> /// <param name="port"></param> /// <param name="needToReconnect"></param> /// <returns></returns> public async Task InitAsync(string host = "127.0.0.1", int port = 1883, bool needToReconnect = true) { // 客戶端引數 _host = host; _port = port; _needToReconnect = needToReconnect; // 建立連線 await ConnectAsync(); // 判斷連線狀態的執行緒 CheckConnectThread(); } /// <summary> /// 建立連線 /// </summary> /// <returns></returns> /// <exception cref="Exception"></exception> private async Task ConnectAsync() { // 客戶端引數 _mqttClientOptions = new MqttClientOptionsBuilder() .WithTcpServer(_host, _port) .WithClientId(_clientId) .Build(); // 建立客戶端 _mqttClient = new MqttFactory().CreateMqttClient(); // 連線成功事件 _mqttClient.ConnectedAsync += MqttClient_ConnectedAsync; // 斷開連線事件 _mqttClient.DisconnectedAsync += MqttClient_DisconnectedAsync; // 訊息接收事件 _mqttClient.ApplicationMessageReceivedAsync += MqttClient_ApplicationMessageReceivedAsync; // 開始連線 var result = await _mqttClient.ConnectAsync(_mqttClientOptions); if (result.ResultCode != MqttClientConnectResultCode.Success) { throw new Exception($"Mqtt connect fail, Result code {result.ResultCode}"); } } /// <summary> /// 訂閱主題 /// </summary> /// <param name="topic"></param> /// <returns></returns> private async Task SubscribeAsync(string topic) { if (_mqttClient?.IsConnected == true) { var mqttSubscribeOptions = new MqttFactory().CreateSubscribeOptionsBuilder() .WithTopicFilter( f => { f.WithTopic(topic).WithQualityOfServiceLevel(MqttQualityOfServiceLevel.ExactlyOnce); }) .Build(); var response = await _mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None); } } /// <summary> /// 訂閱主題 /// </summary> /// <param name="topics"></param> /// <returns></returns> public async Task SubscribeAsync(List<string> topics) { if (_mqttClient?.IsConnected == true) { // 快取 if (_topics != topics) { _topics = topics; } // 遍歷 foreach (string topic in _topics) { await SubscribeAsync(topic); } } } /// <summary> /// 釋出訊息 /// </summary> /// <param name="topic"></param> /// <param name="message"></param> /// <param name="qos"></param> /// <returns></returns> public async Task PublishAsync(string topic, string message, int qos) { if (_mqttClient?.IsConnected == true) { _logger?.LogDebug("Mqtt message publish: {0}, {1}", topic, message); var applicationMessage = new MqttApplicationMessageBuilder() .WithTopic(topic) .WithPayload(message) .WithQualityOfServiceLevel((MqttQualityOfServiceLevel)qos) .Build(); await _mqttClient.PublishAsync(applicationMessage, CancellationToken.None); } } /// <summary> /// 連線成功事件 /// </summary> /// <param name="arg"></param> /// <returns></returns> private async Task MqttClient_ConnectedAsync(MqttClientConnectedEventArgs arg) { _logger?.LogInformation("Mqtt connected, IP: {0}, IsSessionPresent: {1}", _host, arg.ConnectResult.IsSessionPresent); await Task.CompletedTask; } /// <summary> /// 斷開連線事件 /// </summary> /// <param name="arg"></param> /// <returns></returns> private async Task MqttClient_DisconnectedAsync(MqttClientDisconnectedEventArgs arg) { _logger?.LogInformation("Mqtt disconnected, IP: {0}, Reason: {1}, IsSessionPresent: {2}", _host, arg.Reason.ToString(), arg.ClientWasConnected); await Task.CompletedTask; } /// <summary> /// 訊息接收事件 /// </summary> /// <param name="arg"></param> /// <returns></returns> private async Task MqttClient_ApplicationMessageReceivedAsync(MqttApplicationMessageReceivedEventArgs arg) { try { // 話題 string topic = arg.ApplicationMessage.Topic; // 接收的資料 byte[] data = arg.ApplicationMessage.PayloadSegment.ToArray(); // 接收的字串 string message = Encoding.UTF8.GetString(data); _logger?.LogDebug("Mqtt message received: {0}, {1}", topic, message); // 資料接收事件 MessageReceivedEvent?.Invoke(new MqttReceivedEventArgs(topic, message)); await Task.CompletedTask; } catch (Exception ex) { _logger?.LogError(ex, "MQTT接收資料的執行緒報錯"); } } /// <summary> /// 判斷連線狀態的執行緒 /// </summary> private void CheckConnectThread() { Task.Run(async () => { while (true) { try { // 判斷連線狀態 if (_needToReconnect && _mqttClient?.IsConnected != true) { // 重新連線 await ConnectAsync(); // 訂閱主題 await SubscribeAsync(_topics); } } catch (Exception ex) { _logger?.LogError(ex, "MQTT判斷連線狀態的執行緒報錯"); } await Task.Delay(1000); } }); } } }
MQTT遠端客戶端
using Microsoft.Extensions.Logging; using MQTTnet; using MQTTnet.Client; using MQTTnet.Protocol; using MQTTnet.Server; using System.Text; namespace Demo { /// <summary> /// MQTT 遠端客戶端 /// </summary> public class MqttToRemoteClient { /// <summary> /// 客戶端ID /// </summary> private string? _clientId; /// <summary> /// 伺服器地址 /// </summary> private string? _host; /// <summary> /// 伺服器埠 /// </summary> private int _port = 1883; /// <summary> /// 是否需要重連 /// </summary> private bool _needToReconnect = true; /// <summary> /// 需要訂閱的主題 /// </summary> private List<string> _topics = new(); /// <summary> /// 初始化標誌 /// </summary> private bool _initFlag = false; /// <summary> /// 日誌服務 /// </summary> private readonly ILogger? _logger; /// <summary> /// MQTT客戶端 /// </summary> private IMqttClient? _mqttClient; /// <summary> /// MQTT客戶端選項 /// </summary> private MqttClientOptions? _mqttClientOptions; /// <summary> /// MQTT客戶端鎖 /// </summary> private readonly object _lock = new(); /// <summary> /// 訊息接收事件 /// </summary> public event Func<MqttReceivedEventArgs, Task>? MessageReceivedEvent; /// <summary> /// 建構函式 /// </summary> /// <param name="logger"></param> public MqttToRemoteClient(ILogger<MqttToLocalClient>? logger = null) { _logger = logger; } /// <summary> /// 初始化 /// </summary> public void Init() { if (_initFlag == false) { // 判斷連線狀態的執行緒 CheckConnectThread(); _initFlag = true; } } /// <summary> /// 更新MQTT資訊 /// </summary> /// <param name="clientId"></param> /// <param name="host"></param> /// <param name="port"></param> /// <param name="needToReconnect"></param> /// <returns></returns> public async Task UpdMqttInfoAsync(string clientId, string host, int port = 1883, bool needToReconnect = true) { await Task.Run(() => { lock (_lock) { _clientId = clientId; _host = host; _port = port; _needToReconnect = needToReconnect; } }); } /// <summary> /// 建立連線 /// </summary> /// <returns></returns> /// <exception cref="Exception"></exception> private async Task ConnectAsync() { // 客戶端引數 _mqttClientOptions = new MqttClientOptionsBuilder() .WithTcpServer(_host, _port) .WithClientId(_clientId) .Build(); // 建立客戶端 _mqttClient = new MqttFactory().CreateMqttClient(); // 連線成功事件 _mqttClient.ConnectedAsync += MqttClient_ConnectedAsync; // 斷開連線事件 _mqttClient.DisconnectedAsync += MqttClient_DisconnectedAsync; // 訊息接收事件 _mqttClient.ApplicationMessageReceivedAsync += MqttClient_ApplicationMessageReceivedAsync; // 開始連線 var result = await _mqttClient.ConnectAsync(_mqttClientOptions); if (result.ResultCode != MqttClientConnectResultCode.Success) { throw new Exception($"Mqtt connect fail, Result code {result.ResultCode}"); } } /// <summary> /// 斷開連線 /// </summary> /// <param name="needToReconnect"></param> /// <returns></returns> public async Task DisconnectAsync(bool needToReconnect = false) { await Task.Run(() => { lock (_lock) { _needToReconnect = needToReconnect; _mqttClient.DisconnectAsync().Wait(); } }); } /// <summary> /// 訂閱主題 /// </summary> /// <param name="topic"></param> /// <returns></returns> private async Task SubscribeAsync(string topic) { if (_mqttClient?.IsConnected == true) { var mqttSubscribeOptions = new MqttFactory().CreateSubscribeOptionsBuilder() .WithTopicFilter( f => { f.WithTopic(topic).WithQualityOfServiceLevel(MqttQualityOfServiceLevel.ExactlyOnce); }) .Build(); var response = await _mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None); } } /// <summary> /// 訂閱主題 /// </summary> /// <param name="topics"></param> /// <returns></returns> public async Task SubscribeAsync(List<string> topics) { if (_mqttClient?.IsConnected == true) { // 快取 if (_topics != topics) { _topics = topics; } // 遍歷 foreach (string topic in _topics) { await SubscribeAsync(topic); } } } /// <summary> /// 釋出訊息 /// </summary> /// <param name="topic"></param> /// <param name="message"></param> /// <param name="qos"></param> /// <returns></returns> public async Task PublishAsync(string topic, string message, int qos) { if (_mqttClient?.IsConnected == true) { _logger?.LogDebug("Mqtt message publish: {0}, {1}", topic, message); var applicationMessage = new MqttApplicationMessageBuilder() .WithTopic(topic) .WithPayload(message) .WithQualityOfServiceLevel((MqttQualityOfServiceLevel)qos) .Build(); await _mqttClient.PublishAsync(applicationMessage, CancellationToken.None); } } /// <summary> /// 連線成功事件 /// </summary> /// <param name="arg"></param> /// <returns></returns> private async Task MqttClient_ConnectedAsync(MqttClientConnectedEventArgs arg) { _logger?.LogInformation("Mqtt connected, IP: {0}, IsSessionPresent: {1}", _host, arg.ConnectResult.IsSessionPresent); await Task.CompletedTask; } /// <summary> /// 斷開連線事件 /// </summary> /// <param name="arg"></param> /// <returns></returns> private async Task MqttClient_DisconnectedAsync(MqttClientDisconnectedEventArgs arg) { _logger?.LogInformation("Mqtt disconnected, IP: {0}, Reason: {1}, IsSessionPresent: {2}", _host, arg.Reason.ToString(), arg.ClientWasConnected); await Task.CompletedTask; } /// <summary> /// 訊息接收事件 /// </summary> /// <param name="arg"></param> /// <returns></returns> private async Task MqttClient_ApplicationMessageReceivedAsync(MqttApplicationMessageReceivedEventArgs arg) { try { // 話題 string topic = arg.ApplicationMessage.Topic; // 接收的資料 byte[] data = arg.ApplicationMessage.PayloadSegment.ToArray(); // 接收的字串 string message = Encoding.UTF8.GetString(data); _logger?.LogDebug("Mqtt message received: {0}, {1}", topic, message); // 資料接收事件 MessageReceivedEvent?.Invoke(new MqttReceivedEventArgs(topic, message)); await Task.CompletedTask; } catch (Exception ex) { _logger?.LogError(ex, "MQTT接收資料的執行緒報錯"); } } /// <summary> /// 判斷連線狀態的執行緒 /// </summary> private void CheckConnectThread() { Task.Run(async () => { while (true) { try { await Task.Run(() => { lock (_lock) { // 判斷連線狀態 if (_host != null && _needToReconnect && _mqttClient?.IsConnected != true) { // 重新連線 ConnectAsync().Wait(); // 訂閱主題 SubscribeAsync(_topics).Wait(); } } }); } catch (Exception ex) { _logger?.LogError(ex, "MQTT判斷連線狀態的執行緒報錯"); } await Task.Delay(1000); } }); } } }