C# MQTT客戶端

Mr_Xul發表於2024-04-02

使用的第三方庫:MQTTnet

MQTT資料接收事件引數

C# 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));
        }
    }
}
View Code

MQTT本地客戶端

C# 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);
                }
            });
        }
    }
}
View Code

MQTT遠端客戶端

C# 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);
                }
            });
        }
    }
}
View Code

相關文章