前言
閱讀前,先了解下 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
,用於彙總並一次性調用用於初始化 MQTTnet
的 MqttServer
對象的所有服務接口:
IMqttConnectionService
IMqttPublishingService
IMqttSubscriptionService
,
目的是實現內部封裝,調用者就不用關心瞭解 MQTTnet
的 MqttServer
對象初始化細節。
在項目【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()
內對 MQTTnet
的 MqttServer
對象相關初始化進行調用。
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()
的方法,對 MQTTnet
的 MqttServer
所有的初始化服務一次性調用。
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.AddHostedMqttServerWithServices
的 MQTTnet
框架內置擴展方法。
擴展方法: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);
});
內部調用 IotHubMqttServer
的 ConfigureMqttServer()
方法:
public class IotHubMqttServer : IIotHubMqttServer, ISingletonDependency
{
private readonly IMqttServerService _mqttServerService;
public void ConfigureMqttServer(MqttServer mqttServer)
{
MqttServer = mqttServer;
_mqttServerService.ConfigureMqttService(mqttServer);
}
}
然後調用:MqttServerService
的 ConfigureMqttService()
方法
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;
}
這樣就把MQTTnet
的 MqttServer
對象的各個事件處理器註冊成功了
設置 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