Abp vnext + MQTTnet

前言

閱讀前,先了解下 MQTTnet 如何集成 AspNetCore:
https://github.com/dotnet/MQTTnet/wiki/Server#validating-mqtt-clients

Artizan.Iot.Hub.Mqtt.Application.Contracts

新建類庫項目 【Artizan.Iot.Hub.Mqtt.Application.Contracts】,添加對包MQTTnet的引用

 <PackageReference Include="MQTTnet" Version="4.1.4.563" />

IMqttServiceBase

在項目【Artizan.Iot.Hub.Mqtt.Application.Contracts】中新建接口 IMqttServiceBase ,用於配置 MQTTnet 框架的 MqttServer 對象
代碼清單:Artizan.Iot.Hub.Mqtt.Application.Contracts/Server/IMqttServiceBase.cs

using MQTTnet.Server;

namespace Artizan.Iot.Hub.Mqtt.Server
{
    public interface IMqttServiceBase
    {
        protected MqttServer MqttServer { get; }
        /// <summary>
        /// 配置MqttServer
        /// </summary>
        /// <param name="mqttServer"></param>
        void ConfigureMqttServer(MqttServer mqttServer);
    }
}

其中,MqttServer 保留對 MqttServer 的對象的引用,以便在接口方法

void ConfigureMqttServer(MqttServer mqttServer)

中對其進行配置。

IMqttConnectionService :負責 MqttServer 與連接相關初始化

代碼清單:Artizan.Iot.Hub.Mqtt.Application.Contracts/Server/IMqttConnectionService.cs

namespace Artizan.Iot.Hub.Mqtt.Server
{
    public interface IMqttConnectionService : IMqttServiceBase
    {
    }
}

IMqttPublishingService:負責 MqttServer 的發佈相關初始化

代碼清單:Artizan.Iot.Hub.Mqtt.Application.Contracts/Server/IMqttPublishingService .cs

namespace Artizan.Iot.Hub.Mqtt.Server
{
    public interface IMqttPublishingService : IMqttServiceBase
    {
    }
}

IMqttSubscriptionService:負責 MqttServer 的訂閱相關初始化

代碼清單:Artizan.Iot.Hub.Mqtt.Application.Contracts/Server/IMqttPublishingService .cs

namespace Artizan.Iot.Hub.Mqtt.Server
{
    public interface IMqttSubscriptionService : IMqttServiceBase
    {
    }
}

Artizan.Iot.Hub.Mqtt.Application

新建類庫項目 【Artizan.Iot.Hub.Mqtt.Application】,添加對項目 【Artizan.Iot.Hub.Mqtt.Application.Contracts的引用

MqttServiceBase

在項目【Artizan.Iot.Hub.Mqtt.Application】中新建接口 IMqttServiceBase 的實現類IMqttServiceBase:
代碼清單:Artizan.Iot.Hub.Mqtt.Application/Server/MqttServiceBase.cs

using MQTTnet;
using MQTTnet.Server;

namespace Artizan.Iot.Hub.Mqtt.Server
{
    public class MqttServiceBase : IMqttServiceBase
    {
        public MqttServer MqttServer { get; private set; }

        public MqttServiceBase() { }

        public virtual void ConfigureMqttServer(MqttServer mqttServer)
        {
            MqttServer = mqttServer;
        }

        protected virtual async Task<MqttClientStatus?> GetClientStatusAsync(string clientId)
        {
            var allClientStatuses = await MqttServer.GetClientsAsync();
            return allClientStatuses.FirstOrDefault(cs => cs.Id == clientId);
        }

        protected virtual string GetClientIdFromPayload(MqttApplicationMessage message)
        {
            var payload = System.Text.Encoding.UTF8.GetString(message.Payload);
            // TODO: for JSON type data transfer get clientId from json payload
            return payload;
        }

        protected virtual async Task DisconnectClientAsync(string clientId)
        {
            var clientStatus = await GetClientStatusAsync(clientId);
            if (clientStatus != null)
            {
                await clientStatus.DisconnectAsync();
            }
        }

   
    }
}

MqttConnectionService :負責 MqttServer 與連接相關初始化

代碼清單:Artizan.Iot.Hub.Mqtt.Application/Server/MqttConnectionService.cs

using Artizan.Iot.Hub.Mqtt.Topics;
using Artizan.Iot.Hub.Mqtt.Server.Etos;
using MQTTnet;
using MQTTnet.Server;
using System;
using System.Collections.Generic;
using System.Diagnostics.Tracing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.EventBus.Distributed;
using MQTTnet.Protocol;
using Microsoft.Extensions.Logging;
using Artizan.Iot.Hub.Mqtt.Server.Extensions;

namespace Artizan.Iot.Hub.Mqtt.Server
{
    public class MqttConnectionService : MqttServiceBase, IMqttConnectionService, ITransientDependency
    {
        private readonly ILogger<MqttConnectionService> _logger;
        private readonly IDistributedEventBus _distributedEventBus;

        public MqttConnectionService(
            ILogger<MqttConnectionService> logger,
            IDistributedEventBus distributedEventBus)
            : base()
        {
            _logger = logger;
            _distributedEventBus = distributedEventBus;
        }

        public override void ConfigureMqttServer(MqttServer mqttServer)
        {
            base.ConfigureMqttServer(mqttServer);

            MqttServer.ValidatingConnectionAsync += ValidatingConnectionHandlerAsync; ;
            MqttServer.ClientConnectedAsync += ClientConnectedHandlerAsync;
            MqttServer.ClientDisconnectedAsync += ClientDisconnectedHandlerAsync;
        }

        private Task ValidatingConnectionHandlerAsync(ValidatingConnectionEventArgs eventArgs)
        {
            // TODO:檢查設備
            // 無效客戶端:if(context.ClientId != deviceId) context.ReasonCode = MQTTnet.Protocol.MqttConnectReasonCode.ClientIdentifierNotValid; // id不對
            // 用戶名和密碼不對:MQTTnet.Protocol.MqttConnectReasonCode.BadUserNameOrPassword;
            // 沒有權限:        MQTTnet.Protocol.MqttConnectReasonCode.NotAuthorized;
            // ???:          MqttConnectReasonCode.UnspecifiedError;

            if (eventArgs.ClientId == "SpecialClient")
            {
                if (eventArgs.UserName != "USER" || eventArgs.Password != "PASS")
                {
                    eventArgs.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword;
                }
            }

            return Task.CompletedTask;
        }

        private async Task ClientConnectedHandlerAsync(ClientConnectedEventArgs eventArgs)
        {
            MqttServer.PublishByHub(BrokerTopics.Event.NewClientConnected, eventArgs.ClientId);

            await _distributedEventBus.PublishAsync(
               new ClientConnectedEto
               {
                   ClientId = eventArgs.ClientId
               }
            );
        }

        private async Task ClientDisconnectedHandlerAsync(ClientDisconnectedEventArgs eventArgs)
        {
            MqttServer.PublishByHub(BrokerTopics.Event.NewClientDisconnected, eventArgs.ClientId);

            await _distributedEventBus.PublishAsync(
               new ClientDisconnectedEto
               {
                   ClientId = eventArgs.ClientId
               }
            );
        }
    }
}

其中,

  • MQTTnet的MqttServer對象的初始化,
            MqttServer.ValidatingConnectionAsync += ValidatingConnectionHandlerAsync; // 處理連接驗證
            MqttServer.ClientConnectedAsync += ClientConnectedHandlerAsync;  // 處理連接
            MqttServer.ClientDisconnectedAsync += ClientDisconnectedHandlerAsync; // 處理客戶端斷開
  • IotHub 發佈內部主題
// MQtt 主題:/sys/broker/event/client-connected/new
 MqttServer.PublishByHub(BrokerTopics.Event.NewClientConnected, eventArgs.ClientId);

其中使用的是自定義擴展方法:
代碼清單:Artizan.Iot.Hub.Mqtt.Application/Server/Extensions/MqttServerExtensions

using Artizan.Iot.Hub.Mqtt.Topics;
using MQTTnet;
using MQTTnet.Server;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Artizan.Iot.Hub.Mqtt.Server.Extensions
{
    public static class MqttServerExtensions
    {
        /// <summary>
        ////  -----------------------------------------------------------------  
        ///  MQTTnet v3.x :
        ///     MQTTnet v3.x 中的MqttServer 類中的方法PublishAsync() 已被刪除,故以下代碼不能再使用:
        ///
        ///     await MqttServer.PublishAsync(BrokerEventTopics.NewClientConnected, arg.ClientId);
        ///     ,其源碼: https://github.com/dotnet/MQTTnet/blob/release/3.1.x/Source/MQTTnet/Server/MqttServer.cs
        ///     
        ///  ----------------------------------------------------------------- 
        ///   MQTTnet v4.1 :
        ///     該版本中可以調用:MqttServer.InjectApplicationMessage() 方法注入消息
        ///    
        /// </summary>
        /// <param name="mqttServer"></param>
        /// <param name="mqttApplicationMessage"></param>
        /// <exception cref="ArgumentNullException"></exception>

        public static void PublishByHub(this MqttServer mqttServer, string topic, string payload)
        {
            if (topic == null) throw new ArgumentNullException(nameof(topic));

            mqttServer.PublishByHub(new MqttApplicationMessageBuilder()
                .WithTopic(topic)
                .WithPayload(payload)
                .Build());
        }

        public static void PublishByHub(this MqttServer mqttServer, MqttApplicationMessage mqttApplicationMessage)
        {
            if (mqttServer == null) throw new ArgumentNullException(nameof(mqttServer));
            if (mqttApplicationMessage == null) throw new ArgumentNullException(nameof(mqttApplicationMessage));

            mqttServer.InjectApplicationMessage(new InjectedMqttApplicationMessage(mqttApplicationMessage)
            {
                SenderClientId = MqttServiceConsts.IotHubMqttClientId
            });

        }
    }
}

  • 通過分佈式事件總線,IDistributedEventBus _distributedEventBus 向它模塊或者微服務發送事件,以降低耦合度。
    事件參數Eto的定義如下:
namespace Artizan.Iot.Hub.Mqtt.Server.Etos
{
    /// <summary>
    /// EventName 屬性是可選的,但是建議使用。
    /// 如果沒有爲事件類型(ETO 類)聲明它,
    /// 則事件名稱將是事件類的全名,
    /// 參見:Abp:Distributed Event Bus: https://docs.abp.io/en/abp/latest/Distributed-Event-Bus
    /// 
    /// 客戶端上線
    /// 
    /// </summary>
    [EventName(MqttServerEventConsts.ClientConnected)]
    public class ClientConnectedEto
    {
        public string ClientId { get; set; }
    }

    /// <summary>
    /// Eto: Client下線
    /// </summary>
    [EventName(MqttServerEventConsts.ClientDisconnected)]
    public class ClientDisconnectedEto
    {
        public string ClientId { get; set; }
    }
}

IMqttPublishingService:負責 MqttServer 的發佈相關初始化

代碼清單:Artizan.Iot.Hub.Mqtt.Application/Server/MqttPublishingService.cs

using Artizan.Iot.Hub.Mqtt.Server.Etos;
using MQTTnet.Server;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.EventBus.Distributed;
using Artizan.Iot.Hub.Mqtt.Topics;
using MQTTnet;
using Microsoft.Extensions.Logging;

namespace Artizan.Iot.Hub.Mqtt.Server
{
    public class MqttPublishingService : MqttServiceBase, IMqttPublishingService, ITransientDependency
    {
        private readonly ILogger<MqttPublishingService> _logger;
        private readonly IDistributedEventBus _distributedEventBus;

        public MqttPublishingService(
            ILogger<MqttPublishingService> logger,
            IDistributedEventBus distributedEventBus)
           : base()
        {
            _logger = logger;
            _distributedEventBus = distributedEventBus;
        }

        public override void ConfigureMqttServer(MqttServer mqttServer)
        {
            base.ConfigureMqttServer(mqttServer);

            MqttServer.InterceptingPublishAsync += InterceptingPublishHandlerAsync;
        }

        private async Task InterceptingPublishHandlerAsync(InterceptingPublishEventArgs eventArgs)
        {
            var topic = eventArgs.ApplicationMessage.Topic;

            _logger.LogDebug($"'{eventArgs.ClientId}' published '{eventArgs.ApplicationMessage.Topic}' > '{Encoding.UTF8.GetString(eventArgs.ApplicationMessage.Payload ?? new byte[0])}'");

            #region TODO:根據ClientId,用戶等判斷是能發佈這種主題 等具體的業務邏輯,慢慢規劃

            /// TDOD
            if (topic == "not_allowed_topic") // TODO:
            {
                eventArgs.ProcessPublish = false;
                eventArgs.CloseConnection = true;
            }

            if (MqttTopicFilterComparer.Compare(eventArgs.ApplicationMessage.Topic, "/myTopic/WithTimestamp/#") == MqttTopicFilterCompareResult.IsMatch)
            {
                // Replace the payload with the timestamp. But also extending a JSON 
                // based payload with the timestamp is a suitable use case.
                eventArgs.ApplicationMessage.Payload = Encoding.UTF8.GetBytes(DateTime.Now.ToString("O"));
            } 
            #endregion

            // TODO:根據ClientId, 判斷是否有權限去發佈主題
            if (MqttTopicHelper.IsSystemTopic(topic))
            {
                if (!MqttTopicHelper.IsBrokerItself(eventArgs.ClientId))
                {
                    // 客戶端要發佈系統主題,不接受,而是由系統執行系統主題的發佈
                    // await _mqttInternalService.ExecuteSystemCommandAsync(context);
                    eventArgs.ProcessPublish = false;
                    return;
                }
            }
            else
            {
                if (!eventArgs.SessionItems.Contains(topic))
                {
                    var payloadString = Encoding.UTF8.GetString(eventArgs.ApplicationMessage.Payload);
                    eventArgs.SessionItems.Add(eventArgs.ApplicationMessage.Topic, eventArgs.ApplicationMessage.Payload);
                }
                else
                {
                    var retainPayload = (byte[])eventArgs.SessionItems[eventArgs.ApplicationMessage.Topic];
                    if (!retainPayload.SequenceEqual(eventArgs.ApplicationMessage.Payload))
                    {
                    }
                }
            }

            if (eventArgs.ProcessPublish)
            {
                await _distributedEventBus.PublishAsync(
                   new ClientPublishTopicEto
                   {
                       ClientId = eventArgs.ClientId,
                       Topic = eventArgs.ApplicationMessage.Topic,
                       Payload = eventArgs.ApplicationMessage.Payload
                   }
                );
            }
        }
    }
}

IMqttSubscriptionService:負責 MqttServer 的訂閱相關初始化

代碼清單:Artizan.Iot.Hub.Mqtt.Application/Server/MqttPublishingService.cs

using Artizan.Iot.Hub.Mqtt.Topics;
using Microsoft.Extensions.Logging;
using MQTTnet.Protocol;
using MQTTnet.Server;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.EventBus.Distributed;

namespace Artizan.Iot.Hub.Mqtt.Server
{
    /// <summary>
    /// TODO: 加入權限系統
    /// </summary>
    public class MqttSubscriptionService : MqttServiceBase, IMqttSubscriptionService, ITransientDependency
    {
        private readonly ILogger<MqttPublishingService> _logger;
        private readonly IDistributedEventBus _distributedEventBus;
        public MqttSubscriptionService(
            ILogger<MqttPublishingService> logger,
            IDistributedEventBus distributedEventBus)
           : base()
        {
            _logger= logger;
            _distributedEventBus = distributedEventBus;
        }
        public override void ConfigureMqttServer(MqttServer mqttServer)
        {
            base.ConfigureMqttServer(mqttServer);

            MqttServer.ClientSubscribedTopicAsync += ClientSubscribedTopicHandlerAsync;
            MqttServer.InterceptingSubscriptionAsync += InterceptingSubscriptionHandlerAsync;
            MqttServer.ClientUnsubscribedTopicAsync += ClientUnsubscribedTopicHandlerAsync;
            MqttServer.InterceptingUnsubscriptionAsync += InterceptingUnsubscriptionHandlerAsync;
        }

        /// <summary>
        /// 處理客戶端訂閱
        /// </summary>
        /// <param name="eventArgs"></param>
        /// <returns></returns>
        /// <exception cref="NotImplementedException"></exception>
        private Task ClientSubscribedTopicHandlerAsync(ClientSubscribedTopicEventArgs eventArgs)
        {
            _logger.LogDebug($"'{eventArgs.ClientId}' subscribed '{eventArgs.TopicFilter.Topic}'");
            return Task.CompletedTask;
        }

        /// <summary>
        /// 攔截客戶端訂閱
        /// </summary>
        /// <param name="eventArgs"></param>
        /// <returns></returns>
        /// <exception cref="NotImplementedException"></exception>
        private Task InterceptingSubscriptionHandlerAsync(InterceptingSubscriptionEventArgs eventArgs)
        {
            /// TODO: 使用數據庫+緩存 Client 的訂閱權限

            if (MqttTopicHelper.IsSystemTopic(eventArgs.TopicFilter.Topic) && eventArgs.ClientId != "Administrator")  // TODO:後續支持可配置是否可訂閱指定的主題
            {
                eventArgs.Response.ReasonCode = MqttSubscribeReasonCode.ImplementationSpecificError;
            }

            if (eventArgs.TopicFilter.Topic.StartsWith(BrokerTopics.Secret.Base) && eventArgs.ClientId != "Imperator")
            {
                eventArgs.Response.ReasonCode = MqttSubscribeReasonCode.ImplementationSpecificError;
                eventArgs.CloseConnection = true;
            }

            return Task.CompletedTask;
        }

        /// <summary>
        /// 處理客戶端取消訂閱
        /// </summary>
        /// <param name="eventArgs "></param>
        /// <returns></returns>
        /// <exception cref="NotImplementedException"></exception>
        private Task ClientUnsubscribedTopicHandlerAsync(ClientUnsubscribedTopicEventArgs eventArgs )
        {
            //_logger.LogDebug($"'{eventArgs.ClientId}' unsubscribed topic '{eventArgs.xxx}'");
            return Task.CompletedTask;
        }

        /// <summary>
        /// 攔截取消訂閱
        /// </summary>
        /// <param name="eventArgs"></param>
        /// <returns></returns>
        /// <exception cref="NotImplementedException"></exception>
        private Task InterceptingUnsubscriptionHandlerAsync(InterceptingUnsubscriptionEventArgs eventArgs)
        {
            _logger.LogDebug($"'{eventArgs.ClientId}' unsubscribed topic '{eventArgs.Topic}'");
            return Task.CompletedTask;
        }

    }
}

MqttServer 初始化彙總類

定義一個接口IMqttServerService,用於彙總並一次性調用用於初始化 MQTTnetMqttServer對象的所有服務接口:

  • IMqttConnectionService
  • IMqttPublishingService
  • IMqttSubscriptionService,

目的是實現內部封裝,調用者就不用關心瞭解 MQTTnetMqttServer對象初始化細節。

在項目【Artizan.Iot.Hub.Mqtt.Application.Contracts】中新建接口 IMqttServerService
代碼清單:Artizan.Iot.Hub.Mqtt.Application.Contracts/Server/IMqttServerService.cs

using MQTTnet.Server;

namespace Artizan.Iot.Hub.Mqtt.Server
{
    public interface IMqttServerService
    {
        MqttServer MqttServer { get; }
          
        /// <summary>
        /// 配置MqttServer
        /// </summary>
        /// <param name="mqttServer"></param>
        void ConfigureMqttService(MqttServer mqttServer);
    }
}

在項目【Artizan.Iot.Hub.Mqtt.Application】中實現接口 IMqttServerService
代碼清單:Artizan.Iot.Hub.Mqtt.ApplicationServer/MqttServerService.cs

using MQTTnet.Server;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Volo.Abp.Application.Services;
using Volo.Abp.DependencyInjection;

namespace Artizan.Iot.Hub.Mqtt.Server
{
    public class MqttServerService: IMqttServerService, ITransientDependency
    { 
        private readonly IMqttConnectionService _mqttConnectionService;
        private readonly IMqttPublishingService _mqttPublishingService;
        private readonly IMqttSubscriptionService _mqttSubscriptionService;
        private readonly IMqttInternalService _mqttInternalService;
        public MqttServer MqttServer { get; private set; }

        public MqttServerService(
            IMqttConnectionService mqttConnectionService, 
            IMqttPublishingService mqttPublishingService, 
            IMqttSubscriptionService mqttSubscriptionService, 
            IMqttInternalService mqttInternalService)
        {
            _mqttConnectionService = mqttConnectionService;
            _mqttPublishingService = mqttPublishingService;
            _mqttSubscriptionService = mqttSubscriptionService;
            _mqttInternalService = mqttInternalService;
        }


        public void ConfigureMqttService(MqttServer mqttServer)
        {
            MqttServer =  mqttServer;

            _mqttConnectionService.ConfigureMqttServer(mqttServer);
            _mqttPublishingService.ConfigureMqttServer(mqttServer);
            _mqttSubscriptionService.ConfigureMqttServer(mqttServer);
            _mqttInternalService.ConfigureMqttServer(mqttServer);
        }
    }
}

ConfigureMqttService()內對 MQTTnetMqttServer對象相關初始化進行調用。

MQTTnet 集成 AspNet Core 擴展類庫

MQTTnet集成 AspNet Core需要做一些額外的代碼編寫,單獨新建一個類庫,以便使用者不需要了結 MQTTnet 內部的初始化步驟,使用起來更加絲滑,參見:
https://github.com/dotnet/MQTTnet/wiki/Server#validating-mqtt-clients
下面編寫一個類庫【Artizan.Iot.Hub.Mqtt.AspNetCore】,封裝這些初始化工作,
新建類庫項目【Artizan.Iot.Hub.Mqtt.AspNetCore】,添加如下包引用:

  <ItemGroup>
    <PackageReference Include="MQTTnet.AspNetCore" Version="4.1.4.563" />
    <PackageReference Include="Volo.Abp.AspNetCore.Mvc" Version="7.0.1" />
  </ItemGroup>

IIotHubMqttServer

新建接口IIotHubMqttServer,注意這裏繼承接口:ISingletonDependency,使其實現類成爲單例
代碼清單:Artizan.Iot.Hub.Mqtt.AspNetCore/Server/IIotHubMqttServer

using MQTTnet.Server;

namespace Artizan.Iot.Hub.Mqtt.AspNetCore.Servser
{
    public interface IIotHubMqttServer : ISingletonDependency
    {
        MqttServer MqttServer { get; }
        void ConfigureMqttServer(MqttServer mqttServer);
    }
}

實現類:IotHubMqttServer

接口IIotHubMqttServer的實現類:IotHubMqttServer,
特別注意:這裏使用的是單例接口 ISingletonDependency,其目的是確保 MQTTnet中的MqttServer對象是唯一的(即:單例)。

代碼清單:Artizan.Iot.Hub.Mqtt.AspNetCore/Server/IotHubMqttServer

using Artizan.Iot.Hub.Mqtt.Server;
using MQTTnet.Server;
using Volo.Abp.DependencyInjection;

namespace Artizan.Iot.Hub.Mqtt.AspNetCore.Servser
{

    /// <summary>
    /// MQTTnet 集成 AspNetCore:
    /// https://github.com/dotnet/MQTTnet/wiki/Server#validating-mqtt-clients
    /// 
    /// 特別注意:這裏使用的是單例接口 ISingletonDependency
    /// 
    /// ----------
    /// 使用MQTTnet部署MQTT服務:
    /// https://www.cnblogs.com/wx881208/p/14325011.html--+
    /// C# MQTTnet 3.1升級到MQTTnet 4.0 Client編程變化:
    /// https://blog.csdn.net/yuming/article/details/125834921?spm=1001.2101.3001.6650.7&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7ERate-7-125834921-blog-127175694.pc_relevant_3mothn_strategy_recovery&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7ERate-7-125834921-blog-127175694.pc_relevant_3mothn_strategy_recovery&utm_relevant_index=12
    /// </summary>
    public class IotHubMqttServer : IIotHubMqttServer, ISingletonDependency
    {
        private readonly IMqttServerService _mqttServerService;

        public IotHubMqttServer(IMqttServerService mqttServerService)
        {
            _mqttServerService = mqttServerService;
        }
        public void ConfigureMqttServer(MqttServer mqttServer)
        {
            MqttServer = mqttServer;
            _mqttServerService.ConfigureMqttService(mqttServer);
        }
    }
}

其中,

    _mqttServerService.ConfigureMqttService(mqttServer);

調用IMqttServerService 初始化彙總類的ConfigureMqttServer()的方法,對 MQTTnetMqttServer所有的初始化服務一次性調用。

MQTTnet 與 AspNetCore 集成

新建 【AspNet Core WebApi】項目,名爲【Artizan.Iot.Hub.Mqtt.HttpApi.Host】,添加如下包的引用

  <ItemGroup>
    <PackageReference Include="Serilog.AspNetCore" Version="6.1.0" />
    <PackageReference Include="Serilog.Sinks.Async" Version="1.5.0" />
    <PackageReference Include="Volo.Abp.AspNetCore.Serilog" Version="7.0.1" />
    <PackageReference Include="Volo.Abp.Autofac" Version="7.0.1" />
    <PackageReference Include="Volo.Abp.Swashbuckle" Version="7.0.1" />
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\Artizan.Iot.Hub.Mqtt.Application\Artizan.Iot.Hub.Mqtt.Application.csproj" />
    <ProjectReference Include="..\Artizan.Iot.Hub.Mqtt.AspNetCore\Artizan.Iot.Hub.Mqtt.AspNetCore.csproj" />
    <ProjectReference Include="..\Artizan.Iot.Hub.Mqtt.HttpApi\Artizan.Iot.Hub.Mqtt.HttpApi.csproj" />
  </ItemGroup>

配置 MqttServer

代碼清單:Artizan.Iot.Hub.Mqtt.HttpApi.Host/ArtizanIotHubMqttHttpApiHostModule.cs

namespace Artizan.Iot.Hub.Mqtt.HttpApi.Host
{
    [DependsOn(
        typeof(AbpAutofacModule),
        typeof(AbpAspNetCoreSerilogModule),
        typeof(AbpSwashbuckleModule),
        typeof(ArtizanIotHubMqttApplicationModule),
        typeof(ArtizanIotHubMqttHttpApiModule),
        typeof(ArtizanIotHubMqttAspNetCoreModule)
    )]
    public class ArtizanIotHubMqttHttpApiHostModule : AbpModule
    {
          ...
        public override void ConfigureServices(ServiceConfigurationContext context)
        {
          ...
          ConfigureIotHubMqttServer(context);
        }

        private void ConfigureIotHubMqttServer(ServiceConfigurationContext context)
        {
            context.Services.AddIotHubMqttServer(builder =>
            {
                builder.WithDefaultEndpoint();
            });
        }

        public override void OnApplicationInitialization(ApplicationInitializationContext context)
        {
            app.UseRouting();
            ...
            app.UseIotHubMqttServer();
            ...
        }
   }

其中,

擴展方法:AddIotHubMqttServer

AddIotHubMqttServer()是自定義擴展方法:
代碼清單:Artizan.Iot.Hub.Mqtt.HttpApi.Host/Extensions/IotHubMqttServerExtensions.cs

using Artizan.Iot.Hub.Mqtt.AspNetCore.Servser;
using MQTTnet.AspNetCore;

namespace Artizan.Iot.Hub.Mqtt.HttpApi.Host.Extensions
{
    public static class IotHubMqttServerExtensions
    {
        /// <summary>
        /// MQTTnet 集成 AspNetCore:
        ///    https://github.com/dotnet/MQTTnet/wiki/Server#validating-mqtt-clients
        /// </summary>
        public static void AddIotHubMqttServer(this IServiceCollection services,
            Action<AspNetMqttServerOptionsBuilder> configure)
        {
            services.AddHostedMqttServerWithServices(builder =>
            {
                configure(builder);
            });
            services.AddMqttConnectionHandler();
            services.AddConnections();
        }
    }
}

其中,
services.AddHostedMqttServerWithServicesMQTTnet框架內置擴展方法。

擴展方法:UseIotHubMqttServer()

在模塊ArtizanIotHubMqttHttpApiHostModule的如下方法中調用擴展方法app.UseIotHubMqttServer()
代碼清單:Artizan.Iot.Hub.Mqtt.HttpApi.Host/ArtizanIotHubMqttHttpApiHostModule.cs

    public class ArtizanIotHubMqttHttpApiHostModule : AbpModule
    {
        ...
        public override void OnApplicationInitialization(ApplicationInitializationContext context)
        {
            app.UseRouting();
            ...
            app.UseIotHubMqttServer();
            ...
        }

代碼清單:Artizan.Iot.Hub.Mqtt.HttpApi.Host/Extensions/IotHubMqttServerExtensions.cs

        public static void UseIotHubMqttServer(this IApplicationBuilder app)
        {
            app.UseMqttServer(mqttServer =>
            {
                app.ApplicationServices.GetRequiredService<IIotHubMqttServer>()
                  .ConfigureMqttServer(mqttServer);
            });
        }

初始化 MQTTnet的 MqttServer對象

其中方法 MQTTnet 庫的方法UseMqttServer(mqttServer =>{...})

       app.UseMqttServer(mqttServer =>
            {
                app.ApplicationServices.GetRequiredService<IIotHubMqttServer>()
                  .ConfigureMqttServer(mqttServer);
            });

內部調用 IotHubMqttServerConfigureMqttServer()方法:

    public class IotHubMqttServer : IIotHubMqttServer, ISingletonDependency
    {
        private readonly IMqttServerService _mqttServerService;


        public void ConfigureMqttServer(MqttServer mqttServer)
        {
            MqttServer = mqttServer;
            _mqttServerService.ConfigureMqttService(mqttServer);
        }
    }

然後調用:MqttServerServiceConfigureMqttService()方法

    public class MqttServerService: IMqttServerService, ITransientDependency
    { 
        public void ConfigureMqttService(MqttServer mqttServer)
        {
            MqttServer =  mqttServer;

            _mqttConnectionService.ConfigureMqttServer(mqttServer);
            _mqttPublishingService.ConfigureMqttServer(mqttServer);
            _mqttSubscriptionService.ConfigureMqttServer(mqttServer);
            _mqttInternalService.ConfigureMqttServer(mqttServer);
        }

    
    }

最終調用

MqttConnectionService
    public override void ConfigureMqttServer(MqttServer mqttServer)
        {
            base.ConfigureMqttServer(mqttServer);

            MqttServer.ValidatingConnectionAsync += ValidatingConnectionHandlerAsync; ;
            MqttServer.ClientConnectedAsync += ClientConnectedHandlerAsync;
            MqttServer.ClientDisconnectedAsync += ClientDisconnectedHandlerAsync;
        }
MqttPublishingService
    public override void ConfigureMqttServer(MqttServer mqttServer)
        {
            base.ConfigureMqttServer(mqttServer);

           MqttServer.InterceptingPublishAsync += InterceptingPublishHandlerAsync;
      }
MqttSubscriptionService
    public override void ConfigureMqttServer(MqttServer mqttServer)
        {
            base.ConfigureMqttServer(mqttServer);

            MqttServer.ClientSubscribedTopicAsync += ClientSubscribedTopicHandlerAsync;
            MqttServer.InterceptingSubscriptionAsync += InterceptingSubscriptionHandlerAsync;
            MqttServer.ClientUnsubscribedTopicAsync += ClientUnsubscribedTopicHandlerAsync;
            MqttServer.InterceptingUnsubscriptionAsync += InterceptingUnsubscriptionHandlerAsync;
      }

這樣就把MQTTnetMqttServer對象的各個事件處理器註冊成功了

設置 MQTT終結點

代碼清單:Artizan.Iot.Hub.Mqtt.HttpApi.Host/Extensions/IotHubMqttServerExtensions.cs

        public static void UseIotHubMqttServer(this IApplicationBuilder app)
        {
            ...
            app.UseEndpoints(endpoint =>
            {
                // endpoint.MapMqtt("/mqtt");
                endpoint.MapConnectionHandler<MqttConnectionHandler>(
                    "/mqtt", // 設置MQTT的訪問地址: localhost:端口/mqtt
                    httpConnectionDispatcherOptions => httpConnectionDispatcherOptions.WebSockets.SubProtocolSelector =
                        protocolList => protocolList.FirstOrDefault() ?? string.Empty); // MQTT 支持 HTTP WebSockets
            });
        }

設置 MQTT的終結點,並讓其 MQTT 支持 HTTP WebSockets 支持。

Program.cs

代碼清單:Artizan.Iot.Hub.Mqtt.HttpApi.Host/Program.cs

using Artizan.Iot.Hub.Mqtt.HttpApi.Host;
using MQTTnet.AspNetCore;
using Serilog;
using Serilog.Events;

namespace AbpManual.BookStore;

public class Program
{
    public async static Task<int> Main(string[] args)
    {
        Log.Logger = new LoggerConfiguration()
#if DEBUG
            .MinimumLevel.Debug()
#else
            .MinimumLevel.Information()
#endif
            .MinimumLevel.Override("Microsoft", LogEventLevel.Information)
            .MinimumLevel.Override("Microsoft.EntityFrameworkCore", LogEventLevel.Warning)
            .Enrich.FromLogContext()
            .WriteTo.Async(c => c.File("Logs/logs.txt"))
            .WriteTo.Async(c => c.Console())
            .CreateLogger();

        try
        {
            Log.Information("Starting Artizan-IotHub HttpApi Host.");
            var builder = WebApplication.CreateBuilder(args);
            builder.Host.AddAppSettingsSecretsJson()
                .UseAutofac()
                .UseSerilog();

            builder.WebHost.ConfigureKestrel(serverOptions =>
            {
                // This will allow MQTT connections based on TCP port 1883.
                serverOptions.ListenAnyIP(2883, opts => opts.UseMqtt());

                // This will allow MQTT connections based on HTTP WebSockets with URI "localhost:5883/mqtt"
                // See code below for URI configuration.
                serverOptions.ListenAnyIP(5883); // Default HTTP pipeline
            });

            await builder.AddApplicationAsync<ArtizanIotHubMqttHttpApiHostModule>();
            var app = builder.Build();
            await app.InitializeApplicationAsync();
            await app.RunAsync();
            return 0;
        }
        catch (Exception ex)
        {
            Log.Fatal(ex, "Host terminated unexpectedly!");
            return 1;
        }
        finally
        {
            Log.CloseAndFlush();
        }
    }
}

其中,設置 MQTT的監聽端口

          builder.WebHost.ConfigureKestrel(serverOptions =>
            {
                // This will allow MQTT connections based on TCP port 2883.
                serverOptions.ListenAnyIP(2883, opts => opts.UseMqtt());

                // This will allow MQTT connections based on HTTP WebSockets with URI "localhost:5883/mqtt"
                // See code below for URI configuration.
                serverOptions.ListenAnyIP(5883); // Default HTTP pipeline
            });

普通的TCP端口使用:2883,訪問URL:localhost:2883/mqtt,
HTTP WebSockets端口:5883,訪問URL:localhost:5883/mqtt

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章