@@ -0,0 +1,18 @@ | |||||
<Project Sdk="Microsoft.NET.Sdk"> | |||||
<PropertyGroup> | |||||
<TargetFramework>netcoreapp3.1</TargetFramework> | |||||
<VersionPrefix>1.0.2</VersionPrefix> | |||||
<PackageReleaseNotes /> | |||||
</PropertyGroup> | |||||
<ItemGroup> | |||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="6.0.0" /> | |||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.0" /> | |||||
<PackageReference Include="Nuget.Tools.V2" Version="1.1.6"> | |||||
<PrivateAssets>all</PrivateAssets> | |||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | |||||
</PackageReference> | |||||
</ItemGroup> | |||||
<ItemGroup> | |||||
<ProjectReference Include="..\Source\MQTTnet\MQTTnet.csproj" /> | |||||
</ItemGroup> | |||||
</Project> |
@@ -0,0 +1,16 @@ | |||||
<Project Sdk="Microsoft.NET.Sdk"> | |||||
<PropertyGroup> | |||||
<TargetFramework>netcoreapp3.1</TargetFramework> | |||||
<VersionPrefix>1.0.3</VersionPrefix> | |||||
<PackageReleaseNotes /> | |||||
</PropertyGroup> | |||||
<ItemGroup> | |||||
<PackageReference Include="BPA.MQTTnet" Version="1.0.3" /> | |||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="6.0.0" /> | |||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.0" /> | |||||
<PackageReference Include="Nuget.Tools.V2" Version="1.1.6"> | |||||
<PrivateAssets>all</PrivateAssets> | |||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | |||||
</PackageReference> | |||||
</ItemGroup> | |||||
</Project> |
@@ -0,0 +1,104 @@ | |||||
using BPA.MQTTnet.Extensions.Settings; | |||||
using Microsoft.Extensions.Configuration; | |||||
using Microsoft.Extensions.DependencyInjection; | |||||
using MQTTnet; | |||||
using MQTTnet.Client; | |||||
using MQTTnet.Client.Connecting; | |||||
using MQTTnet.Client.Disconnecting; | |||||
using MQTTnet.Client.Options; | |||||
using System; | |||||
using System.Threading.Tasks; | |||||
namespace BPA.MQTTnet.Extensions.Extensions | |||||
{ | |||||
public static class ServiceCollectionExtension | |||||
{ | |||||
public static IServiceCollection AddMqttClientHostedService(this IServiceCollection services, IConfiguration configuration) | |||||
{ | |||||
ClientSettings clientSettings = new ClientSettings(); | |||||
configuration.GetSection(nameof(ClientSettings)).Bind(clientSettings); | |||||
services.AddSingleton(clientSettings); | |||||
BrokerHostSettings brokerHostSettings = new BrokerHostSettings(); | |||||
configuration.GetSection(nameof(BrokerHostSettings)).Bind(brokerHostSettings); | |||||
services.AddSingleton(brokerHostSettings); | |||||
services.AddMqttClientServiceWithConfig(); | |||||
//services.AddMqttClientServiceWithConfig(aspOptionBuilder => | |||||
//{ | |||||
// aspOptionBuilder | |||||
// .WithCredentials(clientSettings.UserName, clientSettings.Password) | |||||
// .WithClientId(clientSettings.Id) | |||||
// .WithTcpServer(brokerHostSettings.Host, brokerHostSettings.Port); | |||||
//}); | |||||
return services; | |||||
} | |||||
private static IServiceCollection AddMqttClientServiceWithConfig(this IServiceCollection services) | |||||
{ | |||||
var clientSettinigs = services.BuildServiceProvider().GetService<ClientSettings>(); | |||||
var brokerHostSettings = services.BuildServiceProvider().GetService<BrokerHostSettings>(); | |||||
//services.AddSingleton(serviceProvider => | |||||
//{ | |||||
// var optionBuilder = new AspCoreMqttClientOptionBuilder(serviceProvider); | |||||
// configure(new MqttClientOptionsBuilder()); | |||||
// return optionBuilder.Build(); | |||||
//}); | |||||
services.AddSingleton(serviceProvider => | |||||
{ | |||||
var clientOptions = new MqttClientOptionsBuilder() | |||||
.WithTcpServer(brokerHostSettings.Host, brokerHostSettings.Port) | |||||
.WithCredentials(clientSettinigs.UserName, clientSettinigs.Password) | |||||
.WithClientId(clientSettinigs.Id) | |||||
.Build(); | |||||
return clientOptions; | |||||
}); | |||||
services.AddSingleton(serviceProvider => | |||||
{ | |||||
var factory = new MqttFactory(); | |||||
var client = factory.CreateMqttClient(); | |||||
client.DisconnectedHandler = new MqttClientDisconnectedHandlerDelegate(async e => | |||||
{ | |||||
Console.WriteLine("### MQTT 从服务端断开连接 ###"); | |||||
await Task.Delay(TimeSpan.FromSeconds(5)); | |||||
try | |||||
{ | |||||
await client.ConnectAsync(serviceProvider.GetService<IMqttClientOptions>()); | |||||
} | |||||
catch | |||||
{ | |||||
Console.WriteLine("### MQTT 重连失败 ###"); | |||||
} | |||||
}); | |||||
client.ConnectedHandler = new MqttClientConnectedHandlerDelegate(async e => | |||||
{ | |||||
Console.WriteLine("### MQTT 连接服务端 ###"); | |||||
await client.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build()); | |||||
Console.WriteLine("### MQTT 连接成功 ###"); | |||||
}); | |||||
Task.WaitAny(client.ConnectAsync(serviceProvider.GetService<IMqttClientOptions>())); | |||||
return client; | |||||
}); | |||||
//service | |||||
// | |||||
//s.AddSingleton<MqttClientService>(); | |||||
//services.AddSingleton<IHostedService>(serviceProvider => | |||||
//{ | |||||
// return serviceProvider.GetService<MqttClientService>(); | |||||
//}); | |||||
//services.AddSingleton<MqttClientServiceProvider>(serviceProvider => | |||||
//{ | |||||
// var mqttClientService = serviceProvider.GetService<MqttClientService>(); | |||||
// var mqttClientServiceProvider = new MqttClientServiceProvider(mqttClientService); | |||||
// return mqttClientServiceProvider; | |||||
//}); | |||||
return services; | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,13 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Threading.Tasks; | |||||
namespace BPA.MQTTnet.Extensions.Settings | |||||
{ | |||||
public class BrokerHostSettings | |||||
{ | |||||
public string Host { set; get; } | |||||
public int Port { set; get; } | |||||
} | |||||
} |
@@ -0,0 +1,14 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Threading.Tasks; | |||||
namespace BPA.MQTTnet.Extensions.Settings | |||||
{ | |||||
public class ClientSettings | |||||
{ | |||||
public string Id { set; get; } | |||||
public string UserName { set; get; } | |||||
public string Password { set; get; } | |||||
} | |||||
} |
@@ -0,0 +1,83 @@ | |||||
using Microsoft.Extensions.Configuration; | |||||
using Microsoft.Extensions.DependencyInjection; | |||||
using MQTTnet.client.Test.Options; | |||||
using MQTTnet.client.Test.Settings; | |||||
using MQTTnet.Client; | |||||
using MQTTnet.Client.Connecting; | |||||
using MQTTnet.Client.Disconnecting; | |||||
using MQTTnet.Client.Options; | |||||
using System; | |||||
using System.Threading.Tasks; | |||||
namespace MQTTnet.client.Test.Extensions | |||||
{ | |||||
public static class ServiceCollectionExtension | |||||
{ | |||||
public static IServiceCollection AddMqttClientHostedService(this IServiceCollection services, IConfiguration configuration) | |||||
{ | |||||
ClientSettings clientSettings = new ClientSettings(); | |||||
configuration.GetSection(nameof(ClientSettings)).Bind(clientSettings); | |||||
services.AddSingleton(clientSettings); | |||||
BrokerHostSettings brokerHostSettings = new BrokerHostSettings(); | |||||
configuration.GetSection(nameof(BrokerHostSettings)).Bind(brokerHostSettings); | |||||
services.AddSingleton(brokerHostSettings); | |||||
services.AddMqttClientServiceWithConfig( ); | |||||
return services; | |||||
} | |||||
private static IServiceCollection AddMqttClientServiceWithConfig(this IServiceCollection services) | |||||
{ | |||||
var clientSettinigs = services.BuildServiceProvider().GetService<ClientSettings>(); | |||||
var brokerHostSettings = services.BuildServiceProvider().GetService<BrokerHostSettings>(); | |||||
//services.AddSingleton(serviceProvider => | |||||
//{ | |||||
// var optionBuilder = new AspCoreMqttClientOptionBuilder(serviceProvider); | |||||
// configure(new MqttClientOptionsBuilder()); | |||||
// return optionBuilder.Build(); | |||||
//}); | |||||
services.AddSingleton(serviceProvider => | |||||
{ | |||||
var clientOptions = new MqttClientOptionsBuilder() | |||||
.WithTcpServer(brokerHostSettings.Host, brokerHostSettings.Port) | |||||
.WithCredentials(clientSettinigs.UserName, clientSettinigs.Password) | |||||
.WithClientId(clientSettinigs.Id) | |||||
.Build(); | |||||
return clientOptions; | |||||
}); | |||||
services.AddSingleton(serviceProvider => | |||||
{ | |||||
var factory = new MqttFactory(); | |||||
var client = factory.CreateMqttClient(); | |||||
client.DisconnectedHandler = new MqttClientDisconnectedHandlerDelegate(async e => | |||||
{ | |||||
Console.WriteLine("### MQTT 从服务端断开连接 ###"); | |||||
await Task.Delay(TimeSpan.FromSeconds(5)); | |||||
try | |||||
{ | |||||
await client.ConnectAsync(serviceProvider.GetService<IMqttClientOptions>()); | |||||
} | |||||
catch | |||||
{ | |||||
Console.WriteLine("### MQTT 重连失败 ###"); | |||||
} | |||||
}); | |||||
client.ConnectedHandler = new MqttClientConnectedHandlerDelegate(async e => | |||||
{ | |||||
Console.WriteLine("### MQTT 连接服务端 ###"); | |||||
await client.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build()); | |||||
Console.WriteLine("### MQTT 连接成功 ###"); | |||||
}); | |||||
Task.WaitAny(client.ConnectAsync(serviceProvider.GetService<IMqttClientOptions>())); | |||||
return client; | |||||
}); | |||||
return services; | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,11 @@ | |||||
<Project Sdk="Microsoft.NET.Sdk.Web"> | |||||
<PropertyGroup> | |||||
<TargetFramework>netcoreapp3.1</TargetFramework> | |||||
</PropertyGroup> | |||||
<ItemGroup> | |||||
<ProjectReference Include="..\Source\MQTTnet\MQTTnet.csproj" /> | |||||
</ItemGroup> | |||||
</Project> |
@@ -0,0 +1,433 @@ | |||||
using MQTTnet; | |||||
using MQTTnet.Client; | |||||
using MQTTnet.Client.Disconnecting; | |||||
using MQTTnet.Client.Options; | |||||
using MQTTnet.Protocol; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.IO; | |||||
using System.Text; | |||||
using System.Threading; | |||||
using System.Threading.Tasks; | |||||
namespace MQTT | |||||
{ | |||||
public class MqttClient | |||||
{ | |||||
#region Field Area | |||||
/// <summary> | |||||
/// Mqtt factory | |||||
/// </summary> | |||||
private MqttFactory _factory; | |||||
/// <summary> | |||||
/// Mqtt client | |||||
/// </summary> | |||||
private IMqttClient _mqttClient; | |||||
/// <summary> | |||||
/// Mqtt 配置信息 | |||||
/// </summary> | |||||
private MqttClientConfig _mqttClientConfig; | |||||
/// <summary> | |||||
/// Mqtt options | |||||
/// </summary> | |||||
private IMqttClientOptions _options; | |||||
#endregion | |||||
#region CTOR | |||||
/// <summary> | |||||
/// 默认启动IP为127.0.0.1 端口为1883 | |||||
/// </summary> | |||||
public MqttClient() | |||||
{ | |||||
_mqttClientConfig = new MqttClientConfig | |||||
{ | |||||
ServerIp = "10.2.1.21", | |||||
Port = 1883 | |||||
}; | |||||
Init(); | |||||
} | |||||
/// <summary> | |||||
/// 调用示例 | |||||
/// | |||||
/// //var client = new MqttClient(s => | |||||
/// { | |||||
/// s.Port = 1883; | |||||
/// s.ServerIp = "127.0.0.1"; | |||||
/// s.UserName = "mqtt-test"; | |||||
/// s.Password = "mqtt-test"; | |||||
/// s.ReciveMsgCallback = s => | |||||
/// { | |||||
/// Console.WriteLine(s.Payload_UTF8); | |||||
/// }; | |||||
/// }, true); | |||||
/// client.Subscribe("/TopicName/"); | |||||
/// </summary> | |||||
/// <param name="config">客户端配置信息</param> | |||||
/// <param name="autoStart">直接启动</param> | |||||
public MqttClient(Action<MqttClientConfig> config, bool autoStart = false) | |||||
{ | |||||
_mqttClientConfig = new MqttClientConfig(); | |||||
config(_mqttClientConfig); | |||||
Init(); | |||||
if (autoStart) | |||||
{ | |||||
Task.WaitAll( Start()); | |||||
} | |||||
} | |||||
#endregion | |||||
/// <summary> | |||||
/// 获取MqttClient实例 | |||||
/// | |||||
/// 调用示例 | |||||
/// //var client = MqttClient.Instance(s => | |||||
/// { | |||||
/// s.Port = 1883; | |||||
/// s.ServerIp = "127.0.0.1"; | |||||
/// s.UserName = "mqtt-test"; | |||||
/// s.Password = "mqtt-test"; | |||||
/// s.ReciveMsgCallback = s => | |||||
/// { | |||||
/// Console.WriteLine(s.Payload_UTF8); | |||||
/// }; | |||||
/// }, true); | |||||
/// client.Subscribe("/TopicName/"); | |||||
/// </summary> | |||||
/// <param name="config">客户端配置信息</param> | |||||
/// <param name="autoStart">直接启动</param> | |||||
/// <returns></returns> | |||||
public static MqttClient Instance(Action<MqttClientConfig> config, bool autoStart = false) | |||||
=> new MqttClient(config, autoStart); | |||||
/// <summary> | |||||
/// 初始化注册 | |||||
/// </summary> | |||||
private void Init() | |||||
{ | |||||
try | |||||
{ | |||||
_factory = new MqttFactory(); | |||||
_mqttClient = _factory.CreateMqttClient(); | |||||
_options = new MqttClientOptionsBuilder() | |||||
.WithTcpServer(_mqttClientConfig.ServerIp, _mqttClientConfig.Port) | |||||
.WithCredentials(_mqttClientConfig.UserName, _mqttClientConfig.Password) | |||||
.WithClientId(_mqttClientConfig.ClientId) | |||||
.Build(); | |||||
//消息回调 | |||||
_mqttClient.UseApplicationMessageReceivedHandler(ReciveMsg); | |||||
} | |||||
catch (Exception exp) | |||||
{ | |||||
if (_mqttClientConfig.Exception is null) | |||||
{ | |||||
throw exp; | |||||
} | |||||
_mqttClientConfig.Exception(exp); | |||||
} | |||||
} | |||||
#region 内部事件转换处理 | |||||
/// <summary> | |||||
/// 消息接收回调 | |||||
/// </summary> | |||||
/// <param name="e"></param> | |||||
/// <returns></returns> | |||||
private void ReciveMsg(MqttApplicationMessageReceivedEventArgs e) | |||||
{ | |||||
if (_mqttClientConfig.ReciveMsgCallback != null) | |||||
{ | |||||
_mqttClientConfig.ReciveMsgCallback(new MqttClientReciveMsg | |||||
{ | |||||
Topic = e.ApplicationMessage.Topic, | |||||
Payload_UTF8 = Encoding.UTF8.GetString(e.ApplicationMessage.Payload), | |||||
Payload = e.ApplicationMessage.Payload, | |||||
Qos = e.ApplicationMessage.QualityOfServiceLevel, | |||||
Retain = e.ApplicationMessage.Retain, | |||||
}); | |||||
} | |||||
} | |||||
/// <summary> | |||||
/// 订阅 | |||||
/// </summary> | |||||
/// <param name="topicName"></param> | |||||
public async void Subscribe(string topicName) | |||||
{ | |||||
topicName = topicName.Trim(); | |||||
if (string.IsNullOrEmpty(topicName)) | |||||
{ | |||||
throw new Exception("订阅主题不能为空!"); | |||||
} | |||||
if (!_mqttClient.IsConnected) | |||||
{ | |||||
throw new Exception("MQTT客户端尚未连接!请先启动连接"); | |||||
} | |||||
await _mqttClient.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic(topicName).Build()); | |||||
} | |||||
/// <summary> | |||||
/// 取消订阅 | |||||
/// </summary> | |||||
/// <param name="topicName"></param> | |||||
public async void Unsubscribe(string topicName) | |||||
{ | |||||
topicName = topicName.Trim(); | |||||
if (string.IsNullOrEmpty(topicName)) | |||||
{ | |||||
throw new Exception("订阅主题不能为空!"); | |||||
} | |||||
if (!_mqttClient.IsConnected) | |||||
{ | |||||
throw new Exception("MQTT客户端尚未连接!请先启动连接"); | |||||
} | |||||
await _mqttClient.UnsubscribeAsync(topicName); | |||||
} | |||||
/// <summary> | |||||
/// 重连机制 | |||||
/// </summary> | |||||
private void ReConnected() | |||||
{ | |||||
if (_mqttClient.IsConnected) | |||||
{ | |||||
return; | |||||
} | |||||
for (int i = 0; i < 10; i++) | |||||
{ | |||||
//重连机制 | |||||
_mqttClient.UseDisconnectedHandler(async e => | |||||
{ | |||||
try | |||||
{ | |||||
await Task.Delay(TimeSpan.FromSeconds(_mqttClientConfig.ReconneTime)); | |||||
await _mqttClient.ConnectAsync(_options); | |||||
return; | |||||
} | |||||
catch (Exception exp) | |||||
{ | |||||
if (_mqttClientConfig.Exception is null) | |||||
{ | |||||
throw exp; | |||||
} | |||||
_mqttClientConfig.Exception(exp); | |||||
} | |||||
}); | |||||
} | |||||
} | |||||
#endregion | |||||
#region 消息发送 | |||||
/// <summary> | |||||
/// 发送消息,含有重连机制,如掉线会自动重连 | |||||
/// </summary> | |||||
/// <param name="message"></param> | |||||
private async Task PublishAsync(string topicName, MqttApplicationMessageBuilder MessageBuilder, PublicQos qos = 0) | |||||
{ | |||||
string topic = topicName.Trim(); | |||||
if (string.IsNullOrEmpty(topic)) | |||||
{ | |||||
throw new Exception("主题不能为空!"); | |||||
} | |||||
ReConnected(); | |||||
MessageBuilder.WithTopic(topic).WithRetainFlag(); | |||||
if (qos == PublicQos.Qos_0) | |||||
{ | |||||
MessageBuilder.WithAtLeastOnceQoS(); | |||||
} | |||||
else if (qos == PublicQos.Qos_1) | |||||
{ | |||||
MessageBuilder.WithAtMostOnceQoS(); | |||||
} | |||||
else | |||||
{ | |||||
MessageBuilder.WithExactlyOnceQoS(); | |||||
} | |||||
var Message = MessageBuilder.Build(); | |||||
try | |||||
{ | |||||
await _mqttClient.PublishAsync(Message); | |||||
} | |||||
catch (Exception e) | |||||
{ | |||||
if (_mqttClientConfig.Exception is null) | |||||
{ | |||||
throw e; | |||||
} | |||||
_mqttClientConfig.Exception(e); | |||||
} | |||||
} | |||||
/// <summary> | |||||
/// 发送消息,含有重连机制,如掉线会自动重连 | |||||
/// </summary> | |||||
/// <param name="message">文字消息</param> | |||||
public async Task Publish(string topicName, string message, PublicQos qos = 0) | |||||
{ | |||||
await PublishAsync(topicName, new MqttApplicationMessageBuilder() | |||||
.WithPayload(message), qos); | |||||
} | |||||
/// <summary> | |||||
/// 发送消息,含有重连机制,如掉线会自动重连 | |||||
/// </summary> | |||||
/// <param name="message">消息流</param> | |||||
public async void Publish(string topicName, Stream message, PublicQos qos = 0) | |||||
=> await PublishAsync(topicName, new MqttApplicationMessageBuilder() | |||||
.WithPayload(message), qos); | |||||
/// <summary> | |||||
/// 发送消息,含有重连机制,如掉线会自动重连 | |||||
/// </summary> | |||||
/// <param name="message">Byte消息</param> | |||||
public async void Publish(string topicName, IEnumerable<byte> message, PublicQos qos = 0) | |||||
=> await PublishAsync(topicName, new MqttApplicationMessageBuilder() | |||||
.WithPayload(message), qos); | |||||
/// <summary> | |||||
/// 发送消息,含有重连机制,如掉线会自动重连 | |||||
/// </summary> | |||||
/// <param name="message">Byte消息</param> | |||||
public async void Publish(string topicName, byte[] message, PublicQos qos = 0) | |||||
=> await PublishAsync(topicName, new MqttApplicationMessageBuilder() | |||||
.WithPayload(message), qos); | |||||
#endregion | |||||
/// <summary> | |||||
/// 启动服务 | |||||
/// </summary> | |||||
/// <returns></returns> | |||||
public async Task Start() | |||||
=> await _mqttClient.ConnectAsync(_options); | |||||
/// <summary> | |||||
/// 停止服务 | |||||
/// </summary> | |||||
/// <returns></returns> | |||||
public async Task Stop() | |||||
=> await _mqttClient.DisconnectAsync(new MqttClientDisconnectOptions { ReasonCode = MqttClientDisconnectReason.NormalDisconnection }, CancellationToken.None); | |||||
} | |||||
public class MqttClientConfig | |||||
{ | |||||
private string _serverIp; | |||||
/// <summary> | |||||
/// 服务器IP | |||||
/// </summary> | |||||
public string ServerIp | |||||
{ | |||||
get => _serverIp; | |||||
set | |||||
{ | |||||
if (string.IsNullOrEmpty(value.Trim())) | |||||
{ | |||||
throw new ArgumentException("ServerIp can't be null or empty!"); | |||||
} | |||||
_serverIp = value; | |||||
} | |||||
} | |||||
private int _port; | |||||
/// <summary> | |||||
/// 服务器端口 | |||||
/// </summary> | |||||
public int Port | |||||
{ | |||||
get => _port; | |||||
set | |||||
{ | |||||
if (value <= 0) | |||||
{ | |||||
throw new ArgumentException("Port can't below the zero!"); | |||||
} | |||||
_port = value; | |||||
} | |||||
} | |||||
/// <summary> | |||||
/// 用户名 | |||||
/// </summary> | |||||
public string UserName { get; set; } | |||||
/// <summary> | |||||
/// 密码 | |||||
/// </summary> | |||||
public string Password { get; set; } | |||||
private string _clientId; | |||||
/// <summary> | |||||
/// 唯一用户ID,默认使用Guid | |||||
/// </summary> | |||||
public string ClientId | |||||
{ | |||||
get | |||||
{ | |||||
_clientId = _clientId ?? Guid.NewGuid().ToString(); | |||||
return _clientId; | |||||
} | |||||
set => _clientId = value; | |||||
} | |||||
/// <summary> | |||||
/// 客户端掉线重连时间,单位/s,默认5s | |||||
/// </summary> | |||||
public double ReconneTime { get; set; } = 5; | |||||
/// <summary> | |||||
/// 异常回调,默认为空,为空抛异常 | |||||
/// </summary> | |||||
public Action<Exception> Exception = null; | |||||
/// <summary> | |||||
/// 接收消息回调,默认不接收 | |||||
/// </summary> | |||||
public Action<MqttClientReciveMsg> ReciveMsgCallback = null; | |||||
} | |||||
public class MqttClientReciveMsg | |||||
{ | |||||
/// <summary> | |||||
/// 主题 | |||||
/// </summary> | |||||
public string Topic { get; set; } | |||||
/// <summary> | |||||
/// UTF-8格式下的 负载/消息 | |||||
/// </summary> | |||||
public string Payload_UTF8 { get; set; } | |||||
/// <summary> | |||||
/// 原始 负载/消息 | |||||
/// </summary> | |||||
public byte[] Payload { get; set; } | |||||
/// <summary> | |||||
/// Qos | |||||
/// </summary> | |||||
public MqttQualityOfServiceLevel Qos { get; set; } | |||||
/// <summary> | |||||
/// 保留 | |||||
/// </summary> | |||||
public bool Retain { get; set; } | |||||
} | |||||
public enum PublicQos | |||||
{ | |||||
/// <summary> | |||||
/// //At most once,至多一次 | |||||
/// </summary> | |||||
Qos_0, | |||||
/// <summary> | |||||
/// //At least once,至少一次 | |||||
/// </summary> | |||||
Qos_1, | |||||
/// <summary> | |||||
/// //QoS2,Exactly once | |||||
/// </summary> | |||||
Qos_2, | |||||
} | |||||
} |
@@ -0,0 +1,18 @@ | |||||
using MQTTnet.Client.Options; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Threading.Tasks; | |||||
namespace MQTTnet.client.Test.Options | |||||
{ | |||||
public class AspCoreMqttClientOptionBuilder : MqttClientOptionsBuilder | |||||
{ | |||||
public IServiceProvider ServiceProvider { get; } | |||||
public AspCoreMqttClientOptionBuilder(IServiceProvider serviceProvider) | |||||
{ | |||||
ServiceProvider = serviceProvider; | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,26 @@ | |||||
@page | |||||
@model ErrorModel | |||||
@{ | |||||
ViewData["Title"] = "Error"; | |||||
} | |||||
<h1 class="text-danger">Error.</h1> | |||||
<h2 class="text-danger">An error occurred while processing your request.</h2> | |||||
@if (Model.ShowRequestId) | |||||
{ | |||||
<p> | |||||
<strong>Request ID:</strong> <code>@Model.RequestId</code> | |||||
</p> | |||||
} | |||||
<h3>Development Mode</h3> | |||||
<p> | |||||
Swapping to the <strong>Development</strong> environment displays detailed information about the error that occurred. | |||||
</p> | |||||
<p> | |||||
<strong>The Development environment shouldn't be enabled for deployed applications.</strong> | |||||
It can result in displaying sensitive information from exceptions to end users. | |||||
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong> | |||||
and restarting the app. | |||||
</p> |
@@ -0,0 +1,31 @@ | |||||
using Microsoft.AspNetCore.Mvc; | |||||
using Microsoft.AspNetCore.Mvc.RazorPages; | |||||
using Microsoft.Extensions.Logging; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Diagnostics; | |||||
using System.Linq; | |||||
using System.Threading.Tasks; | |||||
namespace MQTTnet.client.Test.Pages | |||||
{ | |||||
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] | |||||
public class ErrorModel : PageModel | |||||
{ | |||||
public string RequestId { get; set; } | |||||
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); | |||||
private readonly ILogger<ErrorModel> _logger; | |||||
public ErrorModel(ILogger<ErrorModel> logger) | |||||
{ | |||||
_logger = logger; | |||||
} | |||||
public void OnGet() | |||||
{ | |||||
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,10 @@ | |||||
@page | |||||
@model IndexModel | |||||
@{ | |||||
ViewData["Title"] = "Home page"; | |||||
} | |||||
<div class="text-center"> | |||||
<h1 class="display-4">Welcome</h1> | |||||
<p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p> | |||||
</div> |
@@ -0,0 +1,78 @@ | |||||
using Microsoft.AspNetCore.Mvc; | |||||
using Microsoft.AspNetCore.Mvc.RazorPages; | |||||
using Microsoft.Extensions.Logging; | |||||
using MQTTnet.Client; | |||||
using MQTTnet.Client.Connecting; | |||||
using MQTTnet.Client.Disconnecting; | |||||
using MQTTnet.Client.Options; | |||||
using MQTTnet.Client.Receiving; | |||||
using MQTTnet.Protocol; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
namespace MQTTnet.client.Test.Pages | |||||
{ | |||||
public class IndexModel : PageModel | |||||
{ | |||||
private readonly ILogger<IndexModel> _logger; | |||||
private IMqttClient _mqttClient; | |||||
private IMqttClientOptions _mqttClientOptions; | |||||
public IndexModel(ILogger<IndexModel> logger) | |||||
{ | |||||
//, IMqttClient mqttClient, IMqttClientOptions mqttClientOptions | |||||
//_mqttClient =mqttClient; | |||||
//_mqttClientOptions=mqttClientOptions; | |||||
// Task.Run(RunAsync); | |||||
Task.Run(PublicAsync); | |||||
} | |||||
public async Task PublicAsync() | |||||
{ | |||||
var applicationMessage = new MqttApplicationMessageBuilder() | |||||
.WithTopic("test") | |||||
.WithPayload("Hello World") | |||||
.WithAtLeastOnceQoS() | |||||
.Build(); | |||||
await _mqttClient.PublishAsync(applicationMessage); | |||||
} | |||||
public async Task RunAsync() | |||||
{ | |||||
try | |||||
{ | |||||
IMqttClient client = _mqttClient; | |||||
client.ApplicationMessageReceivedHandler = new MqttApplicationMessageReceivedHandlerDelegate(e => | |||||
{ | |||||
Console.WriteLine("### RECEIVED APPLICATION MESSAGE ###"); | |||||
Console.WriteLine($"+ Topic = {e.ApplicationMessage.Topic}"); | |||||
Console.WriteLine($"+ Payload = {Encoding.UTF8.GetString(e.ApplicationMessage.Payload)}"); | |||||
Console.WriteLine($"+ QoS = {e.ApplicationMessage.QualityOfServiceLevel}"); | |||||
Console.WriteLine($"+ Retain = {e.ApplicationMessage.Retain}"); | |||||
// Console.WriteLine(); | |||||
}); | |||||
await client.SubscribeAsync(new MqttTopicFilter { Topic = "test", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce }); | |||||
await client.SubscribeAsync(new MqttTopicFilter { Topic = "6666", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce }); | |||||
} | |||||
catch (Exception exception) | |||||
{ | |||||
Console.WriteLine(exception); | |||||
} | |||||
} | |||||
public void OnGet() | |||||
{ | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,8 @@ | |||||
@page | |||||
@model PrivacyModel | |||||
@{ | |||||
ViewData["Title"] = "Privacy Policy"; | |||||
} | |||||
<h1>@ViewData["Title"]</h1> | |||||
<p>Use this page to detail your site's privacy policy.</p> |
@@ -0,0 +1,24 @@ | |||||
using Microsoft.AspNetCore.Mvc; | |||||
using Microsoft.AspNetCore.Mvc.RazorPages; | |||||
using Microsoft.Extensions.Logging; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Threading.Tasks; | |||||
namespace MQTTnet.client.Test.Pages | |||||
{ | |||||
public class PrivacyModel : PageModel | |||||
{ | |||||
private readonly ILogger<PrivacyModel> _logger; | |||||
public PrivacyModel(ILogger<PrivacyModel> logger) | |||||
{ | |||||
_logger = logger; | |||||
} | |||||
public void OnGet() | |||||
{ | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,50 @@ | |||||
<!DOCTYPE html> | |||||
<html lang="en"> | |||||
<head> | |||||
<meta charset="utf-8" /> | |||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |||||
<title>@ViewData["Title"] - MQTTnet.client.Test</title> | |||||
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" /> | |||||
<link rel="stylesheet" href="~/css/site.css" /> | |||||
</head> | |||||
<body> | |||||
<header> | |||||
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3"> | |||||
<div class="container"> | |||||
<a class="navbar-brand" asp-area="" asp-page="/Index">MQTTnet.client.Test</a> | |||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse" aria-controls="navbarSupportedContent" | |||||
aria-expanded="false" aria-label="Toggle navigation"> | |||||
<span class="navbar-toggler-icon"></span> | |||||
</button> | |||||
<div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse"> | |||||
<ul class="navbar-nav flex-grow-1"> | |||||
<li class="nav-item"> | |||||
<a class="nav-link text-dark" asp-area="" asp-page="/Index">Home</a> | |||||
</li> | |||||
<li class="nav-item"> | |||||
<a class="nav-link text-dark" asp-area="" asp-page="/Privacy">Privacy</a> | |||||
</li> | |||||
</ul> | |||||
</div> | |||||
</div> | |||||
</nav> | |||||
</header> | |||||
<div class="container"> | |||||
<main role="main" class="pb-3"> | |||||
@RenderBody() | |||||
</main> | |||||
</div> | |||||
<footer class="border-top footer text-muted"> | |||||
<div class="container"> | |||||
© 2022 - MQTTnet.client.Test - <a asp-area="" asp-page="/Privacy">Privacy</a> | |||||
</div> | |||||
</footer> | |||||
<script src="~/lib/jquery/dist/jquery.min.js"></script> | |||||
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script> | |||||
<script src="~/js/site.js" asp-append-version="true"></script> | |||||
@RenderSection("Scripts", required: false) | |||||
</body> | |||||
</html> |
@@ -0,0 +1,2 @@ | |||||
<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script> | |||||
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script> |
@@ -0,0 +1,3 @@ | |||||
@using MQTTnet.client.Test | |||||
@namespace MQTTnet.client.Test.Pages | |||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers |
@@ -0,0 +1,3 @@ | |||||
@{ | |||||
Layout = "_Layout"; | |||||
} |
@@ -0,0 +1,26 @@ | |||||
using Microsoft.AspNetCore.Hosting; | |||||
using Microsoft.Extensions.Configuration; | |||||
using Microsoft.Extensions.Hosting; | |||||
using Microsoft.Extensions.Logging; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Threading.Tasks; | |||||
namespace MQTTnet.client.Test | |||||
{ | |||||
public class Program | |||||
{ | |||||
public static void Main(string[] args) | |||||
{ | |||||
CreateHostBuilder(args).Build().Run(); | |||||
} | |||||
public static IHostBuilder CreateHostBuilder(string[] args) => | |||||
Host.CreateDefaultBuilder(args) | |||||
.ConfigureWebHostDefaults(webBuilder => | |||||
{ | |||||
webBuilder.UseStartup<Startup>(); | |||||
}); | |||||
} | |||||
} |
@@ -0,0 +1,13 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Threading.Tasks; | |||||
namespace MQTTnet.client.Test.Settings | |||||
{ | |||||
public class AppSettingsProvider | |||||
{ | |||||
public static BrokerHostSettings BrokerHostSettings; | |||||
public static ClientSettings ClientSettings; | |||||
} | |||||
} |
@@ -0,0 +1,13 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Threading.Tasks; | |||||
namespace MQTTnet.client.Test.Settings | |||||
{ | |||||
public class BrokerHostSettings | |||||
{ | |||||
public string Host { set; get; } | |||||
public int Port { set; get; } | |||||
} | |||||
} |
@@ -0,0 +1,14 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Threading.Tasks; | |||||
namespace MQTTnet.client.Test.Settings | |||||
{ | |||||
public class ClientSettings | |||||
{ | |||||
public string Id { set; get; } | |||||
public string UserName { set; get; } | |||||
public string Password { set; get; } | |||||
} | |||||
} |
@@ -0,0 +1,70 @@ | |||||
using Microsoft.AspNetCore.Builder; | |||||
using Microsoft.AspNetCore.Hosting; | |||||
using Microsoft.Extensions.Configuration; | |||||
using Microsoft.Extensions.DependencyInjection; | |||||
using Microsoft.Extensions.Hosting; | |||||
using MQTTnet.client.Test.Extensions; | |||||
using MQTTnet.client.Test.Settings; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Threading.Tasks; | |||||
namespace MQTTnet.client.Test | |||||
{ | |||||
public class Startup | |||||
{ | |||||
public Startup(IConfiguration configuration) | |||||
{ | |||||
Configuration = configuration; | |||||
MapBrokerHostSettings(); | |||||
MapClientSettings(); | |||||
} | |||||
public IConfiguration Configuration { get; } | |||||
// This method gets called by the runtime. Use this method to add services to the container. | |||||
public void ConfigureServices(IServiceCollection services) | |||||
{ | |||||
services.AddRazorPages(); | |||||
services.AddMqttClientHostedService(Configuration); | |||||
} | |||||
private void MapBrokerHostSettings() | |||||
{ | |||||
BrokerHostSettings brokerHostSettings = new BrokerHostSettings(); | |||||
Configuration.GetSection(nameof(BrokerHostSettings)).Bind(brokerHostSettings); | |||||
AppSettingsProvider.BrokerHostSettings = brokerHostSettings; | |||||
} | |||||
private void MapClientSettings() | |||||
{ | |||||
ClientSettings clientSettings = new ClientSettings(); | |||||
Configuration.GetSection(nameof(ClientSettings)).Bind(clientSettings); | |||||
AppSettingsProvider.ClientSettings = clientSettings; | |||||
} | |||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. | |||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) | |||||
{ | |||||
if (env.IsDevelopment()) | |||||
{ | |||||
app.UseDeveloperExceptionPage(); | |||||
} | |||||
else | |||||
{ | |||||
app.UseExceptionHandler("/Error"); | |||||
} | |||||
app.UseStaticFiles(); | |||||
app.UseRouting(); | |||||
app.UseAuthorization(); | |||||
app.UseEndpoints(endpoints => | |||||
{ | |||||
endpoints.MapRazorPages(); | |||||
}); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,9 @@ | |||||
{ | |||||
"Logging": { | |||||
"LogLevel": { | |||||
"Default": "Information", | |||||
"Microsoft": "Warning", | |||||
"Microsoft.Hosting.Lifetime": "Information" | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,20 @@ | |||||
{ | |||||
"Logging": { | |||||
"LogLevel": { | |||||
"Default": "Information", | |||||
"Microsoft": "Warning", | |||||
"Microsoft.Hosting.Lifetime": "Information" | |||||
} | |||||
}, | |||||
"AllowedHosts": "*", | |||||
"BrokerHostSettings": { | |||||
"Host": "10.2.1.21", | |||||
"Port": 1883 | |||||
}, | |||||
"ClientSettings": { | |||||
"Id": "5eb020f043ba8930506acbdd", | |||||
"UserName": "rafiul", | |||||
"Password": "12345678" | |||||
} | |||||
} |
@@ -0,0 +1,71 @@ | |||||
/* Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification | |||||
for details on configuring this project to bundle and minify static web assets. */ | |||||
a.navbar-brand { | |||||
white-space: normal; | |||||
text-align: center; | |||||
word-break: break-all; | |||||
} | |||||
/* Provide sufficient contrast against white background */ | |||||
a { | |||||
color: #0366d6; | |||||
} | |||||
.btn-primary { | |||||
color: #fff; | |||||
background-color: #1b6ec2; | |||||
border-color: #1861ac; | |||||
} | |||||
.nav-pills .nav-link.active, .nav-pills .show > .nav-link { | |||||
color: #fff; | |||||
background-color: #1b6ec2; | |||||
border-color: #1861ac; | |||||
} | |||||
/* Sticky footer styles | |||||
-------------------------------------------------- */ | |||||
html { | |||||
font-size: 14px; | |||||
} | |||||
@media (min-width: 768px) { | |||||
html { | |||||
font-size: 16px; | |||||
} | |||||
} | |||||
.border-top { | |||||
border-top: 1px solid #e5e5e5; | |||||
} | |||||
.border-bottom { | |||||
border-bottom: 1px solid #e5e5e5; | |||||
} | |||||
.box-shadow { | |||||
box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05); | |||||
} | |||||
button.accept-policy { | |||||
font-size: 1rem; | |||||
line-height: inherit; | |||||
} | |||||
/* Sticky footer styles | |||||
-------------------------------------------------- */ | |||||
html { | |||||
position: relative; | |||||
min-height: 100%; | |||||
} | |||||
body { | |||||
/* Margin bottom by footer height */ | |||||
margin-bottom: 60px; | |||||
} | |||||
.footer { | |||||
position: absolute; | |||||
bottom: 0; | |||||
width: 100%; | |||||
white-space: nowrap; | |||||
line-height: 60px; /* Vertically center the text there */ | |||||
} |
@@ -0,0 +1,22 @@ | |||||
The MIT License (MIT) | |||||
Copyright (c) 2011-2018 Twitter, Inc. | |||||
Copyright (c) 2011-2018 The Bootstrap Authors | |||||
Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
of this software and associated documentation files (the "Software"), to deal | |||||
in the Software without restriction, including without limitation the rights | |||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
copies of the Software, and to permit persons to whom the Software is | |||||
furnished to do so, subject to the following conditions: | |||||
The above copyright notice and this permission notice shall be included in | |||||
all copies or substantial portions of the Software. | |||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
THE SOFTWARE. |
@@ -0,0 +1,331 @@ | |||||
/*! | |||||
* Bootstrap Reboot v4.3.1 (https://getbootstrap.com/) | |||||
* Copyright 2011-2019 The Bootstrap Authors | |||||
* Copyright 2011-2019 Twitter, Inc. | |||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) | |||||
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md) | |||||
*/ | |||||
*, | |||||
*::before, | |||||
*::after { | |||||
box-sizing: border-box; | |||||
} | |||||
html { | |||||
font-family: sans-serif; | |||||
line-height: 1.15; | |||||
-webkit-text-size-adjust: 100%; | |||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0); | |||||
} | |||||
article, aside, figcaption, figure, footer, header, hgroup, main, nav, section { | |||||
display: block; | |||||
} | |||||
body { | |||||
margin: 0; | |||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; | |||||
font-size: 1rem; | |||||
font-weight: 400; | |||||
line-height: 1.5; | |||||
color: #212529; | |||||
text-align: left; | |||||
background-color: #fff; | |||||
} | |||||
[tabindex="-1"]:focus { | |||||
outline: 0 !important; | |||||
} | |||||
hr { | |||||
box-sizing: content-box; | |||||
height: 0; | |||||
overflow: visible; | |||||
} | |||||
h1, h2, h3, h4, h5, h6 { | |||||
margin-top: 0; | |||||
margin-bottom: 0.5rem; | |||||
} | |||||
p { | |||||
margin-top: 0; | |||||
margin-bottom: 1rem; | |||||
} | |||||
abbr[title], | |||||
abbr[data-original-title] { | |||||
text-decoration: underline; | |||||
-webkit-text-decoration: underline dotted; | |||||
text-decoration: underline dotted; | |||||
cursor: help; | |||||
border-bottom: 0; | |||||
-webkit-text-decoration-skip-ink: none; | |||||
text-decoration-skip-ink: none; | |||||
} | |||||
address { | |||||
margin-bottom: 1rem; | |||||
font-style: normal; | |||||
line-height: inherit; | |||||
} | |||||
ol, | |||||
ul, | |||||
dl { | |||||
margin-top: 0; | |||||
margin-bottom: 1rem; | |||||
} | |||||
ol ol, | |||||
ul ul, | |||||
ol ul, | |||||
ul ol { | |||||
margin-bottom: 0; | |||||
} | |||||
dt { | |||||
font-weight: 700; | |||||
} | |||||
dd { | |||||
margin-bottom: .5rem; | |||||
margin-left: 0; | |||||
} | |||||
blockquote { | |||||
margin: 0 0 1rem; | |||||
} | |||||
b, | |||||
strong { | |||||
font-weight: bolder; | |||||
} | |||||
small { | |||||
font-size: 80%; | |||||
} | |||||
sub, | |||||
sup { | |||||
position: relative; | |||||
font-size: 75%; | |||||
line-height: 0; | |||||
vertical-align: baseline; | |||||
} | |||||
sub { | |||||
bottom: -.25em; | |||||
} | |||||
sup { | |||||
top: -.5em; | |||||
} | |||||
a { | |||||
color: #007bff; | |||||
text-decoration: none; | |||||
background-color: transparent; | |||||
} | |||||
a:hover { | |||||
color: #0056b3; | |||||
text-decoration: underline; | |||||
} | |||||
a:not([href]):not([tabindex]) { | |||||
color: inherit; | |||||
text-decoration: none; | |||||
} | |||||
a:not([href]):not([tabindex]):hover, a:not([href]):not([tabindex]):focus { | |||||
color: inherit; | |||||
text-decoration: none; | |||||
} | |||||
a:not([href]):not([tabindex]):focus { | |||||
outline: 0; | |||||
} | |||||
pre, | |||||
code, | |||||
kbd, | |||||
samp { | |||||
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; | |||||
font-size: 1em; | |||||
} | |||||
pre { | |||||
margin-top: 0; | |||||
margin-bottom: 1rem; | |||||
overflow: auto; | |||||
} | |||||
figure { | |||||
margin: 0 0 1rem; | |||||
} | |||||
img { | |||||
vertical-align: middle; | |||||
border-style: none; | |||||
} | |||||
svg { | |||||
overflow: hidden; | |||||
vertical-align: middle; | |||||
} | |||||
table { | |||||
border-collapse: collapse; | |||||
} | |||||
caption { | |||||
padding-top: 0.75rem; | |||||
padding-bottom: 0.75rem; | |||||
color: #6c757d; | |||||
text-align: left; | |||||
caption-side: bottom; | |||||
} | |||||
th { | |||||
text-align: inherit; | |||||
} | |||||
label { | |||||
display: inline-block; | |||||
margin-bottom: 0.5rem; | |||||
} | |||||
button { | |||||
border-radius: 0; | |||||
} | |||||
button:focus { | |||||
outline: 1px dotted; | |||||
outline: 5px auto -webkit-focus-ring-color; | |||||
} | |||||
input, | |||||
button, | |||||
select, | |||||
optgroup, | |||||
textarea { | |||||
margin: 0; | |||||
font-family: inherit; | |||||
font-size: inherit; | |||||
line-height: inherit; | |||||
} | |||||
button, | |||||
input { | |||||
overflow: visible; | |||||
} | |||||
button, | |||||
select { | |||||
text-transform: none; | |||||
} | |||||
select { | |||||
word-wrap: normal; | |||||
} | |||||
button, | |||||
[type="button"], | |||||
[type="reset"], | |||||
[type="submit"] { | |||||
-webkit-appearance: button; | |||||
} | |||||
button:not(:disabled), | |||||
[type="button"]:not(:disabled), | |||||
[type="reset"]:not(:disabled), | |||||
[type="submit"]:not(:disabled) { | |||||
cursor: pointer; | |||||
} | |||||
button::-moz-focus-inner, | |||||
[type="button"]::-moz-focus-inner, | |||||
[type="reset"]::-moz-focus-inner, | |||||
[type="submit"]::-moz-focus-inner { | |||||
padding: 0; | |||||
border-style: none; | |||||
} | |||||
input[type="radio"], | |||||
input[type="checkbox"] { | |||||
box-sizing: border-box; | |||||
padding: 0; | |||||
} | |||||
input[type="date"], | |||||
input[type="time"], | |||||
input[type="datetime-local"], | |||||
input[type="month"] { | |||||
-webkit-appearance: listbox; | |||||
} | |||||
textarea { | |||||
overflow: auto; | |||||
resize: vertical; | |||||
} | |||||
fieldset { | |||||
min-width: 0; | |||||
padding: 0; | |||||
margin: 0; | |||||
border: 0; | |||||
} | |||||
legend { | |||||
display: block; | |||||
width: 100%; | |||||
max-width: 100%; | |||||
padding: 0; | |||||
margin-bottom: .5rem; | |||||
font-size: 1.5rem; | |||||
line-height: inherit; | |||||
color: inherit; | |||||
white-space: normal; | |||||
} | |||||
progress { | |||||
vertical-align: baseline; | |||||
} | |||||
[type="number"]::-webkit-inner-spin-button, | |||||
[type="number"]::-webkit-outer-spin-button { | |||||
height: auto; | |||||
} | |||||
[type="search"] { | |||||
outline-offset: -2px; | |||||
-webkit-appearance: none; | |||||
} | |||||
[type="search"]::-webkit-search-decoration { | |||||
-webkit-appearance: none; | |||||
} | |||||
::-webkit-file-upload-button { | |||||
font: inherit; | |||||
-webkit-appearance: button; | |||||
} | |||||
output { | |||||
display: inline-block; | |||||
} | |||||
summary { | |||||
display: list-item; | |||||
cursor: pointer; | |||||
} | |||||
template { | |||||
display: none; | |||||
} | |||||
[hidden] { | |||||
display: none !important; | |||||
} | |||||
/*# sourceMappingURL=bootstrap-reboot.css.map */ |
@@ -0,0 +1,8 @@ | |||||
/*! | |||||
* Bootstrap Reboot v4.3.1 (https://getbootstrap.com/) | |||||
* Copyright 2011-2019 The Bootstrap Authors | |||||
* Copyright 2011-2019 Twitter, Inc. | |||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) | |||||
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md) | |||||
*/*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus{outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([tabindex]){color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus,a:not([href]):not([tabindex]):hover{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=date],input[type=datetime-local],input[type=month],input[type=time]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important} | |||||
/*# sourceMappingURL=bootstrap-reboot.min.css.map */ |
@@ -0,0 +1,12 @@ | |||||
Copyright (c) .NET Foundation. All rights reserved. | |||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use | |||||
these files except in compliance with the License. You may obtain a copy of the | |||||
License at | |||||
http://www.apache.org/licenses/LICENSE-2.0 | |||||
Unless required by applicable law or agreed to in writing, software distributed | |||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR | |||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the | |||||
specific language governing permissions and limitations under the License. |
@@ -0,0 +1,22 @@ | |||||
The MIT License (MIT) | |||||
===================== | |||||
Copyright Jörn Zaefferer | |||||
Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
of this software and associated documentation files (the "Software"), to deal | |||||
in the Software without restriction, including without limitation the rights | |||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
copies of the Software, and to permit persons to whom the Software is | |||||
furnished to do so, subject to the following conditions: | |||||
The above copyright notice and this permission notice shall be included in | |||||
all copies or substantial portions of the Software. | |||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
THE SOFTWARE. |
@@ -0,0 +1,36 @@ | |||||
Copyright JS Foundation and other contributors, https://js.foundation/ | |||||
This software consists of voluntary contributions made by many | |||||
individuals. For exact contribution history, see the revision history | |||||
available at https://github.com/jquery/jquery | |||||
The following license applies to all parts of this software except as | |||||
documented below: | |||||
==== | |||||
Permission is hereby granted, free of charge, to any person obtaining | |||||
a copy of this software and associated documentation files (the | |||||
"Software"), to deal in the Software without restriction, including | |||||
without limitation the rights to use, copy, modify, merge, publish, | |||||
distribute, sublicense, and/or sell copies of the Software, and to | |||||
permit persons to whom the Software is furnished to do so, subject to | |||||
the following conditions: | |||||
The above copyright notice and this permission notice shall be | |||||
included in all copies or substantial portions of the Software. | |||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | |||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | |||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | |||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |||||
==== | |||||
All files located in the node_modules and external directories are | |||||
externally maintained libraries used by this software which have their | |||||
own licenses; we recommend you read them, as their terms may differ from | |||||
the terms above. |
@@ -57,6 +57,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Test.BlazorApp.Clie | |||||
EndProject | EndProject | ||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Test.BlazorApp.Shared", "Tests\MQTTnet.Test.BlazorApp\Shared\MQTTnet.Test.BlazorApp.Shared.csproj", "{DDB069BA-6E1A-48C7-B374-5D903B553CAD}" | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Test.BlazorApp.Shared", "Tests\MQTTnet.Test.BlazorApp\Shared\MQTTnet.Test.BlazorApp.Shared.csproj", "{DDB069BA-6E1A-48C7-B374-5D903B553CAD}" | ||||
EndProject | EndProject | ||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.client.Test", "MQTTnet.client.Test\MQTTnet.client.Test.csproj", "{DEA26B21-C87D-4426-8F13-3BE5D3F61673}" | |||||
EndProject | |||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "BPA", "BPA", "{9D386218-14AA-4340-96B5-540A60B28B87}" | |||||
EndProject | |||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BPA.MQTTnet.Extensions", "BPA.MQTTnet.Extensions\BPA.MQTTnet.Extensions.csproj", "{B860A392-D4EC-4034-B7A2-5E49C884C28B}" | |||||
EndProject | |||||
Global | Global | ||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution | GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||||
Debug|Any CPU = Debug|Any CPU | Debug|Any CPU = Debug|Any CPU | ||||
@@ -277,6 +283,38 @@ Global | |||||
{DDB069BA-6E1A-48C7-B374-5D903B553CAD}.Release|x64.Build.0 = Release|Any CPU | {DDB069BA-6E1A-48C7-B374-5D903B553CAD}.Release|x64.Build.0 = Release|Any CPU | ||||
{DDB069BA-6E1A-48C7-B374-5D903B553CAD}.Release|x86.ActiveCfg = Release|Any CPU | {DDB069BA-6E1A-48C7-B374-5D903B553CAD}.Release|x86.ActiveCfg = Release|Any CPU | ||||
{DDB069BA-6E1A-48C7-B374-5D903B553CAD}.Release|x86.Build.0 = Release|Any CPU | {DDB069BA-6E1A-48C7-B374-5D903B553CAD}.Release|x86.Build.0 = Release|Any CPU | ||||
{DEA26B21-C87D-4426-8F13-3BE5D3F61673}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||||
{DEA26B21-C87D-4426-8F13-3BE5D3F61673}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||||
{DEA26B21-C87D-4426-8F13-3BE5D3F61673}.Debug|ARM.ActiveCfg = Debug|Any CPU | |||||
{DEA26B21-C87D-4426-8F13-3BE5D3F61673}.Debug|ARM.Build.0 = Debug|Any CPU | |||||
{DEA26B21-C87D-4426-8F13-3BE5D3F61673}.Debug|x64.ActiveCfg = Debug|Any CPU | |||||
{DEA26B21-C87D-4426-8F13-3BE5D3F61673}.Debug|x64.Build.0 = Debug|Any CPU | |||||
{DEA26B21-C87D-4426-8F13-3BE5D3F61673}.Debug|x86.ActiveCfg = Debug|Any CPU | |||||
{DEA26B21-C87D-4426-8F13-3BE5D3F61673}.Debug|x86.Build.0 = Debug|Any CPU | |||||
{DEA26B21-C87D-4426-8F13-3BE5D3F61673}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||||
{DEA26B21-C87D-4426-8F13-3BE5D3F61673}.Release|Any CPU.Build.0 = Release|Any CPU | |||||
{DEA26B21-C87D-4426-8F13-3BE5D3F61673}.Release|ARM.ActiveCfg = Release|Any CPU | |||||
{DEA26B21-C87D-4426-8F13-3BE5D3F61673}.Release|ARM.Build.0 = Release|Any CPU | |||||
{DEA26B21-C87D-4426-8F13-3BE5D3F61673}.Release|x64.ActiveCfg = Release|Any CPU | |||||
{DEA26B21-C87D-4426-8F13-3BE5D3F61673}.Release|x64.Build.0 = Release|Any CPU | |||||
{DEA26B21-C87D-4426-8F13-3BE5D3F61673}.Release|x86.ActiveCfg = Release|Any CPU | |||||
{DEA26B21-C87D-4426-8F13-3BE5D3F61673}.Release|x86.Build.0 = Release|Any CPU | |||||
{B860A392-D4EC-4034-B7A2-5E49C884C28B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||||
{B860A392-D4EC-4034-B7A2-5E49C884C28B}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||||
{B860A392-D4EC-4034-B7A2-5E49C884C28B}.Debug|ARM.ActiveCfg = Debug|Any CPU | |||||
{B860A392-D4EC-4034-B7A2-5E49C884C28B}.Debug|ARM.Build.0 = Debug|Any CPU | |||||
{B860A392-D4EC-4034-B7A2-5E49C884C28B}.Debug|x64.ActiveCfg = Debug|Any CPU | |||||
{B860A392-D4EC-4034-B7A2-5E49C884C28B}.Debug|x64.Build.0 = Debug|Any CPU | |||||
{B860A392-D4EC-4034-B7A2-5E49C884C28B}.Debug|x86.ActiveCfg = Debug|Any CPU | |||||
{B860A392-D4EC-4034-B7A2-5E49C884C28B}.Debug|x86.Build.0 = Debug|Any CPU | |||||
{B860A392-D4EC-4034-B7A2-5E49C884C28B}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||||
{B860A392-D4EC-4034-B7A2-5E49C884C28B}.Release|Any CPU.Build.0 = Release|Any CPU | |||||
{B860A392-D4EC-4034-B7A2-5E49C884C28B}.Release|ARM.ActiveCfg = Release|Any CPU | |||||
{B860A392-D4EC-4034-B7A2-5E49C884C28B}.Release|ARM.Build.0 = Release|Any CPU | |||||
{B860A392-D4EC-4034-B7A2-5E49C884C28B}.Release|x64.ActiveCfg = Release|Any CPU | |||||
{B860A392-D4EC-4034-B7A2-5E49C884C28B}.Release|x64.Build.0 = Release|Any CPU | |||||
{B860A392-D4EC-4034-B7A2-5E49C884C28B}.Release|x86.ActiveCfg = Release|Any CPU | |||||
{B860A392-D4EC-4034-B7A2-5E49C884C28B}.Release|x86.Build.0 = Release|Any CPU | |||||
EndGlobalSection | EndGlobalSection | ||||
GlobalSection(SolutionProperties) = preSolution | GlobalSection(SolutionProperties) = preSolution | ||||
HideSolutionNode = FALSE | HideSolutionNode = FALSE | ||||
@@ -296,6 +334,8 @@ Global | |||||
{A9662AF3-3520-4BF8-9DFF-C55C0C33D08F} = {61B165A0-5AA8-4E04-A53D-A22A84AA6EB7} | {A9662AF3-3520-4BF8-9DFF-C55C0C33D08F} = {61B165A0-5AA8-4E04-A53D-A22A84AA6EB7} | ||||
{D260D63D-7902-4C55-9665-84C5CACBBB24} = {61B165A0-5AA8-4E04-A53D-A22A84AA6EB7} | {D260D63D-7902-4C55-9665-84C5CACBBB24} = {61B165A0-5AA8-4E04-A53D-A22A84AA6EB7} | ||||
{DDB069BA-6E1A-48C7-B374-5D903B553CAD} = {61B165A0-5AA8-4E04-A53D-A22A84AA6EB7} | {DDB069BA-6E1A-48C7-B374-5D903B553CAD} = {61B165A0-5AA8-4E04-A53D-A22A84AA6EB7} | ||||
{DEA26B21-C87D-4426-8F13-3BE5D3F61673} = {9248C2E1-B9D6-40BF-81EC-86004D7765B4} | |||||
{B860A392-D4EC-4034-B7A2-5E49C884C28B} = {9D386218-14AA-4340-96B5-540A60B28B87} | |||||
EndGlobalSection | EndGlobalSection | ||||
GlobalSection(ExtensibilityGlobals) = postSolution | GlobalSection(ExtensibilityGlobals) = postSolution | ||||
SolutionGuid = {07536672-5CBC-4BE3-ACE0-708A431A7894} | SolutionGuid = {07536672-5CBC-4BE3-ACE0-708A431A7894} | ||||
@@ -1,5 +1,4 @@ | |||||
<Project Sdk="Microsoft.NET.Sdk"> | |||||
<Project Sdk="Microsoft.NET.Sdk"> | |||||
<PropertyGroup> | <PropertyGroup> | ||||
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0</TargetFrameworks> | <TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0</TargetFrameworks> | ||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks> | <TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks> | ||||
@@ -7,18 +6,19 @@ | |||||
<AssemblyName>MQTTnet</AssemblyName> | <AssemblyName>MQTTnet</AssemblyName> | ||||
<RootNamespace>MQTTnet</RootNamespace> | <RootNamespace>MQTTnet</RootNamespace> | ||||
<GeneratePackageOnBuild>False</GeneratePackageOnBuild> | <GeneratePackageOnBuild>False</GeneratePackageOnBuild> | ||||
<Company /> | |||||
<Product /> | |||||
<Description /> | |||||
<Authors /> | |||||
<PackageId /> | |||||
<Company>黑菠萝</Company> | |||||
<Product>BPA.MQTTnet</Product> | |||||
<Description>黑菠萝-BPA.MQTTnet</Description> | |||||
<Authors>BPA.MQTTnet</Authors> | |||||
<PackageId>BPA.MQTTnet</PackageId> | |||||
<SignAssembly>false</SignAssembly> | <SignAssembly>false</SignAssembly> | ||||
<DelaySign>false</DelaySign> | <DelaySign>false</DelaySign> | ||||
<PublishRepositoryUrl>true</PublishRepositoryUrl> | <PublishRepositoryUrl>true</PublishRepositoryUrl> | ||||
<IncludeSymbols>true</IncludeSymbols> | <IncludeSymbols>true</IncludeSymbols> | ||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat> | <SymbolPackageFormat>snupkg</SymbolPackageFormat> | ||||
<VersionPrefix>1.0.3</VersionPrefix> | |||||
<PackageReleaseNotes /> | |||||
</PropertyGroup> | </PropertyGroup> | ||||
<PropertyGroup Condition="'$(TargetFramework)'=='uap10.0'"> | <PropertyGroup Condition="'$(TargetFramework)'=='uap10.0'"> | ||||
<CopyLocalLockFileAssemblies>false</CopyLocalLockFileAssemblies> | <CopyLocalLockFileAssemblies>false</CopyLocalLockFileAssemblies> | ||||
<NugetTargetMoniker>UAP,Version=v10.0</NugetTargetMoniker> | <NugetTargetMoniker>UAP,Version=v10.0</NugetTargetMoniker> | ||||
@@ -31,23 +31,18 @@ | |||||
<DefaultLanguage>en</DefaultLanguage> | <DefaultLanguage>en</DefaultLanguage> | ||||
<LanguageTargets>$(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets</LanguageTargets> | <LanguageTargets>$(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets</LanguageTargets> | ||||
</PropertyGroup> | </PropertyGroup> | ||||
<PropertyGroup Condition="'$(Configuration)'=='Debug'"> | <PropertyGroup Condition="'$(Configuration)'=='Debug'"> | ||||
<DebugType>Full</DebugType> | <DebugType>Full</DebugType> | ||||
</PropertyGroup> | </PropertyGroup> | ||||
<ItemGroup> | <ItemGroup> | ||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" /> | <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" /> | ||||
</ItemGroup> | </ItemGroup> | ||||
<ItemGroup Condition="'$(TargetFramework)'=='netstandard1.3'"> | <ItemGroup Condition="'$(TargetFramework)'=='netstandard1.3'"> | ||||
<PackageReference Include="System.Net.Security" Version="4.3.2" /> | <PackageReference Include="System.Net.Security" Version="4.3.2" /> | ||||
<PackageReference Include="System.Net.WebSockets" Version="4.3.0" /> | <PackageReference Include="System.Net.WebSockets" Version="4.3.0" /> | ||||
<PackageReference Include="System.Net.WebSockets.Client" Version="4.3.2" /> | <PackageReference Include="System.Net.WebSockets.Client" Version="4.3.2" /> | ||||
</ItemGroup> | </ItemGroup> | ||||
<ItemGroup Condition="'$(TargetFramework)'=='uap10.0'"> | <ItemGroup Condition="'$(TargetFramework)'=='uap10.0'"> | ||||
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform" Version="6.2.10" /> | <PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform" Version="6.2.10" /> | ||||
</ItemGroup> | </ItemGroup> | ||||
</Project> | </Project> |
@@ -0,0 +1,433 @@ | |||||
using MQTTnet; | |||||
using MQTTnet.Client; | |||||
using MQTTnet.Client.Disconnecting; | |||||
using MQTTnet.Client.Options; | |||||
using MQTTnet.Protocol; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.IO; | |||||
using System.Text; | |||||
using System.Threading; | |||||
using System.Threading.Tasks; | |||||
namespace MQTT | |||||
{ | |||||
public class MqttClient | |||||
{ | |||||
#region Field Area | |||||
/// <summary> | |||||
/// Mqtt factory | |||||
/// </summary> | |||||
private MqttFactory _factory; | |||||
/// <summary> | |||||
/// Mqtt client | |||||
/// </summary> | |||||
private IMqttClient _mqttClient; | |||||
/// <summary> | |||||
/// Mqtt 配置信息 | |||||
/// </summary> | |||||
private MqttClientConfig _mqttClientConfig; | |||||
/// <summary> | |||||
/// Mqtt options | |||||
/// </summary> | |||||
private IMqttClientOptions _options; | |||||
#endregion | |||||
#region CTOR | |||||
/// <summary> | |||||
/// 默认启动IP为127.0.0.1 端口为1883 | |||||
/// </summary> | |||||
public MqttClient() | |||||
{ | |||||
_mqttClientConfig = new MqttClientConfig | |||||
{ | |||||
ServerIp = "127.0.0.1", | |||||
Port = 1883 | |||||
}; | |||||
Init(); | |||||
} | |||||
/// <summary> | |||||
/// 调用示例 | |||||
/// | |||||
/// //var client = new MqttClient(s => | |||||
/// { | |||||
/// s.Port = 1883; | |||||
/// s.ServerIp = "127.0.0.1"; | |||||
/// s.UserName = "mqtt-test"; | |||||
/// s.Password = "mqtt-test"; | |||||
/// s.ReciveMsgCallback = s => | |||||
/// { | |||||
/// Console.WriteLine(s.Payload_UTF8); | |||||
/// }; | |||||
/// }, true); | |||||
/// client.Subscribe("/TopicName/"); | |||||
/// </summary> | |||||
/// <param name="config">客户端配置信息</param> | |||||
/// <param name="autoStart">直接启动</param> | |||||
public MqttClient(Action<MqttClientConfig> config, bool autoStart = false) | |||||
{ | |||||
_mqttClientConfig = new MqttClientConfig(); | |||||
config(_mqttClientConfig); | |||||
Init(); | |||||
if (autoStart) | |||||
{ | |||||
Task.WaitAll( Start()); | |||||
} | |||||
} | |||||
#endregion | |||||
/// <summary> | |||||
/// 获取MqttClient实例 | |||||
/// | |||||
/// 调用示例 | |||||
/// //var client = MqttClient.Instance(s => | |||||
/// { | |||||
/// s.Port = 1883; | |||||
/// s.ServerIp = "127.0.0.1"; | |||||
/// s.UserName = "mqtt-test"; | |||||
/// s.Password = "mqtt-test"; | |||||
/// s.ReciveMsgCallback = s => | |||||
/// { | |||||
/// Console.WriteLine(s.Payload_UTF8); | |||||
/// }; | |||||
/// }, true); | |||||
/// client.Subscribe("/TopicName/"); | |||||
/// </summary> | |||||
/// <param name="config">客户端配置信息</param> | |||||
/// <param name="autoStart">直接启动</param> | |||||
/// <returns></returns> | |||||
public static MqttClient Instance(Action<MqttClientConfig> config, bool autoStart = false) | |||||
=> new MqttClient(config, autoStart); | |||||
/// <summary> | |||||
/// 初始化注册 | |||||
/// </summary> | |||||
private void Init() | |||||
{ | |||||
try | |||||
{ | |||||
_factory = new MqttFactory(); | |||||
_mqttClient = _factory.CreateMqttClient(); | |||||
_options = new MqttClientOptionsBuilder() | |||||
.WithTcpServer(_mqttClientConfig.ServerIp, _mqttClientConfig.Port) | |||||
.WithCredentials(_mqttClientConfig.UserName, _mqttClientConfig.Password) | |||||
.WithClientId(_mqttClientConfig.ClientId) | |||||
.Build(); | |||||
//消息回调 | |||||
_mqttClient.UseApplicationMessageReceivedHandler(ReciveMsg); | |||||
} | |||||
catch (Exception exp) | |||||
{ | |||||
if (_mqttClientConfig.Exception is null) | |||||
{ | |||||
throw exp; | |||||
} | |||||
_mqttClientConfig.Exception(exp); | |||||
} | |||||
} | |||||
#region 内部事件转换处理 | |||||
/// <summary> | |||||
/// 消息接收回调 | |||||
/// </summary> | |||||
/// <param name="e"></param> | |||||
/// <returns></returns> | |||||
private void ReciveMsg(MqttApplicationMessageReceivedEventArgs e) | |||||
{ | |||||
if (_mqttClientConfig.ReciveMsgCallback != null) | |||||
{ | |||||
_mqttClientConfig.ReciveMsgCallback(new MqttClientReciveMsg | |||||
{ | |||||
Topic = e.ApplicationMessage.Topic, | |||||
Payload_UTF8 = Encoding.UTF8.GetString(e.ApplicationMessage.Payload), | |||||
Payload = e.ApplicationMessage.Payload, | |||||
Qos = e.ApplicationMessage.QualityOfServiceLevel, | |||||
Retain = e.ApplicationMessage.Retain, | |||||
}); | |||||
} | |||||
} | |||||
/// <summary> | |||||
/// 订阅 | |||||
/// </summary> | |||||
/// <param name="topicName"></param> | |||||
public async void Subscribe(string topicName) | |||||
{ | |||||
topicName = topicName.Trim(); | |||||
if (string.IsNullOrEmpty(topicName)) | |||||
{ | |||||
throw new Exception("订阅主题不能为空!"); | |||||
} | |||||
if (!_mqttClient.IsConnected) | |||||
{ | |||||
throw new Exception("MQTT客户端尚未连接!请先启动连接"); | |||||
} | |||||
await _mqttClient.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic(topicName).Build()); | |||||
} | |||||
/// <summary> | |||||
/// 取消订阅 | |||||
/// </summary> | |||||
/// <param name="topicName"></param> | |||||
public async void Unsubscribe(string topicName) | |||||
{ | |||||
topicName = topicName.Trim(); | |||||
if (string.IsNullOrEmpty(topicName)) | |||||
{ | |||||
throw new Exception("订阅主题不能为空!"); | |||||
} | |||||
if (!_mqttClient.IsConnected) | |||||
{ | |||||
throw new Exception("MQTT客户端尚未连接!请先启动连接"); | |||||
} | |||||
await _mqttClient.UnsubscribeAsync(topicName); | |||||
} | |||||
/// <summary> | |||||
/// 重连机制 | |||||
/// </summary> | |||||
private void ReConnected() | |||||
{ | |||||
if (_mqttClient.IsConnected) | |||||
{ | |||||
return; | |||||
} | |||||
for (int i = 0; i < 10; i++) | |||||
{ | |||||
//重连机制 | |||||
_mqttClient.UseDisconnectedHandler(async e => | |||||
{ | |||||
try | |||||
{ | |||||
await Task.Delay(TimeSpan.FromSeconds(_mqttClientConfig.ReconneTime)); | |||||
await _mqttClient.ConnectAsync(_options); | |||||
return; | |||||
} | |||||
catch (Exception exp) | |||||
{ | |||||
if (_mqttClientConfig.Exception is null) | |||||
{ | |||||
throw exp; | |||||
} | |||||
_mqttClientConfig.Exception(exp); | |||||
} | |||||
}); | |||||
} | |||||
} | |||||
#endregion | |||||
#region 消息发送 | |||||
/// <summary> | |||||
/// 发送消息,含有重连机制,如掉线会自动重连 | |||||
/// </summary> | |||||
/// <param name="message"></param> | |||||
private async Task PublishAsync(string topicName, MqttApplicationMessageBuilder MessageBuilder, PublicQos qos = 0) | |||||
{ | |||||
string topic = topicName.Trim(); | |||||
if (string.IsNullOrEmpty(topic)) | |||||
{ | |||||
throw new Exception("主题不能为空!"); | |||||
} | |||||
ReConnected(); | |||||
MessageBuilder.WithTopic(topic).WithRetainFlag(); | |||||
if (qos == PublicQos.Qos_0) | |||||
{ | |||||
MessageBuilder.WithAtLeastOnceQoS(); | |||||
} | |||||
else if (qos == PublicQos.Qos_1) | |||||
{ | |||||
MessageBuilder.WithAtMostOnceQoS(); | |||||
} | |||||
else | |||||
{ | |||||
MessageBuilder.WithExactlyOnceQoS(); | |||||
} | |||||
var Message = MessageBuilder.Build(); | |||||
try | |||||
{ | |||||
await _mqttClient.PublishAsync(Message); | |||||
} | |||||
catch (Exception e) | |||||
{ | |||||
if (_mqttClientConfig.Exception is null) | |||||
{ | |||||
throw e; | |||||
} | |||||
_mqttClientConfig.Exception(e); | |||||
} | |||||
} | |||||
/// <summary> | |||||
/// 发送消息,含有重连机制,如掉线会自动重连 | |||||
/// </summary> | |||||
/// <param name="message">文字消息</param> | |||||
public async Task Publish(string topicName, string message, PublicQos qos = 0) | |||||
{ | |||||
await PublishAsync(topicName, new MqttApplicationMessageBuilder() | |||||
.WithPayload(message), qos); | |||||
} | |||||
/// <summary> | |||||
/// 发送消息,含有重连机制,如掉线会自动重连 | |||||
/// </summary> | |||||
/// <param name="message">消息流</param> | |||||
public async void Publish(string topicName, Stream message, PublicQos qos = 0) | |||||
=> await PublishAsync(topicName, new MqttApplicationMessageBuilder() | |||||
.WithPayload(message), qos); | |||||
/// <summary> | |||||
/// 发送消息,含有重连机制,如掉线会自动重连 | |||||
/// </summary> | |||||
/// <param name="message">Byte消息</param> | |||||
public async void Publish(string topicName, IEnumerable<byte> message, PublicQos qos = 0) | |||||
=> await PublishAsync(topicName, new MqttApplicationMessageBuilder() | |||||
.WithPayload(message), qos); | |||||
/// <summary> | |||||
/// 发送消息,含有重连机制,如掉线会自动重连 | |||||
/// </summary> | |||||
/// <param name="message">Byte消息</param> | |||||
public async void Publish(string topicName, byte[] message, PublicQos qos = 0) | |||||
=> await PublishAsync(topicName, new MqttApplicationMessageBuilder() | |||||
.WithPayload(message), qos); | |||||
#endregion | |||||
/// <summary> | |||||
/// 启动服务 | |||||
/// </summary> | |||||
/// <returns></returns> | |||||
public async Task Start() | |||||
=> await _mqttClient.ConnectAsync(_options); | |||||
/// <summary> | |||||
/// 停止服务 | |||||
/// </summary> | |||||
/// <returns></returns> | |||||
public async Task Stop() | |||||
=> await _mqttClient.DisconnectAsync(new MqttClientDisconnectOptions { ReasonCode = MqttClientDisconnectReason.NormalDisconnection }, CancellationToken.None); | |||||
} | |||||
public class MqttClientConfig | |||||
{ | |||||
private string _serverIp; | |||||
/// <summary> | |||||
/// 服务器IP | |||||
/// </summary> | |||||
public string ServerIp | |||||
{ | |||||
get => _serverIp; | |||||
set | |||||
{ | |||||
if (string.IsNullOrEmpty(value.Trim())) | |||||
{ | |||||
throw new ArgumentException("ServerIp can't be null or empty!"); | |||||
} | |||||
_serverIp = value; | |||||
} | |||||
} | |||||
private int _port; | |||||
/// <summary> | |||||
/// 服务器端口 | |||||
/// </summary> | |||||
public int Port | |||||
{ | |||||
get => _port; | |||||
set | |||||
{ | |||||
if (value <= 0) | |||||
{ | |||||
throw new ArgumentException("Port can't below the zero!"); | |||||
} | |||||
_port = value; | |||||
} | |||||
} | |||||
/// <summary> | |||||
/// 用户名 | |||||
/// </summary> | |||||
public string UserName { get; set; } | |||||
/// <summary> | |||||
/// 密码 | |||||
/// </summary> | |||||
public string Password { get; set; } | |||||
private string _clientId; | |||||
/// <summary> | |||||
/// 唯一用户ID,默认使用Guid | |||||
/// </summary> | |||||
public string ClientId | |||||
{ | |||||
get | |||||
{ | |||||
_clientId = _clientId ?? Guid.NewGuid().ToString(); | |||||
return _clientId; | |||||
} | |||||
set => _clientId = value; | |||||
} | |||||
/// <summary> | |||||
/// 客户端掉线重连时间,单位/s,默认5s | |||||
/// </summary> | |||||
public double ReconneTime { get; set; } = 5; | |||||
/// <summary> | |||||
/// 异常回调,默认为空,为空抛异常 | |||||
/// </summary> | |||||
public Action<Exception> Exception = null; | |||||
/// <summary> | |||||
/// 接收消息回调,默认不接收 | |||||
/// </summary> | |||||
public Action<MqttClientReciveMsg> ReciveMsgCallback = null; | |||||
} | |||||
public class MqttClientReciveMsg | |||||
{ | |||||
/// <summary> | |||||
/// 主题 | |||||
/// </summary> | |||||
public string Topic { get; set; } | |||||
/// <summary> | |||||
/// UTF-8格式下的 负载/消息 | |||||
/// </summary> | |||||
public string Payload_UTF8 { get; set; } | |||||
/// <summary> | |||||
/// 原始 负载/消息 | |||||
/// </summary> | |||||
public byte[] Payload { get; set; } | |||||
/// <summary> | |||||
/// Qos | |||||
/// </summary> | |||||
public MqttQualityOfServiceLevel Qos { get; set; } | |||||
/// <summary> | |||||
/// 保留 | |||||
/// </summary> | |||||
public bool Retain { get; set; } | |||||
} | |||||
public enum PublicQos | |||||
{ | |||||
/// <summary> | |||||
/// //At most once,至多一次 | |||||
/// </summary> | |||||
Qos_0, | |||||
/// <summary> | |||||
/// //At least once,至少一次 | |||||
/// </summary> | |||||
Qos_1, | |||||
/// <summary> | |||||
/// //QoS2,Exactly once | |||||
/// </summary> | |||||
Qos_2, | |||||
} | |||||
} |
@@ -1,4 +1,4 @@ | |||||
using Microsoft.AspNetCore.Builder; | |||||
using Microsoft.AspNetCore.Builder; | |||||
using Microsoft.AspNetCore.Connections; | using Microsoft.AspNetCore.Connections; | ||||
using Microsoft.AspNetCore.Hosting; | using Microsoft.AspNetCore.Hosting; | ||||
using Microsoft.Extensions.DependencyInjection; | using Microsoft.Extensions.DependencyInjection; | ||||
@@ -18,6 +18,7 @@ using System.Net; | |||||
using MQTTnet.AspNetCore.Extensions; | using MQTTnet.AspNetCore.Extensions; | ||||
using MQTTnet.Protocol; | using MQTTnet.Protocol; | ||||
using MQTTnet.Tests.Extensions; | using MQTTnet.Tests.Extensions; | ||||
using MQTT; | |||||
namespace MQTTnet.AspNetCore.Tests | namespace MQTTnet.AspNetCore.Tests | ||||
{ | { | ||||
@@ -27,15 +28,42 @@ namespace MQTTnet.AspNetCore.Tests | |||||
[TestMethod] | [TestMethod] | ||||
public async Task TestReceivePacketAsyncThrowsWhenReaderCompleted() | public async Task TestReceivePacketAsyncThrowsWhenReaderCompleted() | ||||
{ | { | ||||
var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311); | |||||
var pipe = new DuplexPipeMockup(); | |||||
var connection = new DefaultConnectionContext(); | |||||
connection.Transport = pipe; | |||||
var ctx = new MqttConnectionContext(serializer, connection); | |||||
pipe.Receive.Writer.Complete(); | |||||
var client= MqttClient.Instance(s => | |||||
{ | |||||
s.Port = 1883; | |||||
s.ServerIp = "10.2.1.21"; | |||||
s.UserName = "mqtt-test"; | |||||
s.Password = "mqtt-test"; | |||||
s.ReciveMsgCallback = sqq => | |||||
{ | |||||
Console.WriteLine(sqq.Payload_UTF8); | |||||
}; | |||||
}, true); | |||||
//var client = new MqttClient(s => | |||||
// { | |||||
// s.Port = 1883; | |||||
// s.ServerIp = "10.2.1.21"; | |||||
// s.UserName = "mqtt-test"; | |||||
// s.Password = "mqtt-test"; | |||||
// s.ReciveMsgCallback = sqq => | |||||
// { | |||||
// Console.WriteLine(sqq.Payload_UTF8); | |||||
// }; | |||||
// }, true); | |||||
client.Subscribe("/TopicName/"); | |||||
//var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311); | |||||
//var pipe = new DuplexPipeMockup(); | |||||
//var connection = new DefaultConnectionContext(); | |||||
//connection.Transport = pipe; | |||||
//var ctx = new MqttConnectionContext(serializer, connection); | |||||
//pipe.Receive.Writer.Complete(); | |||||
//await Assert.ThrowsExceptionAsync<MqttCommunicationException>(() => ctx.ReceivePacketAsync(CancellationToken.None)); | |||||
await Assert.ThrowsExceptionAsync<MqttCommunicationException>(() => ctx.ReceivePacketAsync(CancellationToken.None)); | |||||
} | } | ||||
[TestMethod] | [TestMethod] | ||||
@@ -1,4 +1,4 @@ | |||||
using Microsoft.VisualStudio.TestTools.UnitTesting; | |||||
using Microsoft.VisualStudio.TestTools.UnitTesting; | |||||
using MQTTnet.Client; | using MQTTnet.Client; | ||||
using MQTTnet.Client.Options; | using MQTTnet.Client.Options; | ||||
using MQTTnet.Server; | using MQTTnet.Server; | ||||
@@ -120,7 +120,7 @@ namespace MQTTnet.Tests.Mockups | |||||
{ | { | ||||
if (options == null) throw new ArgumentNullException(nameof(options)); | if (options == null) throw new ArgumentNullException(nameof(options)); | ||||
options = options.WithTcpServer("127.0.0.1", ServerPort); | |||||
options = options.WithTcpServer("10.2.1.21", 1883); | |||||
var client = CreateClient(); | var client = CreateClient(); | ||||
await client.ConnectAsync(options.Build()).ConfigureAwait(false); | await client.ConnectAsync(options.Build()).ConfigureAwait(false); | ||||
@@ -1,4 +1,4 @@ | |||||
using System.Collections.Generic; | |||||
using System.Collections.Generic; | |||||
using System.Threading; | using System.Threading; | ||||
using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
using Microsoft.VisualStudio.TestTools.UnitTesting; | using Microsoft.VisualStudio.TestTools.UnitTesting; | ||||
@@ -41,7 +41,9 @@ namespace MQTTnet.Tests.Server | |||||
}, CancellationToken.None); | }, CancellationToken.None); | ||||
MqttApplicationMessage receivedMessage = null; | MqttApplicationMessage receivedMessage = null; | ||||
receiver.UseApplicationMessageReceivedHandler(e => receivedMessage = e.ApplicationMessage); | |||||
receiver.UseApplicationMessageReceivedHandler(e => | |||||
receivedMessage = e.ApplicationMessage); | |||||
await sender.PublishAsync(message.Build(), CancellationToken.None); | await sender.PublishAsync(message.Build(), CancellationToken.None); | ||||
@@ -1,4 +1,4 @@ | |||||
using MQTTnet.Client; | |||||
using MQTTnet.Client; | |||||
using MQTTnet.Client.Connecting; | using MQTTnet.Client.Connecting; | ||||
using MQTTnet.Client.Disconnecting; | using MQTTnet.Client.Disconnecting; | ||||
using MQTTnet.Client.Options; | using MQTTnet.Client.Options; | ||||
@@ -26,7 +26,7 @@ namespace MQTTnet.TestApp.NetCore | |||||
{ | { | ||||
ChannelOptions = new MqttClientTcpOptions | ChannelOptions = new MqttClientTcpOptions | ||||
{ | { | ||||
Server = "127.0.0.1" | |||||
Server = "10.2.1.21" | |||||
} | } | ||||
}; | }; | ||||