This commit is contained in:
Christian
2022-02-20 21:10:46 +01:00
committed by GitHub
parent 47e674e4d3
commit 2ce9b94ec9
608 changed files with 22364 additions and 19420 deletions
@@ -0,0 +1,35 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<IsPackable>false</IsPackable>
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
<EnableNETAnalyzers>false</EnableNETAnalyzers>
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.0" />
<PackageReference Include="MSTest.TestAdapter" Version="2.1.2" />
<PackageReference Include="MSTest.TestFramework" Version="2.1.2" />
<PackageReference Include="Microsoft.CodeAnalysis.Compilers" Version="4.0.1" />
<PackageReference Include="Microsoft.Net.Compilers" Version="4.0.1" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netcoreapp3.1' ">
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="2.2.7" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.2.0" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' != 'netcoreapp3.1' ">
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="2.1.1" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.1.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MQTTnet\MQTTnet.csproj" />
<ProjectReference Include="..\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj" />
<ProjectReference Include="..\MQTTnet.Tests\MQTTnet.Tests.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,26 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using System.IO.Pipelines;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Http.Features;
namespace MQTTnet.AspNetCore.Tests.Mockups
{
public class ConnectionContextMockup : ConnectionContext
{
public override string ConnectionId { get; set; }
public override IFeatureCollection Features { get; }
public override IDictionary<object, object> Items { get; set; }
public override IDuplexPipe Transport { get; set; }
public ConnectionContextMockup()
{
//Transport = new DefaultConnectionContext
}
}
}
@@ -0,0 +1,50 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.AspNetCore.Connections;
using MQTTnet.Adapter;
using MQTTnet.Formatter;
using MQTTnet.Server;
using System;
using System.Threading.Tasks;
using MQTTnet.Diagnostics;
namespace MQTTnet.AspNetCore.Tests.Mockups
{
public class ConnectionHandlerMockup: IMqttServerAdapter
{
public TaskCompletionSource<MqttConnectionContext> Context { get; } = new TaskCompletionSource<MqttConnectionContext>();
public Func<IMqttChannelAdapter, Task> ClientHandler { get; set; }
public async Task OnConnectedAsync(ConnectionContext connection)
{
try
{
var formatter = new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535));
var context = new MqttConnectionContext(formatter, connection);
Context.TrySetResult(context);
await ClientHandler(context);
}
catch (Exception ex)
{
Context.TrySetException(ex);
}
}
public Task StartAsync(MqttServerOptions options, IMqttNetLogger logger)
{
return Task.CompletedTask;
}
public Task StopAsync()
{
return Task.CompletedTask;
}
public void Dispose()
{
}
}
}
@@ -0,0 +1,28 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.IO.Pipelines;
namespace MQTTnet.AspNetCore.Tests.Mockups
{
public class DuplexPipeMockup : IDuplexPipe
{
public DuplexPipeMockup()
{
var pool = new LimitedMemoryPool();
var pipeOptions = new PipeOptions(pool);
Receive = new Pipe(pipeOptions);
Send = new Pipe(pipeOptions);
}
PipeReader IDuplexPipe.Input => Receive.Reader;
PipeWriter IDuplexPipe.Output => Send.Writer;
public Pipe Receive { get; set; }
public Pipe Send { get; set; }
}
}
@@ -0,0 +1,22 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Buffers;
namespace MQTTnet.AspNetCore.Tests.Mockups
{
public class LimitedMemoryPool : MemoryPool<byte>
{
protected override void Dispose(bool disposing)
{
}
public override IMemoryOwner<byte> Rent(int minBufferSize = -1)
{
return new MemoryOwner(minBufferSize);
}
public override int MaxBufferSize { get; }
}
}
@@ -0,0 +1,37 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Buffers;
namespace MQTTnet.AspNetCore.Tests.Mockups
{
public class MemoryOwner : IMemoryOwner<byte>
{
private readonly byte[] _raw;
public MemoryOwner(int size)
{
if (size <= 0)
{
size = 1024;
}
if (size > 4096)
{
size = 4096;
}
_raw = ArrayPool<byte>.Shared.Rent(size);
Memory = _raw;
}
public void Dispose()
{
ArrayPool<byte>.Shared.Return(_raw);
}
public Memory<byte> Memory { get; }
}
}
@@ -0,0 +1,136 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Adapter;
using MQTTnet.AspNetCore.Tests.Mockups;
using MQTTnet.Exceptions;
using MQTTnet.Formatter;
using MQTTnet.Packets;
using MQTTnet.Client;
using MQTTnet.Protocol;
using MQTTnet.Tests.Extensions;
namespace MQTTnet.AspNetCore.Tests
{
[TestClass]
public class MqttConnectionContextTest
{
[TestMethod]
public async Task TestReceivePacketAsyncThrowsWhenReaderCompleted()
{
var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535));
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));
}
[TestMethod]
public async Task TestCorruptedConnectPacket()
{
var writer = new MqttBufferWriter(4096, 65535);
var serializer = new MqttPacketFormatterAdapter(writer);
var pipe = new DuplexPipeMockup();
var connection = new DefaultConnectionContext();
connection.Transport = pipe;
var ctx = new MqttConnectionContext(serializer, connection);
await pipe.Receive.Writer.WriteAsync(writer.AddMqttHeader(MqttControlPacketType.Connect, new byte[0]));
await Assert.ThrowsExceptionAsync<MqttProtocolViolationException>(() => ctx.ReceivePacketAsync(CancellationToken.None));
// the first exception should complete the pipes so if someone tries to use the connection after that it should throw immidiatly
await Assert.ThrowsExceptionAsync<InvalidOperationException>(() => ctx.ReceivePacketAsync(CancellationToken.None));
}
// COMMENTED OUT DUE TO DEAD LOCK? OR VERY VERY SLOW PERFORMANCE ON LOCAL DEV MACHINE. TEST WAS STILL RUNNING AFTER SEVERAL MINUTES!
//[TestMethod]
//public async Task TestParallelWrites()
//{
// var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311);
// var pipe = new DuplexPipeMockup();
// var connection = new DefaultConnectionContext();
// connection.Transport = pipe;
// var ctx = new MqttConnectionContext(serializer, connection);
// var tasks = Enumerable.Range(1, 100).Select(_ => Task.Run(async () =>
// {
// for (int i = 0; i < 100; i++)
// {
// await ctx.SendPacketAsync(new MqttPublishPacket(), TimeSpan.Zero, CancellationToken.None).ConfigureAwait(false);
// }
// }));
// await Task.WhenAll(tasks).ConfigureAwait(false);
//}
[TestMethod]
public async Task TestLargePacket()
{
var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535));
var pipe = new DuplexPipeMockup();
var connection = new DefaultConnectionContext();
connection.Transport = pipe;
var ctx = new MqttConnectionContext(serializer, connection);
await ctx.SendPacketAsync(new MqttPublishPacket { Payload = new byte[20_000] }, CancellationToken.None).ConfigureAwait(false);
var readResult = await pipe.Send.Reader.ReadAsync();
Assert.IsTrue(readResult.Buffer.Length > 20000);
}
private class Startup
{
public void Configure(IApplicationBuilder app)
{
}
}
[TestMethod]
public async Task TestEndpoint()
{
var mockup = new ConnectionHandlerMockup();
using (var host = new WebHostBuilder()
.UseKestrel(kestrel => kestrel.ListenLocalhost(1883, listener => listener.Use((ctx, next) => mockup.OnConnectedAsync(ctx))))
.UseStartup<Startup>()
.ConfigureServices((hostContext, services) =>
{
services.AddHostedMqttServer(o => o.WithoutDefaultEndpoint());
services.AddSingleton<IMqttServerAdapter>(mockup);
})
.Build())
using (var client = new MqttFactory().CreateMqttClient())
{
host.Start();
await client.ConnectAsync(new MqttClientOptionsBuilder()
.WithTcpServer("localhost")
.Build(), CancellationToken.None);
var ctx = await mockup.Context.Task;
#if NETCOREAPP3_1
var ep = IPEndPoint.Parse(ctx.Endpoint);
Assert.IsNotNull(ep);
#endif
Assert.IsNotNull(ctx);
}
}
}
}
@@ -0,0 +1,51 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#if NETCOREAPP3_1
using System.Buffers;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.AspNetCore;
using MQTTnet.Formatter;
using MQTTnet.Packets;
namespace MQTTnet.AspNetCore.Tests
{
[TestClass]
public class ReaderExtensionsTest
{
[TestMethod]
public void TestTryDeserialize()
{
var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535));
var buffer = serializer.Encode(new MqttPublishPacket {Topic = "a", Payload = new byte[5]}).Join();
var sequence = new ReadOnlySequence<byte>(buffer.Array, buffer.Offset, buffer.Count);
var part = sequence;
MqttPacket packet;
var consumed = part.Start;
var observed = part.Start;
var result = false;
var read = 0;
part = sequence.Slice(sequence.Start, 0); // empty message should fail
result = serializer.TryDecode(part, out packet, out consumed, out observed, out read);
Assert.IsFalse(result);
part = sequence.Slice(sequence.Start, 1); // partial fixed header should fail
result = serializer.TryDecode(part, out packet, out consumed, out observed, out read);
Assert.IsFalse(result);
part = sequence.Slice(sequence.Start, 4); // partial body should fail
result = serializer.TryDecode(part, out packet, out consumed, out observed, out read);
Assert.IsFalse(result);
part = sequence; // complete msg should work
result = serializer.TryDecode(part, out packet, out consumed, out observed, out read);
Assert.IsTrue(result);
}
}
}
#endif
@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<IsPackable>false</IsPackable>
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
<EnableNETAnalyzers>false</EnableNETAnalyzers>
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj"/>
<PackageReference Include="Microsoft.CodeAnalysis.Compilers" Version="4.0.1" />
<PackageReference Include="Microsoft.Net.Compilers" Version="4.0.1" />
</ItemGroup>
<ItemGroup>
<Folder Include="wwwroot\"/>
</ItemGroup>
</Project>
@@ -0,0 +1,34 @@
@page
@model IndexModel
@{
ViewData["Title"] = "Home page";
}
<div>
<h1>MQTTnet ASP.NET Core Test App</h1>
</div>
<script src="https://unpkg.com/mqtt/dist/mqtt.min.js"></script>
<script>
const options = {
clean: true,
connectTimeout: 4000,
clientId: 'mqttnet_web_client'
}
const client = mqtt.connect('ws://localhost:5049/mqtt')
client.on('connect', function () {
console.log('Connected')
client.subscribe('#', function (err) {
if (!err) {
client.publish('test', 'Hello mqtt')
}
})
})
client.on('message', function (topic, message) {
console.log(message.toString())
})
</script>
@@ -0,0 +1,23 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace MQTTnet.AspTestApp.Pages
{
public class IndexModel : PageModel
{
private readonly ILogger<IndexModel> _logger;
public IndexModel(ILogger<IndexModel> logger)
{
_logger = logger;
}
public void OnGet()
{
}
}
}
@@ -0,0 +1,14 @@
<!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</title>
</head>
<body>
@RenderBody()
@await RenderSectionAsync("Scripts", required: false)
</body>
</html>
@@ -0,0 +1,2 @@
@namespace MQTTnet.AspTestApp.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@@ -0,0 +1,3 @@
@{
Layout = "_Layout";
}
+73
View File
@@ -0,0 +1,73 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using MQTTnet;
using MQTTnet.AspNetCore;
using MQTTnet.Server;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
// Setup MQTT stuff.
builder.Services.AddMqttServer();
builder.Services.AddConnections();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
}
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
// Setup MQTT stuff.
app.UseEndpoints(endpoints =>
{
endpoints.MapMqtt("/mqtt");
});
app.UseMqttServer(server =>
{
server.StartedAsync += args =>
{
_ = Task.Run(async () =>
{
var mqttApplicationMessage = new MqttApplicationMessageBuilder()
.WithPayload($"Test application message from MQTTnet server.")
.WithTopic("message")
.Build();
while (true)
{
try
{
await server.InjectApplicationMessage(new InjectedMqttApplicationMessage(mqttApplicationMessage)
{
SenderClientId = "server"
});
}
catch (Exception e)
{
Console.WriteLine(e);
}
finally
{
await Task.Delay(TimeSpan.FromSeconds(5));
}
}
});
return Task.CompletedTask;
};
});
app.Run();
@@ -0,0 +1,9 @@
{
"DetailedErrors": true,
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
+5
View File
@@ -0,0 +1,5 @@
{
"version": "1.0",
"defaultProvider": "cdnjs",
"libraries": []
}
@@ -1,9 +1,13 @@
using System;
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using MQTTnet.Server;
namespace MQTTnet.AspNetCore.Extensions
namespace MQTTnet.AspNetCore
{
public static class ApplicationBuilderExtensions
{
@@ -36,9 +40,9 @@ namespace MQTTnet.AspNetCore.Extensions
return app;
}
public static IApplicationBuilder UseMqttServer(this IApplicationBuilder app, Action<IMqttServer> configure)
public static IApplicationBuilder UseMqttServer(this IApplicationBuilder app, Action<MqttServer> configure)
{
var server = app.ApplicationServices.GetRequiredService<IMqttServer>();
var server = app.ApplicationServices.GetRequiredService<MqttServer>();
configure(server);
@@ -1,13 +1,17 @@
using MQTTnet.Server;
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using MQTTnet.Server;
using System;
namespace MQTTnet.AspNetCore
{
public class AspNetMqttServerOptionsBuilder : MqttServerOptionsBuilder
public sealed class AspNetMqttServerOptionsBuilder : MqttServerOptionsBuilder
{
public AspNetMqttServerOptionsBuilder(IServiceProvider serviceProvider)
{
ServiceProvider = serviceProvider;
ServiceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
}
public IServiceProvider ServiceProvider { get; }
@@ -1,15 +1,20 @@
using MQTTnet.Adapter;
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using MQTTnet.Adapter;
using MQTTnet.AspNetCore.Client.Tcp;
using MQTTnet.Client.Options;
using MQTTnet.Formatter;
using System;
using System.Net;
using MQTTnet.Client;
using MQTTnet.Diagnostics;
namespace MQTTnet.AspNetCore.Client
{
public class MqttClientConnectionContextFactory : IMqttClientAdapterFactory
public sealed class MqttClientConnectionContextFactory : IMqttClientAdapterFactory
{
public IMqttChannelAdapter CreateClientAdapter(IMqttClientOptions options)
public IMqttChannelAdapter CreateClientAdapter(MqttClientOptions options, MqttPacketInspector packetInspector, IMqttNetLogger logger)
{
if (options == null) throw new ArgumentNullException(nameof(options));
@@ -19,9 +24,8 @@ namespace MQTTnet.AspNetCore.Client
{
var endpoint = new DnsEndPoint(tcpOptions.Server, tcpOptions.GetPort());
var tcpConnection = new TcpConnection(endpoint);
var writer = new SpanBasedMqttPacketWriter();
var formatter = new MqttPacketFormatterAdapter(options.ProtocolVersion, writer);
var formatter = new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(4096, 65535));
return new MqttConnectionContext(formatter, tcpConnection);
}
default:
@@ -1,4 +1,8 @@
using System;
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Runtime.InteropServices;
namespace MQTTnet.AspNetCore.Client.Tcp
@@ -1,4 +1,8 @@
using System.IO.Pipelines;
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.IO.Pipelines;
namespace MQTTnet.AspNetCore.Client.Tcp
{
@@ -1,4 +1,8 @@
using System;
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Diagnostics;
using System.IO.Pipelines;
using System.Net.Sockets;
@@ -1,4 +1,8 @@
using System;
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.IO.Pipelines;
using System.Net.Sockets;
@@ -1,4 +1,8 @@
using System;
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Diagnostics;
@@ -1,4 +1,8 @@
using Microsoft.AspNetCore.Connections;
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Http.Features;
using MQTTnet.Exceptions;
using System;
@@ -40,7 +44,7 @@ namespace MQTTnet.AspNetCore.Client.Tcp
_sender = new SocketSender(_socket, PipeScheduler.ThreadPool);
_receiver = new SocketReceiver(_socket, PipeScheduler.ThreadPool);
}
#if NETCOREAPP3_1 || NET5_0
#if NETCOREAPP3_1 || NET5_0_OR_GREATER
public override ValueTask DisposeAsync()
#else
public Task DisposeAsync()
@@ -53,7 +57,7 @@ namespace MQTTnet.AspNetCore.Client.Tcp
_socket?.Dispose();
#if NETCOREAPP3_1 || NET5_0
#if NETCOREAPP3_1 || NET5_0_OR_GREATER
return base.DisposeAsync();
}
@@ -0,0 +1,16 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.AspNetCore.Connections;
namespace MQTTnet.AspNetCore
{
public static class ConnectionBuilderExtensions
{
public static IConnectionBuilder UseMqtt(this IConnectionBuilder builder)
{
return builder.UseConnectionHandler<MqttConnectionHandler>();
}
}
}
@@ -1,11 +1,15 @@
using Microsoft.AspNetCore.Http;
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Connections;
#if NETCOREAPP3_1
using System;
#endif
namespace MQTTnet.AspNetCore.Extensions
namespace MQTTnet.AspNetCore
{
public static class ConnectionRouteBuilderExtensions
{
@@ -1,5 +1,9 @@

#if NETCOREAPP3_1 || NET5_0
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#if NETCOREAPP3_1 || NET5_0_OR_GREATER
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Routing;
@@ -1,12 +0,0 @@
using Microsoft.AspNetCore.Connections;
namespace MQTTnet.AspNetCore.Extensions
{
public static class ConnectionBuilderExtensions
{
public static IConnectionBuilder UseMqtt(this IConnectionBuilder builder)
{
return builder.UseConnectionHandler<MqttConnectionHandler>();
}
}
}
@@ -1,36 +1,71 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;netcoreapp2.1;netcoreapp3.1;net5.0</TargetFrameworks>
<Product />
<Company />
<Authors />
<PackageId />
<LangVersion>7.2</LangVersion>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>
<PropertyGroup>
<TargetFrameworks>netstandard2.0;netcoreapp2.1;netcoreapp3.1;net5.0;net6.0</TargetFrameworks>
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
</ItemGroup>
<AssemblyName>MQTTnet.AspNetCore</AssemblyName>
<RootNamespace>MQTTnet.AspNetCore</RootNamespace>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<Company>The contributors of MQTTnet</Company>
<Product>MQTTnet</Product>
<Description>MQTTnet is a high performance .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker) and supports v3.1.0, v3.1.1 and v5.0.0 of the MQTT protocol.</Description>
<Authors>The contributors of MQTTnet</Authors>
<PackageId>MQTTnet.AspNetCore</PackageId>
<SignAssembly>false</SignAssembly>
<DelaySign>false</DelaySign>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<Copyright>Christian Kratky 2016-2022</Copyright>
<PackageProjectUrl>https://github.com/dotnet/MQTTnet</PackageProjectUrl>
<RepositoryUrl>https://github.com/dotnet/MQTTnet.git</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageTags>MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin Blazor</PackageTags>
<NeutralLanguage>en-US</NeutralLanguage>
<EnableNETAnalyzers>false</EnableNETAnalyzers>
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
<PackageIcon>nuget.png</PackageIcon>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<PackageReleaseNotes>For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).</PackageReleaseNotes>
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DefineConstants>RELEASE;NETSTANDARD2_0</DefineConstants>
</PropertyGroup>
<ItemGroup>
<None Include="..\..\Images\nuget.png">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp3.1' or '$(TargetFramework)' == 'net5.0' ">
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' != 'netcoreapp3.1' and '$(TargetFramework)' != 'net5.0' ">
<PackageReference Include="Microsoft.AspNetCore.Http.Connections" Version="1.1.0" />
</ItemGroup>
<ItemGroup>
<None Include="..\..\LICENSE">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MQTTnet\MQTTnet.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
</ItemGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DefineConstants>RELEASE;NETSTANDARD2_0</DefineConstants>
</PropertyGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp3.1' or '$(TargetFramework)' == 'net5.0' or '$(TargetFramework)' == 'net6.0' ">
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' != 'netcoreapp3.1' and '$(TargetFramework)' != 'net5.0' and '$(TargetFramework)' != 'net6.0' ">
<PackageReference Include="Microsoft.AspNetCore.Http.Connections" Version="1.1.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MQTTnet\MQTTnet.csproj" />
</ItemGroup>
</Project>
@@ -1,4 +1,8 @@
using Microsoft.AspNetCore.Connections;
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Http.Connections.Features;
using MQTTnet.Adapter;
using MQTTnet.AspNetCore.Client.Tcp;
@@ -10,7 +14,6 @@ using System.IO.Pipelines;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.AspNetCore.Extensions;
using MQTTnet.Internal;
namespace MQTTnet.AspNetCore
@@ -18,7 +21,6 @@ namespace MQTTnet.AspNetCore
public sealed class MqttConnectionContext : IMqttChannelAdapter
{
readonly AsyncLock _writerLock = new AsyncLock();
readonly SpanBasedMqttPacketBodyReader _reader;
PipeReader _input;
PipeWriter _output;
@@ -33,8 +35,6 @@ namespace MQTTnet.AspNetCore
_input = Connection.Transport.Input;
_output = Connection.Transport.Output;
}
_reader = new SpanBasedMqttPacketBodyReader();
}
public string Endpoint
@@ -73,7 +73,7 @@ namespace MQTTnet.AspNetCore
IHttpContextFeature Http => Connection.Features.Get<IHttpContextFeature>();
public async Task ConnectAsync(TimeSpan timeout, CancellationToken cancellationToken)
public async Task ConnectAsync(CancellationToken cancellationToken)
{
if (Connection is TcpConnection tcp && !tcp.IsConnected)
{
@@ -84,7 +84,7 @@ namespace MQTTnet.AspNetCore
_output = Connection.Transport.Output;
}
public Task DisconnectAsync(TimeSpan timeout, CancellationToken cancellationToken)
public Task DisconnectAsync(CancellationToken cancellationToken)
{
_input?.Complete();
_output?.Complete();
@@ -92,7 +92,7 @@ namespace MQTTnet.AspNetCore
return Task.CompletedTask;
}
public async Task<MqttBasePacket> ReceivePacketAsync(CancellationToken cancellationToken)
public async Task<MqttPacket> ReceivePacketAsync(CancellationToken cancellationToken)
{
var input = Connection.Transport.Input;
@@ -120,7 +120,7 @@ namespace MQTTnet.AspNetCore
{
if (!buffer.IsEmpty)
{
if (PacketFormatterAdapter.TryDecode(_reader, buffer, out var packet, out consumed, out observed, out var received))
if (PacketFormatterAdapter.TryDecode(buffer, out var packet, out consumed, out observed, out var received))
{
BytesReceived += received;
return packet;
@@ -167,13 +167,14 @@ namespace MQTTnet.AspNetCore
BytesSent = 0;
}
public async Task SendPacketAsync(MqttBasePacket packet, CancellationToken cancellationToken)
public async Task SendPacketAsync(MqttPacket packet, CancellationToken cancellationToken)
{
var formatter = PacketFormatterAdapter;
using (await _writerLock.WaitAsync(cancellationToken).ConfigureAwait(false))
{
var buffer = formatter.Encode(packet);
var msg = buffer.AsMemory();
var msg = buffer.Join().AsMemory();
var output = _output;
var result = await output.WriteAsync(msg, cancellationToken).ConfigureAwait(false);
if (result.IsCompleted)
@@ -181,7 +182,7 @@ namespace MQTTnet.AspNetCore
BytesSent += msg.Length;
}
PacketFormatterAdapter.FreeBuffer();
PacketFormatterAdapter.Cleanup();
}
}
@@ -1,15 +1,22 @@
using Microsoft.AspNetCore.Connections;
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Connections.Features;
using MQTTnet.Adapter;
using MQTTnet.Server;
using System;
using System.Threading.Tasks;
using MQTTnet.Diagnostics;
using MQTTnet.Formatter;
namespace MQTTnet.AspNetCore
{
public class MqttConnectionHandler : ConnectionHandler, IMqttServerAdapter
public sealed class MqttConnectionHandler : ConnectionHandler, IMqttServerAdapter
{
MqttServerOptions _serverOptions;
public Func<IMqttChannelAdapter, Task> ClientHandler { get; set; }
public override async Task OnConnectedAsync(ConnectionContext connection)
@@ -20,9 +27,8 @@ namespace MQTTnet.AspNetCore
{
transferFormatFeature.ActiveFormat = TransferFormat.Binary;
}
var writer = new SpanBasedMqttPacketWriter();
var formatter = new MqttPacketFormatterAdapter(writer);
var formatter = new MqttPacketFormatterAdapter(new MqttBufferWriter(_serverOptions.WriterBufferSize, _serverOptions.WriterBufferSizeMax));
using (var adapter = new MqttConnectionContext(formatter, connection))
{
var clientHandler = ClientHandler;
@@ -33,8 +39,10 @@ namespace MQTTnet.AspNetCore
}
}
public Task StartAsync(IMqttServerOptions options)
public Task StartAsync(MqttServerOptions options, IMqttNetLogger logger)
{
_serverOptions = options;
return Task.CompletedTask;
}
@@ -1,27 +1,27 @@
using System;
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using MQTTnet.Adapter;
using MQTTnet.Diagnostics.Logger;
using MQTTnet.Diagnostics;
using MQTTnet.Server;
namespace MQTTnet.AspNetCore
{
public sealed class MqttHostedServer : MqttServer, IHostedService
{
readonly IMqttServerOptions _options;
public MqttHostedServer(IMqttServerOptions options, IEnumerable<IMqttServerAdapter> adapters, IMqttNetLogger logger)
: base(adapters, logger)
public MqttHostedServer(MqttServerOptions options, IEnumerable<IMqttServerAdapter> adapters, IMqttNetLogger logger)
: base(options, adapters, logger)
{
_options = options ?? throw new ArgumentNullException(nameof(options));
}
public Task StartAsync(CancellationToken cancellationToken)
{
_ = StartAsync(_options);
_ = StartAsync();
return Task.CompletedTask;
}
@@ -30,4 +30,4 @@ namespace MQTTnet.AspNetCore
return StopAsync();
}
}
}
}
@@ -1,4 +1,8 @@
using System;
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Http;
@@ -1,4 +1,8 @@
using Microsoft.AspNetCore.Http;
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.AspNetCore.Http;
using MQTTnet.Adapter;
using MQTTnet.Formatter;
using MQTTnet.Implementations;
@@ -6,23 +10,19 @@ using MQTTnet.Server;
using System;
using System.Net.WebSockets;
using System.Threading.Tasks;
using MQTTnet.Diagnostics.Logger;
using MQTTnet.Diagnostics;
namespace MQTTnet.AspNetCore
{
public sealed class MqttWebSocketServerAdapter : IMqttServerAdapter
{
readonly IMqttNetLogger _rootLogger;
public MqttWebSocketServerAdapter(IMqttNetLogger logger)
{
_rootLogger = logger ?? throw new ArgumentNullException(nameof(logger));
}
IMqttNetLogger _logger = new MqttNetNullLogger();
public Func<IMqttChannelAdapter, Task> ClientHandler { get; set; }
public Task StartAsync(IMqttServerOptions options)
public Task StartAsync(MqttServerOptions options, IMqttNetLogger logger)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
return Task.CompletedTask;
}
@@ -45,11 +45,10 @@ namespace MQTTnet.AspNetCore
var clientHandler = ClientHandler;
if (clientHandler != null)
{
var writer = new SpanBasedMqttPacketWriter();
var formatter = new MqttPacketFormatterAdapter(writer);
var formatter = new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535));
var channel = new MqttWebSocketChannel(webSocket, endpoint, isSecureConnection, clientCertificate);
using (var channelAdapter = new MqttChannelAdapter(channel, formatter, null, _rootLogger))
using (var channelAdapter = new MqttChannelAdapter(channel, formatter, null, _logger))
{
await clientHandler(channelAdapter).ConfigureAwait(false);
}
@@ -1,18 +1,21 @@
using System;
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Buffers;
using MQTTnet.Adapter;
using MQTTnet.Exceptions;
using MQTTnet.Formatter;
using MQTTnet.Packets;
namespace MQTTnet.AspNetCore.Extensions
namespace MQTTnet.AspNetCore
{
public static class ReaderExtensions
{
public static bool TryDecode(this MqttPacketFormatterAdapter formatter,
SpanBasedMqttPacketBodyReader reader,
in ReadOnlySequence<byte> input,
out MqttBasePacket packet,
out MqttPacket packet,
out SequencePosition consumed,
out SequencePosition observed,
out int bytesRead)
@@ -30,7 +33,7 @@ namespace MQTTnet.AspNetCore.Extensions
return false;
}
var fixedheader = copy.First.Span[0];
var fixedHeader = copy.First.Span[0];
if (!TryReadBodyLength(ref copy, out int headerLength, out var bodyLength))
{
return false;
@@ -42,10 +45,9 @@ namespace MQTTnet.AspNetCore.Extensions
}
var bodySlice = copy.Slice(0, bodyLength);
var buffer = bodySlice.GetMemory();
reader.SetBuffer(buffer);
var receivedMqttPacket = new ReceivedMqttPacket(fixedheader, reader, buffer.Length + 2);
var buffer = bodySlice.GetMemory().ToArray();
var receivedMqttPacket = new ReceivedMqttPacket(fixedHeader, new ArraySegment<byte>(buffer, 0, buffer.Length), buffer.Length + 2);
if (formatter.ProtocolVersion == MqttProtocolVersion.Unknown)
{
@@ -59,7 +61,7 @@ namespace MQTTnet.AspNetCore.Extensions
return true;
}
private static ReadOnlyMemory<byte> GetMemory(this in ReadOnlySequence<byte> input)
static ReadOnlyMemory<byte> GetMemory(this in ReadOnlySequence<byte> input)
{
if (input.IsSingleSegment)
{
@@ -70,7 +72,7 @@ namespace MQTTnet.AspNetCore.Extensions
return input.ToArray();
}
private static bool TryReadBodyLength(ref ReadOnlySequence<byte> input, out int headerLength, out int bodyLength)
static bool TryReadBodyLength(ref ReadOnlySequence<byte> input, out int headerLength, out int bodyLength)
{
// Alorithm taken from https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/errata01/os/mqtt-v3.1.1-errata01-os-complete.html.
var multiplier = 1;
@@ -88,6 +90,7 @@ namespace MQTTnet.AspNetCore.Extensions
{
return false;
}
encodedByte = temp[index];
index++;
@@ -1,17 +1,33 @@
using System;
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using MQTTnet.Adapter;
using MQTTnet.Diagnostics;
using MQTTnet.Diagnostics.Logger;
using MQTTnet.Implementations;
using MQTTnet.Server;
namespace MQTTnet.AspNetCore.Extensions
namespace MQTTnet.AspNetCore
{
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddHostedMqttServer(this IServiceCollection services, IMqttServerOptions options)
public static IServiceCollection AddMqttServer(this IServiceCollection serviceCollection, Action<MqttServerOptionsBuilder> configure = null)
{
if (serviceCollection is null)
{
throw new ArgumentNullException(nameof(serviceCollection));
}
serviceCollection.AddMqttConnectionHandler();
serviceCollection.AddHostedMqttServer(configure);
return serviceCollection;
}
public static IServiceCollection AddHostedMqttServer(this IServiceCollection services, MqttServerOptions options)
{
if (options == null) throw new ArgumentNullException(nameof(options));
@@ -22,13 +38,13 @@ namespace MQTTnet.AspNetCore.Extensions
return services;
}
public static IServiceCollection AddHostedMqttServer(this IServiceCollection services, Action<MqttServerOptionsBuilder> configure)
public static IServiceCollection AddHostedMqttServer(this IServiceCollection services, Action<MqttServerOptionsBuilder> configure = null)
{
services.AddSingleton<IMqttServerOptions>(s =>
services.AddSingleton<MqttServerOptions>(s =>
{
var builder = new MqttServerOptionsBuilder();
configure(builder);
return builder.Build();
var serverOptionsBuilder = new MqttServerOptionsBuilder();
configure?.Invoke(serverOptionsBuilder);
return serverOptionsBuilder.Build();
});
services.AddHostedMqttServer();
@@ -38,7 +54,7 @@ namespace MQTTnet.AspNetCore.Extensions
public static IServiceCollection AddHostedMqttServerWithServices(this IServiceCollection services, Action<AspNetMqttServerOptionsBuilder> configure)
{
services.AddSingleton<IMqttServerOptions>(s =>
services.AddSingleton<MqttServerOptions>(s =>
{
var builder = new AspNetMqttServerOptionsBuilder(s);
configure(builder);
@@ -50,24 +66,14 @@ namespace MQTTnet.AspNetCore.Extensions
return services;
}
public static IServiceCollection AddHostedMqttServer<TOptions>(this IServiceCollection services)
where TOptions : class, IMqttServerOptions
{
services.AddSingleton<IMqttServerOptions, TOptions>();
services.AddHostedMqttServer();
return services;
}
private static IServiceCollection AddHostedMqttServer(this IServiceCollection services)
static IServiceCollection AddHostedMqttServer(this IServiceCollection services)
{
var logger = new MqttNetEventLogger();
services.AddSingleton<IMqttNetLogger>(logger);
services.AddSingleton<MqttHostedServer>();
services.AddSingleton<IHostedService>(s => s.GetService<MqttHostedServer>());
services.AddSingleton<IMqttServer>(s => s.GetService<MqttHostedServer>());
services.AddSingleton<MqttServer>(s => s.GetService<MqttHostedServer>());
return services;
}
@@ -1,129 +0,0 @@
using MQTTnet.Exceptions;
using MQTTnet.Formatter;
using System;
using System.Buffers.Binary;
using System.Text;
namespace MQTTnet.AspNetCore
{
public class SpanBasedMqttPacketBodyReader : IMqttPacketBodyReader
{
ReadOnlyMemory<byte> _buffer;
int _offset;
public void SetBuffer(ReadOnlyMemory<byte> buffer)
{
_buffer = buffer;
_offset = 0;
}
public int Length => _buffer.Length;
public bool EndOfStream => _buffer.Length.Equals(_offset);
public int Offset => _offset;
public byte ReadByte()
{
return _buffer.Span[_offset++];
}
public byte[] ReadRemainingData()
{
return _buffer.Slice(_offset).ToArray();
}
public byte[] ReadWithLengthPrefix()
{
return ReadSegmentWithLengthPrefix().ToArray();
}
public unsafe string ReadStringWithLengthPrefix()
{
var buffer = ReadSegmentWithLengthPrefix();
if (buffer.Length == 0)
{
return string.Empty;
}
fixed (byte* bytes = &buffer.GetPinnableReference())
{
var result = Encoding.UTF8.GetString(bytes, buffer.Length);
return result;
}
}
public ushort ReadTwoByteInteger()
{
var result = BinaryPrimitives.ReadUInt16BigEndian(_buffer.Span.Slice(_offset));
_offset += 2;
return result;
}
public uint ReadFourByteInteger()
{
var result = BinaryPrimitives.ReadUInt32BigEndian(_buffer.Span.Slice(_offset));
_offset += 4;
return result;
}
public uint ReadVariableLengthInteger()
{
var multiplier = 1;
var value = 0U;
byte encodedByte;
do
{
encodedByte = ReadByte();
value += (uint)((encodedByte & 127) * multiplier);
if (multiplier > 2097152)
{
throw new MqttProtocolViolationException("Variable length integer is invalid.");
}
multiplier *= 128;
} while ((encodedByte & 128) != 0);
return value;
}
public bool ReadBoolean()
{
var buffer = ReadByte();
if (buffer == 0)
{
return false;
}
if (buffer == 1)
{
return true;
}
throw new MqttProtocolViolationException("Boolean values can be 0 or 1 only.");
}
public void Seek(int position)
{
_offset = position;
}
ReadOnlySpan<byte> ReadSegmentWithLengthPrefix()
{
var span = _buffer.Span;
var length = BinaryPrimitives.ReadUInt16BigEndian(span.Slice(_offset));
if (Length < _offset + length)
{
throw new MqttProtocolViolationException($"Expected at least {_offset + 2 + length} bytes but there are only {Length} bytes");
}
var result = span.Slice(_offset + 2, length);
_offset += 2 + length;
return result;
}
}
}
@@ -1,144 +0,0 @@
using MQTTnet.Formatter;
using System;
using System.Buffers;
using System.Buffers.Binary;
using System.Text;
namespace MQTTnet.AspNetCore
{
public class SpanBasedMqttPacketWriter : IMqttPacketWriter
{
readonly ArrayPool<byte> _pool = ArrayPool<byte>.Create();
public SpanBasedMqttPacketWriter()
{
Reset(0);
}
byte[] _buffer;
int _position;
public int Length { get; set; }
public void FreeBuffer()
{
_pool.Return(_buffer);
}
public byte[] GetBuffer()
{
return _buffer;
}
public void Reset(int v)
{
_buffer = _pool.Rent(1500);
Length = v;
_position = v;
}
public void Seek(int v)
{
_position = v;
}
public void Write(byte value)
{
GrowIfNeeded(1);
_buffer[_position] = value;
Commit(1);
}
public void Write(ushort value)
{
GrowIfNeeded(2);
BinaryPrimitives.WriteUInt16BigEndian(_buffer.AsSpan(_position), value);
Commit(2);
}
public void Write(IMqttPacketWriter propertyWriter)
{
if (propertyWriter == null) throw new ArgumentNullException(nameof(propertyWriter));
GrowIfNeeded(propertyWriter.Length);
Write(propertyWriter.GetBuffer(), 0, propertyWriter.Length);
}
public void Write(byte[] payload, int start, int length)
{
GrowIfNeeded(length);
payload.AsSpan(start, length).CopyTo(_buffer.AsSpan(_position));
Commit(length);
}
public void WriteVariableLengthInteger(uint value)
{
GrowIfNeeded(4);
var x = value;
do
{
var encodedByte = x % 128;
x = x / 128;
if (x > 0)
{
encodedByte = encodedByte | 128;
}
_buffer[_position] = (byte)encodedByte;
Commit(1);
} while (x > 0);
}
public void WriteWithLengthPrefix(string value)
{
var bytesLength = Encoding.UTF8.GetByteCount(value ?? string.Empty);
GrowIfNeeded(bytesLength + 2);
Write((ushort)bytesLength);
Encoding.UTF8.GetBytes(value ?? string.Empty, 0, value?.Length ?? 0, _buffer, _position);
Commit(bytesLength);
}
public void WriteWithLengthPrefix(byte[] payload)
{
GrowIfNeeded(payload.Length + 2);
Write((ushort)payload.Length);
payload.CopyTo(_buffer, _position);
Commit(payload.Length);
}
void Commit(int count)
{
if (_position == Length)
{
Length += count;
}
_position += count;
}
void GrowIfNeeded(int requiredAdditional)
{
var requiredTotal = _position + requiredAdditional;
if (_buffer.Length >= requiredTotal)
{
return;
}
var newBufferLength = _buffer.Length;
while (newBufferLength < requiredTotal)
{
newBufferLength *= 2;
}
var newBuffer = _pool.Rent(newBufferLength);
Array.Copy(_buffer, newBuffer, _buffer.Length);
_pool.Return(_buffer);
_buffer = newBuffer;
}
}
}
@@ -0,0 +1,6 @@
namespace MQTTnet.Benchmarks
{
public abstract class BaseBenchmark
{
}
}
@@ -0,0 +1,90 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using BenchmarkDotNet.Attributes;
using MQTTnet.Adapter;
using MQTTnet.Internal;
using MQTTnet.Packets;
using System;
using System.IO;
using System.Threading;
using MQTTnet.Diagnostics;
using MQTTnet.Formatter;
namespace MQTTnet.Benchmarks
{
[MemoryDiagnoser]
public sealed class ChannelAdapterBenchmark
{
MqttChannelAdapter _channelAdapter;
int _iterations;
MemoryStream _stream;
MqttPublishPacket _packet;
[GlobalSetup]
public void Setup()
{
_packet = new MqttPublishPacket
{
Topic = "A"
};
var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535));
var serializedPacket = Join(serializer.Encode(_packet).Join());
_iterations = 10000;
_stream = new MemoryStream(_iterations * serializedPacket.Length);
for (var i = 0; i < _iterations; i++)
{
_stream.Write(serializedPacket, 0, serializedPacket.Length);
}
_stream.Position = 0;
var channel = new TestMqttChannel(_stream);
_channelAdapter = new MqttChannelAdapter(channel, serializer, null, new MqttNetEventLogger());
}
[Benchmark]
public void Receive_10000_Messages()
{
_stream.Position = 0;
for (var i = 0; i < 10000; i++)
{
_channelAdapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
}
_stream.Position = 0;
}
[Benchmark]
public void Send_10000_Messages()
{
_stream.Position = 0;
for (var i = 0; i < 10000; i++)
{
_channelAdapter.SendPacketAsync(_packet, CancellationToken.None).GetAwaiter().GetResult();
}
_stream.Position = 0;
}
static byte[] Join(params ArraySegment<byte>[] chunks)
{
var buffer = new MemoryStream();
foreach (var chunk in chunks)
{
buffer.Write(chunk.Array, chunk.Offset, chunk.Count);
}
return buffer.ToArray();
}
}
}
@@ -0,0 +1,22 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Validators;
namespace MQTTnet.Benchmarks.Configurations
{
/// <summary>
/// this options may be used to run benchmarks in debugmode and attach a performance profiler
/// https://benchmarkdotnet.org/Configs/Configs.htm
/// </summary>
public class AllowNonOptimized : BaseConfig
{
public AllowNonOptimized()
{
AddValidator(JitOptimizationsValidator.DontFailOnError); // ALLOW NON-OPTIMIZED DLLS
AddJob(Job.InProcess);
}
}
}
@@ -0,0 +1,19 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using BenchmarkDotNet.Configs;
using System.Linq;
namespace MQTTnet.Benchmarks.Configurations
{
public class BaseConfig : ManualConfig
{
public BaseConfig()
{
AddLogger(DefaultConfig.Instance.GetLoggers().ToArray()); // manual config has no loggers by default
AddExporter(DefaultConfig.Instance.GetExporters().ToArray()); // manual config has no exporters by default
AddColumnProvider(DefaultConfig.Instance.GetColumnProviders().ToArray()); // manual config has no columns by default
}
}
}
@@ -0,0 +1,19 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using BenchmarkDotNet.Environments;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Toolchains.CsProj;
namespace MQTTnet.Benchmarks.Configurations
{
public class RuntimeCompareConfig : BaseConfig
{
public RuntimeCompareConfig()
{
AddJob(Job.Default.WithRuntime(ClrRuntime.Net48));
AddJob(Job.Default.WithRuntime(CoreRuntime.Core50).WithToolchain(CsProjCoreToolchain.NetCoreApp50));
}
}
}
@@ -0,0 +1,91 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using MQTTnet.Diagnostics;
namespace MQTTnet.Benchmarks
{
[SimpleJob(RuntimeMoniker.NetCoreApp50)]
[RPlotExporter]
[MemoryDiagnoser]
public class LoggerBenchmark
{
MqttNetNullLogger _nullLogger;
MqttNetSourceLogger _sourceNullLogger;
MqttNetEventLogger _eventLogger;
MqttNetSourceLogger _sourceEventLogger;
MqttNetEventLogger _eventLoggerNoListener;
MqttNetSourceLogger _sourceEventLoggerNoListener;
bool _useHandler;
[GlobalSetup]
public void Setup()
{
_nullLogger = new MqttNetNullLogger();
_sourceNullLogger = _nullLogger.WithSource("Source");
_eventLogger = new MqttNetEventLogger();
_eventLogger.LogMessagePublished += OnLogMessagePublished;
_sourceEventLogger = _eventLogger.WithSource("Source");
_eventLoggerNoListener = new MqttNetEventLogger();
_sourceEventLoggerNoListener = _eventLoggerNoListener.WithSource("Source");
}
void OnLogMessagePublished(object sender, MqttNetLogMessagePublishedEventArgs eventArgs)
{
if (_useHandler)
{
eventArgs.LogMessage.ToString();
}
}
[Benchmark]
public void Log_10000_Messages_No_Listener()
{
_useHandler = false;
for (var i = 0; i < 10000; i++)
{
_sourceEventLoggerNoListener.Verbose("test log message {0}", "parameter");
}
}
[Benchmark]
public void Log_10000_Messages_With_To_String()
{
_useHandler = true;
for (var i = 0; i < 10000; i++)
{
_sourceEventLogger.Verbose("test log message {0}", "parameter");
}
}
[Benchmark]
public void Log_10000_Messages_Without_To_String()
{
_useHandler = false;
for (var i = 0; i < 10000; i++)
{
_sourceEventLogger.Verbose("test log message {0}", "parameter");
}
}
[Benchmark]
public void Log_10000_Messages_With_NullLogger()
{
for (var i = 0; i < 10000; i++)
{
_sourceNullLogger.Verbose("test log message {0}", "parameter");
}
}
}
}
@@ -0,0 +1,39 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<DebugType>Full</DebugType>
<TargetFrameworks>net5.0;net6.0</TargetFrameworks>
<LangVersion>7.2</LangVersion>
<IsPackable>false</IsPackable>
<EnableNETAnalyzers>false</EnableNETAnalyzers>
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.12.1" />
<PackageReference Include="Microsoft.AspNetCore" Version="2.2.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' != 'net5.0'">
<PackageReference Include="System.IO.Pipelines" Version="4.7.2" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net5.0'">
<PackageReference Include="System.IO.Pipelines" Version="5.0.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net6.0'">
<PackageReference Include="System.IO.Pipelines" Version="5.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj" />
<PackageReference Include="Microsoft.CodeAnalysis.Compilers" Version="4.0.1" />
<PackageReference Include="Microsoft.Net.Compilers" Version="4.0.1" />
<ProjectReference Include="..\MQTTnet.Tests\MQTTnet.Tests.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,234 @@
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using MQTTnet.Client;
using MQTTnet.Server;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace MQTTnet.Benchmarks
{
/// <summary>
/// Create a number of topics, publish, subscribe, and wait for response
/// </summary>
[MemoryDiagnoser]
public class MessageDeliveryBenchmark
{
List<MqttApplicationMessage> _topicPublishMessages;
[Params(1, 5)]
public int NumTopicsPerPublisher;
[Params(1000, 10000)]
public int NumPublishers;
[Params(10)]
public int NumSubscribers;
[Params(5, 10, 20, 50)]
public int NumSubscribedTopicsPerSubscriber;
object _lockMsgCount;
int _messagesReceivedCount;
int _messagesExpectedCount;
CancellationTokenSource _cancellationTokenSource;
MqttServer _mqttServer;
List<MQTTnet.Client.MqttClient> _mqttSubscriberClients;
Dictionary<string, MQTTnet.Client.MqttClient> _mqttPublisherClientsByPublisherName;
Dictionary<string, List<string>> _topicsByPublisher;
Dictionary<string, string> _publisherByTopic;
List<string> _allSubscribedTopics; // Keep track of the subset of topics that are subscribed
[GlobalSetup]
public void Setup()
{
_lockMsgCount = new object();
Dictionary<string, List<string>> singleWildcardTopicsByPublisher;
Dictionary<string, List<string>> multiWildcardTopicsByPublisher;
TopicGenerator.Generate(NumPublishers, NumTopicsPerPublisher, out _topicsByPublisher, out singleWildcardTopicsByPublisher, out multiWildcardTopicsByPublisher);
var topics = _topicsByPublisher.First().Value;
_topicPublishMessages = new List<MqttApplicationMessage>();
// Prepare messages, same for each publisher
foreach (var topic in topics)
{
var message = new MqttApplicationMessageBuilder()
.WithTopic(topic)
.WithPayload(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 })
.Build();
_topicPublishMessages.Add(message);
}
// Create server
var factory = new MqttFactory();
var serverOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
_mqttServer = factory.CreateMqttServer(serverOptions);
_mqttServer.StartAsync().GetAwaiter().GetResult();
// Create publisher clients
_mqttPublisherClientsByPublisherName = new Dictionary<string, MQTTnet.Client.MqttClient>();
foreach (var pt in _topicsByPublisher)
{
var publisherName = pt.Key;
var mqttClient = factory.CreateMqttClient();
var publisherOptions = new MqttClientOptionsBuilder()
.WithTcpServer("localhost")
.WithClientId(publisherName)
.WithKeepAlivePeriod(TimeSpan.FromSeconds(30))
.Build();
mqttClient.ConnectAsync(publisherOptions).GetAwaiter().GetResult();
_mqttPublisherClientsByPublisherName.Add(publisherName, mqttClient);
}
// Create subscriber clients
_mqttSubscriberClients = new List<MQTTnet.Client.MqttClient>();
for (var i = 0; i < NumSubscribers; i++)
{
var mqttSubscriberClient = factory.CreateMqttClient();
_mqttSubscriberClients.Add(mqttSubscriberClient);
var subscriberOptions = new MqttClientOptionsBuilder()
.WithTcpServer("localhost")
.WithClientId("subscriber" + i)
.Build();
mqttSubscriberClient.ApplicationMessageReceivedAsync += r =>
{
// count messages and signal cancellation when expected message count is reached
lock (_lockMsgCount)
{
++_messagesReceivedCount;
if (_messagesReceivedCount == _messagesExpectedCount)
{
_cancellationTokenSource.Cancel();
}
}
return Task.CompletedTask;
};
mqttSubscriberClient.ConnectAsync(subscriberOptions).GetAwaiter().GetResult();
}
List<string> allTopics = new List<string>();
_publisherByTopic = new Dictionary<string, string>();
foreach (var t in _topicsByPublisher)
{
foreach (var topic in t.Value)
{
_publisherByTopic.Add(topic, t.Key);
allTopics.Add(topic);
}
}
// Subscribe to NumSubscribedTopics topics spread across all topics
_allSubscribedTopics = new List<string>();
var totalNumTopics = NumPublishers * NumTopicsPerPublisher;
int topicIndexStep = totalNumTopics / (NumSubscribedTopicsPerSubscriber * NumSubscribers);
if (topicIndexStep * NumSubscribedTopicsPerSubscriber * NumSubscribers != totalNumTopics)
{
throw new System.Exception(
String.Format("The total number of topics must be divisible by the number of subscribed topics across all subscribers. Total number of topics: {0}, topic step: {1}",
totalNumTopics, topicIndexStep
));
}
var topicIndex = 0;
foreach (var mqttSubscriber in _mqttSubscriberClients)
{
for (var i = 0; i < NumSubscribedTopicsPerSubscriber; ++i, topicIndex += topicIndexStep)
{
var topic = allTopics[topicIndex];
_allSubscribedTopics.Add(topic);
var subOptions = new Client.MqttClientSubscribeOptionsBuilder().WithTopicFilter(
new Packets.MqttTopicFilter() { Topic = topic })
.Build();
mqttSubscriber.SubscribeAsync(subOptions).GetAwaiter().GetResult();
}
}
Task.Delay(1000).GetAwaiter().GetResult();
}
/// <summary>
/// Publish messages and wait for messages sent to subscribers
/// </summary>
[Benchmark]
public void DeliverMessages()
{
// There should be one message received per publish for each subscribed topic
_messagesExpectedCount = NumSubscribedTopicsPerSubscriber * NumSubscribers;
// Loop for a while and exchange messages
_messagesReceivedCount = 0;
_cancellationTokenSource = new CancellationTokenSource();
// same payload for all messages
var payload = new byte[] { 1, 2, 3, 4 };
var tasks = new List<Task>();
// publish a message for each subscribed topic
foreach (var topic in _allSubscribedTopics)
{
var message = new MqttApplicationMessageBuilder()
.WithTopic(topic)
.WithPayload(payload)
.Build();
// pick the correct publisher
var publisherName = _publisherByTopic[topic];
var publisherClient = _mqttPublisherClientsByPublisherName[publisherName];
_ = publisherClient.PublishAsync(message);
}
// Wait one message per publish to be received by subscriber (in the subscriber's application message handler)
try
{
Task.Delay(30000, _cancellationTokenSource.Token).GetAwaiter().GetResult();
}
catch
{
}
_cancellationTokenSource.Dispose();
if (_messagesReceivedCount < _messagesExpectedCount)
{
throw new Exception(string.Format("Messages Received Count mismatch, expected {0}, received {1}", _messagesExpectedCount, _messagesReceivedCount));
}
}
[GlobalCleanup]
public void Cleanup()
{
foreach (var mp in _mqttPublisherClientsByPublisherName)
{
var mqttPublisherClient = mp.Value;
mqttPublisherClient.DisconnectAsync().GetAwaiter().GetResult();
mqttPublisherClient.Dispose();
}
_mqttPublisherClientsByPublisherName.Clear();
foreach (var mqttSubscriber in _mqttSubscriberClients)
{
mqttSubscriber.DisconnectAsync().GetAwaiter().GetResult();
mqttSubscriber.Dispose();
}
_mqttSubscriberClients.Clear();
_mqttServer.StopAsync().GetAwaiter().GetResult();
_mqttServer.Dispose();
_mqttServer = null;
}
}
}
@@ -0,0 +1,52 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using MQTTnet.Client;
using MQTTnet.Server;
using MqttClient = MQTTnet.Client.MqttClient;
namespace MQTTnet.Benchmarks
{
[SimpleJob(RuntimeMoniker.NetCoreApp50)]
[RPlotExporter, RankColumn]
[MemoryDiagnoser]
public class MessageProcessingBenchmark
{
MqttServer _mqttServer;
MqttClient _mqttClient;
MqttApplicationMessage _message;
[GlobalSetup]
public void Setup()
{
var serverOptions = new MqttServerOptionsBuilder().Build();
var factory = new MqttFactory();
_mqttServer = factory.CreateMqttServer(serverOptions);
_mqttClient = factory.CreateMqttClient();
_mqttServer.StartAsync().GetAwaiter().GetResult();
var clientOptions = new MqttClientOptionsBuilder()
.WithTcpServer("localhost").Build();
_mqttClient.ConnectAsync(clientOptions).GetAwaiter().GetResult();
_message = new MqttApplicationMessageBuilder()
.WithTopic("A")
.Build();
}
[Benchmark]
public void Send_10000_Messages()
{
for (var i = 0; i < 10000; i++)
{
_mqttClient.PublishAsync(_message).GetAwaiter().GetResult();
}
}
}
}
@@ -0,0 +1,75 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using MQTTnet.Client;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using MQTTnet.AspNetCore.Client;
using MQTTnet.AspNetCore;
using MQTTnet.Diagnostics;
namespace MQTTnet.Benchmarks
{
[SimpleJob(RuntimeMoniker.NetCoreApp50)]
[MemoryDiagnoser]
public class MessageProcessingMqttConnectionContextBenchmark
{
IWebHost _host;
MqttClient _mqttClient;
MqttApplicationMessage _message;
[GlobalSetup]
public void Setup()
{
_host = WebHost.CreateDefaultBuilder()
.UseKestrel(o => o.ListenAnyIP(1883, l => l.UseMqtt()))
.ConfigureServices(services => {
services
.AddHostedMqttServer(mqttServerOptions => mqttServerOptions.WithoutDefaultEndpoint())
.AddMqttConnectionHandler();
})
.Configure(app => {
app.UseMqttServer(s => {
});
})
.Build();
var factory = new MqttFactory();
_mqttClient = factory.CreateMqttClient(new MqttNetEventLogger(), new MqttClientConnectionContextFactory());
_host.StartAsync().GetAwaiter().GetResult();
var clientOptions = new MqttClientOptionsBuilder()
.WithTcpServer("localhost").Build();
_mqttClient.ConnectAsync(clientOptions).GetAwaiter().GetResult();
_message = new MqttApplicationMessageBuilder()
.WithTopic("A")
.Build();
}
[GlobalCleanup]
public void Cleanup()
{
_mqttClient.DisconnectAsync().GetAwaiter().GetResult();
_mqttClient.Dispose();
_host.StopAsync().GetAwaiter().GetResult();
_host.Dispose();
}
[Benchmark]
public void Send_10000_Messages()
{
for (var i = 0; i < 10000; i++)
{
_mqttClient.PublishAsync(_message).GetAwaiter().GetResult();
}
}
}
}
@@ -0,0 +1,93 @@
using System;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using MQTTnet.AspNetCore;
using MQTTnet.Formatter;
using MQTTnet.Tests.Mockups;
namespace MQTTnet.Benchmarks
{
[SimpleJob(RuntimeMoniker.NetCoreApp50)]
[MemoryDiagnoser]
public class MqttPacketReaderWriterBenchmark
{
readonly byte[] _demoPayload = new byte[1024];
byte[] _readPayload;
[GlobalCleanup]
public void GlobalCleanup()
{
}
[GlobalSetup]
public void GlobalSetup()
{
TestEnvironment.EnableLogger = false;
var writer = new MqttBufferWriter(4096, 65535);
writer.WriteString("A relative short string.");
writer.WriteBinaryData(_demoPayload);
writer.WriteByte(0x01);
writer.WriteByte(0x02);
writer.WriteVariableByteInteger(5647382);
writer.WriteString("A relative short string.");
writer.WriteVariableByteInteger(8574489);
writer.WriteBinaryData(_demoPayload);
writer.WriteByte(2);
writer.WriteByte(0x02);
writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug");
writer.WriteBinaryData(_demoPayload);
_readPayload = new ArraySegment<byte>(writer.GetBuffer(), 0, writer.Length).ToArray();
}
[Benchmark]
public void Read_100_000_Messages()
{
var reader = new MqttBufferReader();
reader.SetBuffer(_readPayload, 0, _readPayload.Length);
for (var i = 0; i < 100000; i++)
{
reader.Seek(0);
reader.ReadString();
reader.ReadBinaryData();
reader.ReadByte();
reader.ReadByte();
reader.ReadVariableByteInteger();
reader.ReadString();
reader.ReadVariableByteInteger();
reader.ReadBinaryData();
reader.ReadByte();
reader.ReadByte();
reader.ReadString();
reader.ReadBinaryData();
}
}
[Benchmark]
public void Write_100_000_Messages()
{
var writer = new MqttBufferWriter(4096, 65535);
for (var i = 0; i < 100000; i++)
{
writer.WriteString("A relative short string.");
writer.WriteByte(0x01);
writer.WriteByte(0x02);
writer.WriteVariableByteInteger(5647382);
writer.WriteString("A relative short string.");
writer.WriteVariableByteInteger(8574589);
writer.WriteBinaryData(_demoPayload);
writer.WriteByte(2);
writer.WriteByte(0x02);
writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug");
writer.WriteBinaryData(_demoPayload);
writer.Reset(0);
}
}
}
}
@@ -0,0 +1,88 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using BenchmarkDotNet.Attributes;
using MQTTnet.Channel;
using MQTTnet.Implementations;
using MQTTnet.Server;
using System.Threading;
using System.Threading.Tasks;
using BenchmarkDotNet.Jobs;
using MQTTnet.Client;
using MQTTnet.Diagnostics;
namespace MQTTnet.Benchmarks
{
[SimpleJob(RuntimeMoniker.NetCoreApp50)]
[MemoryDiagnoser]
public sealed class MqttTcpChannelBenchmark
{
MqttServer _mqttServer;
IMqttChannel _serverChannel;
IMqttChannel _clientChannel;
[GlobalSetup]
public void Setup()
{
var factory = new MqttFactory();
var tcpServer = new MqttTcpServerAdapter();
tcpServer.ClientHandler += args =>
{
_serverChannel =
(IMqttChannel)args.GetType().GetField("_channel",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)
.GetValue(args);
return Task.CompletedTask;
};
var serverOptions = new MqttServerOptionsBuilder().Build();
_mqttServer = factory.CreateMqttServer(serverOptions, new[] { tcpServer }, new MqttNetEventLogger());
_mqttServer.StartAsync().GetAwaiter().GetResult();
var clientOptions = new MqttClientOptionsBuilder()
.WithTcpServer("localhost").Build();
var tcpOptions = (MqttClientTcpOptions)clientOptions.ChannelOptions;
_clientChannel = new MqttTcpChannel(new MqttClientOptions { ChannelOptions = tcpOptions });
_clientChannel.ConnectAsync(CancellationToken.None).GetAwaiter().GetResult();
}
[Benchmark]
public async Task Send_10000_Chunks()
{
var size = 5;
var iterations = 10000;
await Task.WhenAll(WriteAsync(iterations, size), ReadAsync(iterations, size));
}
async Task ReadAsync(int iterations, int size)
{
await Task.Yield();
var expected = iterations * size;
long read = 0;
while (read < expected)
{
var readresult = await _clientChannel.ReadAsync(new byte[size], 0, size, CancellationToken.None).ConfigureAwait(false);
read += readresult;
}
}
async Task WriteAsync(int iterations, int size)
{
await Task.Yield();
for (var i = 0; i < iterations; i++)
{
await _serverChannel.WriteAsync(new byte[size], 0, size, CancellationToken.None).ConfigureAwait(false);
}
}
}
}
+83
View File
@@ -0,0 +1,83 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using BenchmarkDotNet.Running;
using MQTTnet.Benchmarks.Configurations;
using MQTTnet.Diagnostics;
namespace MQTTnet.Benchmarks
{
public static class Program
{
public static void Main(string[] args)
{
Console.WriteLine($"MQTTnet - Benchmarks ({TargetFrameworkProvider.TargetFramework})");
Console.WriteLine("--------------------------------------------------------");
Console.WriteLine("1 = MessageProcessingBenchmark");
Console.WriteLine("2 = SerializerBenchmark");
Console.WriteLine("3 = LoggerBenchmark");
Console.WriteLine("4 = TopicFilterComparerBenchmark");
Console.WriteLine("5 = ChannelAdapterBenchmark");
Console.WriteLine("6 = MqttTcpChannelBenchmark");
Console.WriteLine("7 = TcpPipesBenchmark");
Console.WriteLine("8 = MessageProcessingMqttConnectionContextBenchmark");
Console.WriteLine("9 = ServerProcessingBenchmark");
Console.WriteLine("a = MqttPacketReaderWriterBenchmark");
Console.WriteLine("b = RoundtripBenchmark");
Console.WriteLine("c = SubscribeBenchmark");
Console.WriteLine("d = UnsubscribeBenchmark");
Console.WriteLine("e = MessageDeliveryBenchmark");
var pressedKey = Console.ReadKey(true);
switch (pressedKey.KeyChar)
{
case '1':
BenchmarkRunner.Run<MessageProcessingBenchmark>();
break;
case '2':
BenchmarkRunner.Run<SerializerBenchmark>();
break;
case '3':
BenchmarkRunner.Run<LoggerBenchmark>();
break;
case '4':
BenchmarkRunner.Run<TopicFilterComparerBenchmark>();
break;
case '5':
BenchmarkRunner.Run<ChannelAdapterBenchmark>();
break;
case '6':
BenchmarkRunner.Run<MqttTcpChannelBenchmark>();
break;
case '7':
BenchmarkRunner.Run<TcpPipesBenchmark>();
break;
case '8':
BenchmarkRunner.Run<MessageProcessingMqttConnectionContextBenchmark>(new RuntimeCompareConfig());
break;
case '9':
BenchmarkRunner.Run<ServerProcessingBenchmark>();
break;
case 'a':
BenchmarkRunner.Run(typeof(MqttPacketReaderWriterBenchmark));
break;
case 'b':
BenchmarkRunner.Run<RoundtripProcessingBenchmark>();
break;
case 'c':
BenchmarkRunner.Run<SubscribeBenchmark>();
break;
case 'd':
BenchmarkRunner.Run<UnsubscribeBenchmark>();
break;
case 'e':
BenchmarkRunner.Run<MessageDeliveryBenchmark>();
break;
}
Console.ReadLine();
}
}
}
@@ -0,0 +1,30 @@
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using MQTTnet.Tests.Mockups;
using MQTTnet.Tests.Server;
namespace MQTTnet.Benchmarks
{
[SimpleJob(RuntimeMoniker.NetCoreApp50)]
[RPlotExporter, RankColumn]
[MemoryDiagnoser]
public class RoundtripProcessingBenchmark
{
[GlobalSetup]
public void GlobalSetup()
{
TestEnvironment.EnableLogger = false;
}
[GlobalCleanup]
public void GlobalCleanup()
{
}
[Benchmark]
public void Handle_100_000_Messages_In_Receiving_Client()
{
new Load_Tests().Handle_100_000_Messages_In_Receiving_Client().GetAwaiter().GetResult();
}
}
}
@@ -0,0 +1,117 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using BenchmarkDotNet.Attributes;
using MQTTnet.Packets;
using System;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Adapter;
using MQTTnet.Channel;
using MQTTnet.Formatter;
using MQTTnet.Formatter.V3;
using BenchmarkDotNet.Jobs;
using MQTTnet.Diagnostics;
namespace MQTTnet.Benchmarks
{
[SimpleJob(RuntimeMoniker.NetCoreApp50)]
[RPlotExporter]
[MemoryDiagnoser]
public class SerializerBenchmark : BaseBenchmark
{
MqttPacket _packet;
ArraySegment<byte> _serializedPacket;
IMqttPacketFormatter _serializer;
MqttBufferWriter _bufferWriter;
[GlobalSetup]
public void GlobalSetup()
{
_packet = new MqttPublishPacket
{
Topic = "A"
};
_bufferWriter = new MqttBufferWriter(4096, 65535);
_serializer = new MqttV3PacketFormatter(_bufferWriter, MqttProtocolVersion.V311);
_serializedPacket = _serializer.Encode(_packet).Join();
}
[Benchmark]
public void Serialize_10000_Messages()
{
for (var i = 0; i < 10000; i++)
{
_serializer.Encode(_packet);
_bufferWriter.Cleanup();
}
}
[Benchmark]
public void Deserialize_10000_Messages()
{
var channel = new BenchmarkMqttChannel(_serializedPacket);
var reader = new MqttChannelAdapter(channel, new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535)), null, new MqttNetEventLogger());
for (var i = 0; i < 10000; i++)
{
channel.Reset();
var header = reader.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
}
}
class BenchmarkMqttChannel : IMqttChannel
{
readonly ArraySegment<byte> _buffer;
int _position;
public BenchmarkMqttChannel(ArraySegment<byte> buffer)
{
_buffer = buffer;
_position = _buffer.Offset;
}
public string Endpoint { get; } = string.Empty;
public bool IsSecureConnection { get; } = false;
public X509Certificate2 ClientCertificate { get; }
public void Reset()
{
_position = _buffer.Offset;
}
public Task ConnectAsync(CancellationToken cancellationToken)
{
throw new NotSupportedException();
}
public Task DisconnectAsync(CancellationToken cancellationToken)
{
throw new NotSupportedException();
}
public Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
Array.Copy(_buffer.Array, _position, buffer, offset, count);
_position += count;
return Task.FromResult(count);
}
public Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
throw new NotSupportedException();
}
public void Dispose()
{
}
}
}
}
@@ -0,0 +1,36 @@
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using MQTTnet.Tests.Mockups;
using MQTTnet.Tests.Server;
namespace MQTTnet.Benchmarks
{
[SimpleJob(RuntimeMoniker.NetCoreApp50)]
[RPlotExporter, RankColumn]
[MemoryDiagnoser]
public class ServerProcessingBenchmark
{
[GlobalSetup]
public void GlobalSetup()
{
TestEnvironment.EnableLogger = false;
}
[GlobalCleanup]
public void GlobalCleanup()
{
}
[Benchmark]
public void Handle_100_000_Messages_In_Server_MqttClient()
{
new Load_Tests().Handle_100_000_Messages_In_Server().GetAwaiter().GetResult();
}
//[Benchmark]
public void Handle_100_000_Messages_In_Server_LowLevelMqttClient()
{
new Load_Tests().Handle_100_000_Messages_In_Low_Level_Client().GetAwaiter().GetResult();
}
}
}
@@ -0,0 +1,61 @@
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using MQTTnet.Client;
using MQTTnet.Server;
using System.Collections.Generic;
using System.Linq;
namespace MQTTnet.Benchmarks
{
[MemoryDiagnoser]
public class SubscribeBenchmark
{
MqttServer _mqttServer;
MQTTnet.Client.MqttClient _mqttClient;
const int NumPublishers = 1;
const int NumTopicsPerPublisher = 10000;
List<string> _topics;
[GlobalSetup]
public void Setup()
{
TopicGenerator.Generate(NumPublishers, NumTopicsPerPublisher, out var topicsByPublisher, out var singleWildcardTopicsByPublisher, out var multiWildcardTopicsByPublisher);
_topics = topicsByPublisher.Values.First();
var serverOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
var factory = new MqttFactory();
_mqttServer = factory.CreateMqttServer(serverOptions);
_mqttClient = factory.CreateMqttClient();
_mqttServer.StartAsync().GetAwaiter().GetResult();
var clientOptions = new MqttClientOptionsBuilder()
.WithTcpServer("localhost").Build();
_mqttClient.ConnectAsync(clientOptions).GetAwaiter().GetResult();
}
[GlobalCleanup]
public void Cleanup()
{
_mqttClient.DisconnectAsync().GetAwaiter().GetResult();
_mqttServer.StopAsync().GetAwaiter().GetResult();
_mqttServer.Dispose();
}
[Benchmark]
public void Subscribe_10000_Topics()
{
foreach (var topic in _topics)
{
var subscribeOptions = new MqttClientSubscribeOptionsBuilder()
.WithTopicFilter(topic, Protocol.MqttQualityOfServiceLevel.AtMostOnce)
.Build();
_mqttClient.SubscribeAsync(subscribeOptions).GetAwaiter().GetResult();
}
}
}
}
@@ -0,0 +1,80 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.IO.Pipelines;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using MQTTnet.AspNetCore.Client.Tcp;
namespace MQTTnet.Benchmarks
{
[SimpleJob(RuntimeMoniker.NetCoreApp50)]
[MemoryDiagnoser]
public class TcpPipesBenchmark
{
IDuplexPipe _client;
IDuplexPipe _server;
[GlobalSetup]
public void Setup()
{
var server = new TcpListener(IPAddress.Any, 1883);
server.Start(1);
var task = Task.Run(() => server.AcceptSocket());
var clientConnection = new TcpConnection(new IPEndPoint(IPAddress.Loopback, 1883));
clientConnection.StartAsync().GetAwaiter().GetResult();
_client = clientConnection.Transport;
var serverConnection = new TcpConnection(task.GetAwaiter().GetResult());
serverConnection.StartAsync().GetAwaiter().GetResult();
_server = serverConnection.Transport;
}
[Benchmark]
public async Task Send_10000_Chunks_Pipe()
{
var size = 5;
var iterations = 10000;
await Task.WhenAll(WriteAsync(iterations, size), ReadAsync(iterations, size));
}
async Task ReadAsync(int iterations, int size)
{
await Task.Yield();
var expected = iterations * size;
long read = 0;
var input = _client.Input;
while (read < expected)
{
var readresult = await input.ReadAsync(CancellationToken.None).ConfigureAwait(false);
input.AdvanceTo(readresult.Buffer.End);
read += readresult.Buffer.Length;
}
}
async Task WriteAsync(int iterations, int size)
{
await Task.Yield();
var output = _server.Output;
for (var i = 0; i < iterations; i++)
{
await output.WriteAsync(new byte[size], CancellationToken.None).ConfigureAwait(false);
}
}
}
}
@@ -0,0 +1,277 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
namespace MQTTnet.Benchmarks
{
[SimpleJob(RuntimeMoniker.NetCoreApp50)]
[RPlotExporter]
[MemoryDiagnoser]
public class TopicFilterComparerBenchmark
{
static readonly char[] TopicLevelSeparator = { '/' };
readonly string _longTopic =
"AAAAAAAAAAAAAssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssshhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC";
[Benchmark]
public void MqttTopicFilterComparer_10000_LoopMethod()
{
for (var i = 0; i < 100000; i++)
{
MqttTopicFilterComparer.Compare("sport/tennis/player1", "sport/#");
MqttTopicFilterComparer.Compare("sport/tennis/player1/ranking", "sport/#/ranking");
MqttTopicFilterComparer.Compare("sport/tennis/player1/score/wimbledon", "sport/+/player1/#");
MqttTopicFilterComparer.Compare("sport/tennis/player1", "sport/tennis/+");
MqttTopicFilterComparer.Compare("/finance", "+/+");
MqttTopicFilterComparer.Compare("/finance", "/+");
MqttTopicFilterComparer.Compare("/finance", "+");
MqttTopicFilterComparer.Compare(_longTopic, _longTopic);
}
}
[Benchmark]
public void MqttTopicFilterComparer_10000_LoopMethod_Without_Pointer()
{
for (var i = 0; i < 100000; i++)
{
MqttTopicFilterComparerWithoutPointer.Compare("sport/tennis/player1", "sport/#");
MqttTopicFilterComparerWithoutPointer.Compare("sport/tennis/player1/ranking", "sport/#/ranking");
MqttTopicFilterComparerWithoutPointer.Compare("sport/tennis/player1/score/wimbledon", "sport/+/player1/#");
MqttTopicFilterComparerWithoutPointer.Compare("sport/tennis/player1", "sport/tennis/+");
MqttTopicFilterComparerWithoutPointer.Compare("/finance", "+/+");
MqttTopicFilterComparerWithoutPointer.Compare("/finance", "/+");
MqttTopicFilterComparerWithoutPointer.Compare("/finance", "+");
MqttTopicFilterComparerWithoutPointer.Compare(_longTopic, _longTopic);
}
}
[Benchmark]
public void MqttTopicFilterComparer_10000_StringSplitMethod()
{
for (var i = 0; i < 100000; i++)
{
LegacyMethodByStringSplit("sport/tennis/player1", "sport/#");
LegacyMethodByStringSplit("sport/tennis/player1/ranking", "sport/#/ranking");
LegacyMethodByStringSplit("sport/tennis/player1/score/wimbledon", "sport/+/player1/#");
LegacyMethodByStringSplit("sport/tennis/player1", "sport/tennis/+");
LegacyMethodByStringSplit("/finance", "+/+");
LegacyMethodByStringSplit("/finance", "/+");
LegacyMethodByStringSplit("/finance", "+");
MqttTopicFilterComparer.Compare(_longTopic, _longTopic);
}
}
[GlobalSetup]
public void Setup()
{
}
static bool LegacyMethodByStringSplit(string topic, string filter)
{
if (topic == null)
{
throw new ArgumentNullException(nameof(topic));
}
if (filter == null)
{
throw new ArgumentNullException(nameof(filter));
}
if (string.Equals(topic, filter, StringComparison.Ordinal))
{
return true;
}
var fragmentsTopic = topic.Split(TopicLevelSeparator, StringSplitOptions.None);
var fragmentsFilter = filter.Split(TopicLevelSeparator, StringSplitOptions.None);
// # > In either case it MUST be the last character specified in the Topic Filter [MQTT-4.7.1-2].
for (var i = 0; i < fragmentsFilter.Length; i++)
{
if (fragmentsFilter[i] == "+")
{
continue;
}
if (fragmentsFilter[i] == "#")
{
return true;
}
if (i >= fragmentsTopic.Length)
{
return false;
}
if (!string.Equals(fragmentsFilter[i], fragmentsTopic[i], StringComparison.Ordinal))
{
return false;
}
}
return fragmentsTopic.Length == fragmentsFilter.Length;
}
public static class MqttTopicFilterComparerWithoutPointer
{
public const char LevelSeparator = '/';
public const char MultiLevelWildcard = '#';
public const char SingleLevelWildcard = '+';
public const char ReservedTopicPrefix = '$';
public static MqttTopicFilterCompareResult Compare(string topic, string filter)
{
if (string.IsNullOrEmpty(topic))
{
return MqttTopicFilterCompareResult.TopicInvalid;
}
if (string.IsNullOrEmpty(filter))
{
return MqttTopicFilterCompareResult.FilterInvalid;
}
var filterOffset = 0;
var filterLength = filter.Length;
var topicOffset = 0;
var topicLength = topic.Length;
var topicPointer = topic;
var filterPointer = filter;
var isMultiLevelFilter = filterPointer[filterLength - 1] == MultiLevelWildcard;
var isReservedTopic = topicPointer[0] == ReservedTopicPrefix;
if (isReservedTopic && filterLength == 1 && isMultiLevelFilter)
{
// It is not allowed to receive i.e. '$foo/bar' with filter '#'.
return MqttTopicFilterCompareResult.NoMatch;
}
if (isReservedTopic && filterPointer[0] == SingleLevelWildcard)
{
// It is not allowed to receive i.e. '$SYS/monitor/Clients' with filter '+/monitor/Clients'.
return MqttTopicFilterCompareResult.NoMatch;
}
if (filterLength == 1 && isMultiLevelFilter)
{
// Filter '#' matches basically everything.
return MqttTopicFilterCompareResult.IsMatch;
}
// Go through the filter char by char.
while (filterOffset < filterLength && topicOffset < topicLength)
{
// Check if the current char is a multi level wildcard. The char is only allowed
// at the very las position.
if (filterPointer[filterOffset] == MultiLevelWildcard && filterOffset != filterLength - 1)
{
return MqttTopicFilterCompareResult.FilterInvalid;
}
if (filterPointer[filterOffset] == topicPointer[topicOffset])
{
if (topicOffset == topicLength - 1)
{
// Check for e.g. "foo" matching "foo/#"
if (filterOffset == filterLength - 3 && filterPointer[filterOffset + 1] == LevelSeparator && isMultiLevelFilter)
{
return MqttTopicFilterCompareResult.IsMatch;
}
// Check for e.g. "foo/" matching "foo/#"
if (filterOffset == filterLength - 2 && filterPointer[filterOffset] == LevelSeparator && isMultiLevelFilter)
{
return MqttTopicFilterCompareResult.IsMatch;
}
}
filterOffset++;
topicOffset++;
// Check if the end was reached and i.e. "foo/bar" matches "foo/bar"
if (filterOffset == filterLength && topicOffset == topicLength)
{
return MqttTopicFilterCompareResult.IsMatch;
}
var endOfTopic = topicOffset == topicLength;
if (endOfTopic && filterOffset == filterLength - 1 && filterPointer[filterOffset] == SingleLevelWildcard)
{
if (filterOffset > 0 && filterPointer[filterOffset - 1] != LevelSeparator)
{
return MqttTopicFilterCompareResult.FilterInvalid;
}
return MqttTopicFilterCompareResult.IsMatch;
}
}
else
{
if (filterPointer[filterOffset] == SingleLevelWildcard)
{
// Check for invalid "+foo" or "a/+foo" subscription
if (filterOffset > 0 && filterPointer[filterOffset - 1] != LevelSeparator)
{
return MqttTopicFilterCompareResult.FilterInvalid;
}
// Check for bad "foo+" or "foo+/a" subscription
if (filterOffset < filterLength - 1 && filterPointer[filterOffset + 1] != LevelSeparator)
{
return MqttTopicFilterCompareResult.FilterInvalid;
}
filterOffset++;
while (topicOffset < topicLength && topicPointer[topicOffset] != LevelSeparator)
{
topicOffset++;
}
if (topicOffset == topicLength && filterOffset == filterLength)
{
return MqttTopicFilterCompareResult.IsMatch;
}
}
else if (filterPointer[filterOffset] == MultiLevelWildcard)
{
if (filterOffset > 0 && filterPointer[filterOffset - 1] != LevelSeparator)
{
return MqttTopicFilterCompareResult.FilterInvalid;
}
if (filterOffset + 1 != filterLength)
{
return MqttTopicFilterCompareResult.FilterInvalid;
}
return MqttTopicFilterCompareResult.IsMatch;
}
else
{
// Check for e.g. "foo/bar" matching "foo/+/#".
if (filterOffset > 0 && filterOffset + 2 == filterLength && topicOffset == topicLength && filterPointer[filterOffset - 1] == SingleLevelWildcard &&
filterPointer[filterOffset] == LevelSeparator && isMultiLevelFilter)
{
return MqttTopicFilterCompareResult.IsMatch;
}
return MqttTopicFilterCompareResult.NoMatch;
}
}
}
return MqttTopicFilterCompareResult.NoMatch;
}
}
}
}
@@ -0,0 +1,81 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace MQTTnet.Benchmarks
{
public class TopicGenerator
{
public static void Generate(
int numPublishers, int numTopicsPerPublisher,
out Dictionary<string, List<string>> topicsByPublisher,
out Dictionary<string, List<string>> singleWildcardTopicsByPublisher,
out Dictionary<string, List<string>> multiWildcardTopicsByPublisher
)
{
topicsByPublisher = new Dictionary<string, List<string>>();
singleWildcardTopicsByPublisher = new Dictionary<string, List<string>>();
multiWildcardTopicsByPublisher = new Dictionary<string, List<string>>();
// Find some reasonable distribution across three topic levels
var topicsPerLevel = (int)Math.Pow(numTopicsPerPublisher, (1.0 / 3.0));
if (topicsPerLevel <= 0)
{
topicsPerLevel = 1;
}
int numLevel1Topics = topicsPerLevel;
int numLevel2Topics = topicsPerLevel;
var maxNumLevel3Topics = 1 + (int)((double)numTopicsPerPublisher / numLevel1Topics / numLevel2Topics);
if (maxNumLevel3Topics <= 0)
{
maxNumLevel3Topics = 1;
}
for (var p = 0; p < numPublishers; ++p)
{
int publisherTopicCount = 0;
var publisherName = "pub" + p;
for (var l1 = 0; l1 < numLevel1Topics; ++l1)
{
for (var l2 = 0; l2 < numLevel2Topics; ++l2)
{
for (var l3 = 0; l3 < maxNumLevel3Topics; ++l3)
{
if (publisherTopicCount >= numTopicsPerPublisher)
break;
var topic = string.Format("{0}/building{1}/level{2}/sensor{3}", publisherName, l1 + 1, l2 + 1, l3 + 1);
AddPublisherTopic(publisherName, topic, topicsByPublisher);
if (l2 == 0)
{
var singleWildcardTopic = string.Format("{0}/building{1}/+/sensor{2}", publisherName, l1 + 1, l3 + 1);
AddPublisherTopic(publisherName, singleWildcardTopic, singleWildcardTopicsByPublisher);
}
if ((l1 == 0) && (l3 == 0))
{
var multiWildcardTopic = string.Format("{0}/+/level{1}/+", publisherName, l2 + 1);
AddPublisherTopic(publisherName, multiWildcardTopic, multiWildcardTopicsByPublisher);
}
++publisherTopicCount;
}
}
}
}
}
static void AddPublisherTopic(string publisherName, string topic, Dictionary<string, List<string>> topicsByPublisher)
{
List<string> topicList;
if (!topicsByPublisher.TryGetValue(publisherName, out topicList))
{
topicList = new List<string>();
topicsByPublisher.Add(publisherName, topicList);
}
topicList.Add(topic);
}
}
}
@@ -0,0 +1,61 @@
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using MQTTnet.Client;
using MQTTnet.Server;
using System.Collections.Generic;
using System.Linq;
namespace MQTTnet.Benchmarks
{
[MemoryDiagnoser]
public class UnsubscribeBenchmark
{
MqttServer _mqttServer;
MQTTnet.Client.MqttClient _mqttClient;
const int NumPublishers = 1;
const int NumTopicsPerPublisher = 10000;
List<string> _topics;
[GlobalSetup]
public void Setup()
{
TopicGenerator.Generate(NumPublishers, NumTopicsPerPublisher, out var topicsByPublisher, out var singleWildcardTopicsByPublisher, out var multiWildcardTopicsByPublisher);
_topics = topicsByPublisher.Values.First();
var serverOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
var factory = new MqttFactory();
_mqttServer = factory.CreateMqttServer(serverOptions);
_mqttClient = factory.CreateMqttClient();
_mqttServer.StartAsync().GetAwaiter().GetResult();
var clientOptions = new MqttClientOptionsBuilder()
.WithTcpServer("localhost").Build();
_mqttClient.ConnectAsync(clientOptions).GetAwaiter().GetResult();
foreach (var topic in _topics)
{
var subscribeOptions = new MqttClientSubscribeOptionsBuilder()
.WithTopicFilter(topic, Protocol.MqttQualityOfServiceLevel.AtMostOnce)
.Build();
_mqttClient.SubscribeAsync(subscribeOptions).GetAwaiter().GetResult();
}
}
[Benchmark]
public void Unsubscribe_10000_Topics()
{
foreach (var topic in _topics)
{
var unsubscribeOptions = new MqttClientUnsubscribeOptionsBuilder()
.WithTopicFilter(topic)
.Build();
_mqttClient.UnsubscribeAsync(unsubscribeOptions).GetAwaiter().GetResult();
}
}
}
}
+54
View File
@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="BenchmarkDotNet" version="0.10.14" targetFramework="net462" />
<package id="BenchmarkDotNet.Core" version="0.10.14" targetFramework="net462" />
<package id="BenchmarkDotNet.Toolchains.Roslyn" version="0.10.14" targetFramework="net462" />
<package id="Microsoft.CodeAnalysis.Analyzers" version="1.1.0" targetFramework="net462" />
<package id="Microsoft.CodeAnalysis.Common" version="2.6.1" targetFramework="net462" />
<package id="Microsoft.CodeAnalysis.CSharp" version="2.6.1" targetFramework="net462" />
<package id="Microsoft.DotNet.InternalAbstractions" version="1.0.0" targetFramework="net462" />
<package id="Microsoft.DotNet.PlatformAbstractions" version="1.1.1" targetFramework="net462" />
<package id="Microsoft.Win32.Registry" version="4.3.0" targetFramework="net462" />
<package id="System.AppContext" version="4.3.0" targetFramework="net462" />
<package id="System.Collections" version="4.3.0" targetFramework="net462" />
<package id="System.Collections.Concurrent" version="4.3.0" targetFramework="net462" />
<package id="System.Collections.Immutable" version="1.3.1" targetFramework="net462" />
<package id="System.Console" version="4.3.0" targetFramework="net462" />
<package id="System.Diagnostics.Debug" version="4.3.0" targetFramework="net462" />
<package id="System.Diagnostics.FileVersionInfo" version="4.3.0" targetFramework="net462" />
<package id="System.Diagnostics.StackTrace" version="4.3.0" targetFramework="net462" />
<package id="System.Diagnostics.Tools" version="4.3.0" targetFramework="net462" />
<package id="System.Dynamic.Runtime" version="4.3.0" targetFramework="net462" />
<package id="System.Globalization" version="4.3.0" targetFramework="net462" />
<package id="System.IO.Compression" version="4.3.0" targetFramework="net462" />
<package id="System.IO.FileSystem" version="4.3.0" targetFramework="net462" />
<package id="System.IO.FileSystem.Primitives" version="4.3.0" targetFramework="net462" />
<package id="System.Linq" version="4.3.0" targetFramework="net462" />
<package id="System.Linq.Expressions" version="4.3.0" targetFramework="net462" />
<package id="System.Reflection" version="4.3.0" targetFramework="net462" />
<package id="System.Reflection.Metadata" version="1.4.2" targetFramework="net462" />
<package id="System.Resources.ResourceManager" version="4.3.0" targetFramework="net462" />
<package id="System.Runtime" version="4.3.0" targetFramework="net462" />
<package id="System.Runtime.Extensions" version="4.3.0" targetFramework="net462" />
<package id="System.Runtime.InteropServices" version="4.3.0" targetFramework="net462" />
<package id="System.Runtime.Numerics" version="4.3.0" targetFramework="net462" />
<package id="System.Security.Cryptography.Algorithms" version="4.3.0" targetFramework="net462" />
<package id="System.Security.Cryptography.Encoding" version="4.3.0" targetFramework="net462" />
<package id="System.Security.Cryptography.Primitives" version="4.3.0" targetFramework="net462" />
<package id="System.Security.Cryptography.X509Certificates" version="4.3.0" targetFramework="net462" />
<package id="System.Text.Encoding" version="4.3.0" targetFramework="net462" />
<package id="System.Text.Encoding.CodePages" version="4.3.0" targetFramework="net462" />
<package id="System.Text.Encoding.Extensions" version="4.3.0" targetFramework="net462" />
<package id="System.Threading" version="4.3.0" targetFramework="net462" />
<package id="System.Threading.Tasks" version="4.3.0" targetFramework="net462" />
<package id="System.Threading.Tasks.Extensions" version="4.3.0" targetFramework="net462" />
<package id="System.Threading.Tasks.Parallel" version="4.3.0" targetFramework="net462" />
<package id="System.Threading.Thread" version="4.3.0" targetFramework="net462" />
<package id="System.ValueTuple" version="4.3.0" targetFramework="net462" />
<package id="System.Xml.ReaderWriter" version="4.3.0" targetFramework="net462" />
<package id="System.Xml.XDocument" version="4.3.0" targetFramework="net462" />
<package id="System.Xml.XmlDocument" version="4.3.0" targetFramework="net462" />
<package id="System.Xml.XmlSerializer" version="4.3.0" targetFramework="net462" />
<package id="System.Xml.XPath" version="4.3.0" targetFramework="net462" />
<package id="System.Xml.XPath.XDocument" version="4.3.0" targetFramework="net462" />
</packages>
@@ -1,8 +1,12 @@
using System;
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
namespace MQTTnet.Extensions.ManagedClient
{
public class ApplicationMessageProcessedEventArgs : EventArgs
public sealed class ApplicationMessageProcessedEventArgs : EventArgs
{
public ApplicationMessageProcessedEventArgs(ManagedMqttApplicationMessage applicationMessage, Exception exception)
{
@@ -11,9 +15,10 @@ namespace MQTTnet.Extensions.ManagedClient
}
public ManagedMqttApplicationMessage ApplicationMessage { get; }
/// <summary>
/// Then this is _null_ the message was processed successfully without any error.
/// </summary>
public Exception Exception { get; }
public bool HasFailed => Exception != null;
public bool HasSucceeded => Exception == null;
}
}
@@ -1,31 +0,0 @@
using System;
using System.Threading.Tasks;
namespace MQTTnet.Extensions.ManagedClient
{
public class ApplicationMessageProcessedHandlerDelegate : IApplicationMessageProcessedHandler
{
private readonly Func<ApplicationMessageProcessedEventArgs, Task> _handler;
public ApplicationMessageProcessedHandlerDelegate(Action<ApplicationMessageProcessedEventArgs> handler)
{
if (handler == null) throw new ArgumentNullException(nameof(handler));
_handler = context =>
{
handler(context);
return Task.FromResult(0);
};
}
public ApplicationMessageProcessedHandlerDelegate(Func<ApplicationMessageProcessedEventArgs, Task> handler)
{
_handler = handler ?? throw new ArgumentNullException(nameof(handler));
}
public Task HandleApplicationMessageProcessedAsync(ApplicationMessageProcessedEventArgs eventArgs)
{
return _handler(eventArgs);
}
}
}
@@ -1,4 +1,8 @@
using System;
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
namespace MQTTnet.Extensions.ManagedClient
{
@@ -1,4 +1,8 @@
using System;
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Threading.Tasks;
namespace MQTTnet.Extensions.ManagedClient
@@ -0,0 +1,21 @@
using System;
using MQTTnet.Client;
namespace MQTTnet.Extensions.ManagedClient
{
public sealed class ConnectingFailedEventArgs : EventArgs
{
public ConnectingFailedEventArgs(MqttClientConnectResult connectResult, Exception exception)
{
ConnectResult = connectResult;
Exception = exception;
}
/// <summary>
/// This is null when the connection was failing and the server was not reachable.
/// </summary>
public MqttClientConnectResult ConnectResult { get; }
public Exception Exception { get; }
}
}
@@ -1,31 +0,0 @@
using System;
using System.Threading.Tasks;
namespace MQTTnet.Extensions.ManagedClient
{
public class ConnectingFailedHandlerDelegate : IConnectingFailedHandler
{
private readonly Func<ManagedProcessFailedEventArgs, Task> _handler;
public ConnectingFailedHandlerDelegate(Action<ManagedProcessFailedEventArgs> handler)
{
if (handler == null) throw new ArgumentNullException(nameof(handler));
_handler = eventArgs =>
{
handler(eventArgs);
return Task.FromResult(0);
};
}
public ConnectingFailedHandlerDelegate(Func<ManagedProcessFailedEventArgs, Task> handler)
{
_handler = handler ?? throw new ArgumentNullException(nameof(handler));
}
public Task HandleConnectingFailedAsync(ManagedProcessFailedEventArgs eventArgs)
{
return _handler(eventArgs);
}
}
}
@@ -1,9 +0,0 @@
using System.Threading.Tasks;
namespace MQTTnet.Extensions.ManagedClient
{
public interface IApplicationMessageProcessedHandler
{
Task HandleApplicationMessageProcessedAsync(ApplicationMessageProcessedEventArgs eventArgs);
}
}
@@ -1,4 +1,8 @@
using System.Threading.Tasks;
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Threading.Tasks;
namespace MQTTnet.Extensions.ManagedClient
{
@@ -1,9 +0,0 @@
using System.Threading.Tasks;
namespace MQTTnet.Extensions.ManagedClient
{
public interface IConnectingFailedHandler
{
Task HandleConnectingFailedAsync(ManagedProcessFailedEventArgs eventArgs);
}
}
@@ -1,51 +0,0 @@
using MQTTnet.Client.Connecting;
using MQTTnet.Client.Disconnecting;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Client;
namespace MQTTnet.Extensions.ManagedClient
{
public interface IManagedMqttClient : IApplicationMessageReceiver, IApplicationMessagePublisher, IDisposable
{
/// <summary>
/// Gets the internally used MQTT client.
/// This property should be used with caution because manipulating the internal client might break the managed client.
/// </summary>
IMqttClient InternalClient { get; }
bool IsStarted { get; }
bool IsConnected { get; }
int PendingApplicationMessagesCount { get; }
IManagedMqttClientOptions Options { get; }
IMqttClientConnectedHandler ConnectedHandler { get; set; }
IMqttClientDisconnectedHandler DisconnectedHandler { get; set; }
IApplicationMessageProcessedHandler ApplicationMessageProcessedHandler { get; set; }
IApplicationMessageSkippedHandler ApplicationMessageSkippedHandler { get; set; }
IConnectingFailedHandler ConnectingFailedHandler { get; set; }
ISynchronizingSubscriptionsFailedHandler SynchronizingSubscriptionsFailedHandler { get; set; }
Task StartAsync(IManagedMqttClientOptions options);
Task StopAsync();
Task PingAsync(CancellationToken cancellationToken);
Task SubscribeAsync(IEnumerable<MqttTopicFilter> topicFilters);
Task UnsubscribeAsync(IEnumerable<string> topics);
Task PublishAsync(ManagedMqttApplicationMessage applicationMessages);
}
}
@@ -1,27 +0,0 @@
using System;
using MQTTnet.Client.Options;
using MQTTnet.Server;
namespace MQTTnet.Extensions.ManagedClient
{
public interface IManagedMqttClientOptions
{
IMqttClientOptions ClientOptions { get; }
TimeSpan AutoReconnectDelay { get; }
TimeSpan ConnectionCheckInterval { get; }
IManagedMqttClientStorage Storage { get; }
int MaxPendingMessages { get; }
MqttPendingMessagesOverflowStrategy PendingMessagesOverflowStrategy { get; }
/// <summary>
/// Defines the maximum amount of topic filters which will be sent in a SUBSCRIBE/UNSUBSCRIBE packet.
/// Amazon AWS limits this number to 8. The default is int.MaxValue.
/// </summary>
int MaxTopicFiltersInSubscribeUnsubscribePackets { get; }
}
}
@@ -1,4 +1,8 @@
using System.Collections.Generic;
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using System.Threading.Tasks;
namespace MQTTnet.Extensions.ManagedClient
@@ -1,4 +1,8 @@
using System.Threading.Tasks;
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Threading.Tasks;
namespace MQTTnet.Extensions.ManagedClient
{
@@ -1,40 +1,72 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;net5.0</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' AND '$(SolutionName)' != 'MQTTnet.noUWP' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
<Product />
<Company />
<Authors />
<PackageId />
<GeneratePackageOnBuild>False</GeneratePackageOnBuild>
<SignAssembly>false</SignAssembly>
<DelaySign>false</DelaySign>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>
<PropertyGroup>
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
<AssemblyName>MQTTnet.Extensions.ManagedClient</AssemblyName>
<RootNamespace>MQTTnet.Extensions.ManagedClient</RootNamespace>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<Company>The contributors of MQTTnet</Company>
<Product>MQTTnet</Product>
<Description>This is an extension library which provides a managed MQTT client with additional features using MQTTnet.</Description>
<Authors>The contributors of MQTTnet</Authors>
<PackageId>MQTTnet.Extensions.ManagedClient</PackageId>
<SignAssembly>false</SignAssembly>
<DelaySign>false</DelaySign>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<Copyright>Christian Kratky 2016-2022</Copyright>
<PackageProjectUrl>https://github.com/dotnet/MQTTnet</PackageProjectUrl>
<RepositoryUrl>https://github.com/dotnet/MQTTnet.git</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageTags>MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin Blazor</PackageTags>
<NeutralLanguage>en-US</NeutralLanguage>
<EnableNETAnalyzers>false</EnableNETAnalyzers>
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
<PackageIcon>nuget.png</PackageIcon>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<PackageReleaseNotes>For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).</PackageReleaseNotes>
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0'">
<CopyLocalLockFileAssemblies>false</CopyLocalLockFileAssemblies>
<NugetTargetMoniker>UAP,Version=v10.0</NugetTargetMoniker>
<TargetPlatformIdentifier>UAP</TargetPlatformIdentifier>
<TargetPlatformVersion>10.0.18362.0</TargetPlatformVersion>
<TargetPlatformMinVersion>10.0.10240.0</TargetPlatformMinVersion>
<TargetFrameworkIdentifier>.NETCore</TargetFrameworkIdentifier>
<TargetFrameworkVersion>v5.0</TargetFrameworkVersion>
<DefineConstants>$(DefineConstants);WINDOWS_UWP</DefineConstants>
<DefaultLanguage>en</DefaultLanguage>
<LanguageTargets>$(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets</LanguageTargets>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0'">
<CopyLocalLockFileAssemblies>false</CopyLocalLockFileAssemblies>
<NugetTargetMoniker>UAP,Version=v10.0</NugetTargetMoniker>
<TargetPlatformIdentifier>UAP</TargetPlatformIdentifier>
<TargetPlatformVersion>10.0.18362.0</TargetPlatformVersion>
<TargetPlatformMinVersion>10.0.10240.0</TargetPlatformMinVersion>
<TargetFrameworkIdentifier>.NETCore</TargetFrameworkIdentifier>
<TargetFrameworkVersion>v5.0</TargetFrameworkVersion>
<DefineConstants>$(DefineConstants);WINDOWS_UWP</DefineConstants>
<DefaultLanguage>en</DefaultLanguage>
<LanguageTargets>$(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets</LanguageTargets>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
<None Include="..\..\Images\nuget.png">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MQTTnet\MQTTnet.csproj" />
</ItemGroup>
<ItemGroup>
<None Include="..\..\LICENSE">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MQTTnet\MQTTnet.csproj" />
</ItemGroup>
</Project>
@@ -1,4 +1,8 @@
using System;
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
namespace MQTTnet.Extensions.ManagedClient
{
@@ -1,4 +1,8 @@
using System;
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
namespace MQTTnet.Extensions.ManagedClient
{
@@ -1,162 +1,155 @@
using MQTTnet.Client;
using MQTTnet.Client.Connecting;
using MQTTnet.Client.Disconnecting;
using MQTTnet.Client.Publishing;
using MQTTnet.Client.Receiving;
using MQTTnet.Diagnostics;
using MQTTnet.Exceptions;
using MQTTnet.Internal;
using MQTTnet.Protocol;
using MQTTnet.Server;
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Diagnostics.Logger;
using MQTTnet.Client;
using MQTTnet.Diagnostics;
using MQTTnet.Exceptions;
using MQTTnet.Internal;
using MQTTnet.Packets;
using MQTTnet.Protocol;
using MQTTnet.Server;
using MqttClient = MQTTnet.Client.MqttClient;
namespace MQTTnet.Extensions.ManagedClient
{
public class ManagedMqttClient : Disposable, IManagedMqttClient
public sealed class ManagedMqttClient : Disposable
{
readonly AsyncEvent<ApplicationMessageProcessedEventArgs> _applicationMessageProcessedEvent = new AsyncEvent<ApplicationMessageProcessedEventArgs>();
readonly AsyncEvent<ConnectingFailedEventArgs> _connectingFailedEvent = new AsyncEvent<ConnectingFailedEventArgs>();
readonly AsyncEvent<EventArgs> _connectionStateChangedEvent = new AsyncEvent<EventArgs>();
readonly MqttNetSourceLogger _logger;
readonly BlockingQueue<ManagedMqttApplicationMessage> _messageQueue = new BlockingQueue<ManagedMqttApplicationMessage>();
readonly AsyncLock _messageQueueLock = new AsyncLock();
/// <summary>
/// The subscriptions are managed in 2 separate buckets:
/// <see cref="_subscriptions"/> and <see cref="_unsubscriptions"/> are processed during normal operation
/// and are moved to the <see cref="_reconnectSubscriptions"/> when they get processed. They can be accessed by
/// any thread and are therefore mutex'ed. <see cref="_reconnectSubscriptions"/> get sent to the broker
/// at reconnect and are solely owned by <see cref="MaintainConnectionAsync"/>.
/// The subscriptions are managed in 2 separate buckets:
/// <see
/// cref="_subscriptions" />
/// and
/// <see
/// cref="_unsubscriptions" />
/// are processed during normal operation
/// and are moved to the
/// <see
/// cref="_reconnectSubscriptions" />
/// when they get processed. They can be accessed by
/// any thread and are therefore mutex'ed.
/// <see
/// cref="_reconnectSubscriptions" />
/// get sent to the broker
/// at reconnect and are solely owned by
/// <see
/// cref="MaintainConnectionAsync" />
/// .
/// </summary>
readonly Dictionary<string, MqttQualityOfServiceLevel> _reconnectSubscriptions = new Dictionary<string, MqttQualityOfServiceLevel>();
readonly Dictionary<string, MqttQualityOfServiceLevel> _subscriptions = new Dictionary<string, MqttQualityOfServiceLevel>();
readonly HashSet<string> _unsubscriptions = new HashSet<string>();
readonly SemaphoreSlim _subscriptionsQueuedSignal = new SemaphoreSlim(0);
readonly MqttNetSourceLogger _logger;
readonly AsyncLock _messageQueueLock = new AsyncLock();
readonly HashSet<string> _unsubscriptions = new HashSet<string>();
CancellationTokenSource _connectionCancellationToken;
CancellationTokenSource _publishingCancellationToken;
Task _maintainConnectionTask;
CancellationTokenSource _publishingCancellationToken;
ManagedMqttClientStorageManager _storageManager;
public ManagedMqttClient(IMqttClient mqttClient, IMqttNetLogger logger)
public ManagedMqttClient(MqttClient mqttClient, IMqttNetLogger logger)
{
InternalClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
if (logger == null) throw new ArgumentNullException(nameof(logger));
if (logger == null)
{
throw new ArgumentNullException(nameof(logger));
}
_logger = logger.WithSource(nameof(ManagedMqttClient));
}
public event Func<ApplicationMessageProcessedEventArgs, Task> ApplicationMessageProcessedAsync
{
add => _applicationMessageProcessedEvent.AddHandler(value);
remove => _applicationMessageProcessedEvent.RemoveHandler(value);
}
public event Func<MqttApplicationMessageReceivedEventArgs, Task> ApplicationMessageReceivedAsync
{
add => InternalClient.ApplicationMessageReceivedAsync += value;
remove => InternalClient.ApplicationMessageReceivedAsync -= value;
}
public event Func<EventArgs, Task> ConnectedAsync
{
add => InternalClient.ConnectedAsync += value;
remove => InternalClient.ConnectedAsync -= value;
}
public event Func<ConnectingFailedEventArgs, Task> ConnectingFailedAsync
{
add => _connectingFailedEvent.AddHandler(value);
remove => _connectingFailedEvent.RemoveHandler(value);
}
public event Func<EventArgs, Task> ConnectionStateChangedAsync
{
add => _connectionStateChangedEvent.AddHandler(value);
remove => _connectionStateChangedEvent.RemoveHandler(value);
}
public event Func<EventArgs, Task> DisconnectedAsync
{
add => InternalClient.DisconnectedAsync += value;
remove => InternalClient.DisconnectedAsync -= value;
}
public IApplicationMessageSkippedHandler ApplicationMessageSkippedHandler { get; set; }
public MqttClient InternalClient { get; }
public bool IsConnected => InternalClient.IsConnected;
public bool IsStarted => _connectionCancellationToken != null;
public IMqttClient InternalClient { get; }
public ManagedMqttClientOptions Options { get; private set; }
public int PendingApplicationMessagesCount => _messageQueue.Count;
public IManagedMqttClientOptions Options { get; private set; }
public IMqttClientConnectedHandler ConnectedHandler
{
get => InternalClient.ConnectedHandler;
set => InternalClient.ConnectedHandler = value;
}
public IMqttClientDisconnectedHandler DisconnectedHandler
{
get => InternalClient.DisconnectedHandler;
set => InternalClient.DisconnectedHandler = value;
}
public IMqttApplicationMessageReceivedHandler ApplicationMessageReceivedHandler
{
get => InternalClient.ApplicationMessageReceivedHandler;
set => InternalClient.ApplicationMessageReceivedHandler = value;
}
public IApplicationMessageProcessedHandler ApplicationMessageProcessedHandler { get; set; }
public IApplicationMessageSkippedHandler ApplicationMessageSkippedHandler { get; set; }
public IConnectingFailedHandler ConnectingFailedHandler { get; set; }
public ISynchronizingSubscriptionsFailedHandler SynchronizingSubscriptionsFailedHandler { get; set; }
public async Task StartAsync(IManagedMqttClientOptions options)
public async Task EnqueueAsync(MqttApplicationMessage applicationMessage)
{
ThrowIfDisposed();
if (options == null) throw new ArgumentNullException(nameof(options));
if (options.ClientOptions == null) throw new ArgumentException("The client options are not set.", nameof(options));
if (!_maintainConnectionTask?.IsCompleted ?? false) throw new InvalidOperationException("The managed client is already started.");
Options = options;
if (options.Storage != null)
if (applicationMessage == null)
{
_storageManager = new ManagedMqttClientStorageManager(options.Storage);
var messages = await _storageManager.LoadQueuedMessagesAsync().ConfigureAwait(false);
foreach (var message in messages)
{
_messageQueue.Enqueue(message);
}
throw new ArgumentNullException(nameof(applicationMessage));
}
var cancellationTokenSource = new CancellationTokenSource();
var cancellationToken = cancellationTokenSource.Token;
_connectionCancellationToken = cancellationTokenSource;
_maintainConnectionTask = Task.Run(() => MaintainConnectionAsync(cancellationToken), cancellationToken);
_maintainConnectionTask.RunInBackground(_logger);
_logger.Info("Started");
var managedMqttApplicationMessage = new ManagedMqttApplicationMessageBuilder().WithApplicationMessage(applicationMessage);
await EnqueueAsync(managedMqttApplicationMessage.Build()).ConfigureAwait(false);
}
public async Task StopAsync()
public async Task EnqueueAsync(ManagedMqttApplicationMessage applicationMessage)
{
ThrowIfDisposed();
StopPublishing();
StopMaintainingConnection();
_messageQueue.Clear();
if (_maintainConnectionTask != null)
if (applicationMessage == null)
{
await Task.WhenAny(_maintainConnectionTask);
_maintainConnectionTask = null;
throw new ArgumentNullException(nameof(applicationMessage));
}
}
public Task PingAsync(CancellationToken cancellationToken)
{
return InternalClient.PingAsync(cancellationToken);
}
public async Task<MqttClientPublishResult> PublishAsync(MqttApplicationMessage applicationMessage, CancellationToken cancellationToken)
{
ThrowIfDisposed();
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
await PublishAsync(new ManagedMqttApplicationMessageBuilder().WithApplicationMessage(applicationMessage).Build()).ConfigureAwait(false);
return new MqttClientPublishResult();
}
public async Task PublishAsync(ManagedMqttApplicationMessage applicationMessage)
{
ThrowIfDisposed();
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
if (Options == null) throw new InvalidOperationException("call StartAsync before publishing messages");
if (Options == null)
{
throw new InvalidOperationException("call StartAsync before publishing messages");
}
MqttTopicValidator.ThrowIfInvalid(applicationMessage.ApplicationMessage.Topic);
@@ -210,11 +203,77 @@ namespace MQTTnet.Extensions.ManagedClient
}
}
public Task SubscribeAsync(IEnumerable<MqttTopicFilter> topicFilters)
public Task PingAsync(CancellationToken cancellationToken)
{
return InternalClient.PingAsync(cancellationToken);
}
public async Task StartAsync(ManagedMqttClientOptions options)
{
ThrowIfDisposed();
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
if (options.ClientOptions == null)
{
throw new ArgumentException("The client options are not set.", nameof(options));
}
if (!_maintainConnectionTask?.IsCompleted ?? false)
{
throw new InvalidOperationException("The managed client is already started.");
}
Options = options;
if (options.Storage != null)
{
_storageManager = new ManagedMqttClientStorageManager(options.Storage);
var messages = await _storageManager.LoadQueuedMessagesAsync().ConfigureAwait(false);
foreach (var message in messages)
{
_messageQueue.Enqueue(message);
}
}
var cancellationTokenSource = new CancellationTokenSource();
var cancellationToken = cancellationTokenSource.Token;
_connectionCancellationToken = cancellationTokenSource;
_maintainConnectionTask = Task.Run(() => MaintainConnectionAsync(cancellationToken), cancellationToken);
_maintainConnectionTask.RunInBackground(_logger);
_logger.Info("Started");
}
public async Task StopAsync()
{
ThrowIfDisposed();
StopPublishing();
StopMaintainingConnection();
_messageQueue.Clear();
if (_maintainConnectionTask != null)
{
await Task.WhenAny(_maintainConnectionTask);
_maintainConnectionTask = null;
}
}
public Task SubscribeAsync(ICollection<MqttTopicFilter> topicFilters)
{
ThrowIfDisposed();
if (topicFilters == null)
{
throw new ArgumentNullException(nameof(topicFilters));
}
foreach (var topicFilter in topicFilters)
{
@@ -235,11 +294,14 @@ namespace MQTTnet.Extensions.ManagedClient
return Task.FromResult(0);
}
public Task UnsubscribeAsync(IEnumerable<string> topics)
public Task UnsubscribeAsync(ICollection<string> topics)
{
ThrowIfDisposed();
if (topics == null) throw new ArgumentNullException(nameof(topics));
if (topics == null)
{
throw new ArgumentNullException(nameof(topics));
}
lock (_subscriptions)
{
@@ -277,6 +339,23 @@ namespace MQTTnet.Extensions.ManagedClient
base.Dispose(disposing);
}
static TimeSpan GetRemainingTime(DateTime endTime)
{
var remainingTime = endTime - DateTime.UtcNow;
return remainingTime < TimeSpan.Zero ? TimeSpan.Zero : remainingTime;
}
async Task HandleSubscriptionExceptionAsync(Exception exception)
{
_logger.Warning(exception, "Synchronizing subscriptions failed.");
var synchronizingSubscriptionsFailedHandler = SynchronizingSubscriptionsFailedHandler;
if (SynchronizingSubscriptionsFailedHandler != null)
{
await synchronizingSubscriptionsFailedHandler.HandleSynchronizingSubscriptionsFailedAsync(new ManagedProcessFailedEventArgs(exception)).ConfigureAwait(false);
}
}
async Task MaintainConnectionAsync(CancellationToken cancellationToken)
{
try
@@ -299,9 +378,9 @@ namespace MQTTnet.Extensions.ManagedClient
{
try
{
using (var disconnectTimeout = new CancellationTokenSource(Options.ClientOptions.CommunicationTimeout))
using (var disconnectTimeout = new CancellationTokenSource(Options.ClientOptions.Timeout))
{
await InternalClient.DisconnectAsync(disconnectTimeout.Token).ConfigureAwait(false);
await InternalClient.DisconnectAsync(new MqttClientDisconnectOptions(), disconnectTimeout.Token).ConfigureAwait(false);
}
}
catch (OperationCanceledException)
@@ -326,49 +405,6 @@ namespace MQTTnet.Extensions.ManagedClient
}
}
async Task TryMaintainConnectionAsync(CancellationToken cancellationToken)
{
try
{
var connectionState = await ReconnectIfRequiredAsync(cancellationToken).ConfigureAwait(false);
if (connectionState == ReconnectionResult.NotConnected)
{
StopPublishing();
await Task.Delay(Options.AutoReconnectDelay, cancellationToken).ConfigureAwait(false);
return;
}
if (connectionState == ReconnectionResult.Reconnected)
{
await PublishReconnectSubscriptionsAsync().ConfigureAwait(false);
StartPublishing();
return;
}
if (connectionState == ReconnectionResult.Recovered)
{
StartPublishing();
return;
}
if (connectionState == ReconnectionResult.StillConnected)
{
await PublishSubscriptionsAsync(Options.ConnectionCheckInterval, cancellationToken).ConfigureAwait(false);
}
}
catch (OperationCanceledException)
{
}
catch (MqttCommunicationException exception)
{
_logger.Warning(exception, "Communication error while maintaining connection.");
}
catch (Exception exception)
{
_logger.Error(exception, "Error exception while maintaining connection.");
}
}
async Task PublishQueuedMessagesAsync(CancellationToken cancellationToken)
{
try
@@ -407,6 +443,259 @@ namespace MQTTnet.Extensions.ManagedClient
}
}
async Task PublishReconnectSubscriptionsAsync()
{
_logger.Info("Publishing subscriptions at reconnect");
try
{
if (_reconnectSubscriptions.Any())
{
var subscriptions = _reconnectSubscriptions.Select(
i => new MqttTopicFilter
{
Topic = i.Key,
QualityOfServiceLevel = i.Value
});
var topicFilters = new List<MqttTopicFilter>();
foreach (var sub in subscriptions)
{
topicFilters.Add(sub);
if (topicFilters.Count == Options.MaxTopicFiltersInSubscribeUnsubscribePackets)
{
await SendSubscribeUnsubscribe(topicFilters, null).ConfigureAwait(false);
topicFilters.Clear();
}
}
await SendSubscribeUnsubscribe(topicFilters, null).ConfigureAwait(false);
}
}
catch (Exception exception)
{
await HandleSubscriptionExceptionAsync(exception).ConfigureAwait(false);
}
}
async Task PublishSubscriptionsAsync(TimeSpan timeout, CancellationToken cancellationToken)
{
var endTime = DateTime.UtcNow + timeout;
while (await _subscriptionsQueuedSignal.WaitAsync(GetRemainingTime(endTime), cancellationToken).ConfigureAwait(false))
{
List<MqttTopicFilter> subscriptions;
HashSet<string> unsubscriptions;
lock (_subscriptions)
{
subscriptions = _subscriptions.Select(
i => new MqttTopicFilter
{
Topic = i.Key,
QualityOfServiceLevel = i.Value
})
.ToList();
_subscriptions.Clear();
unsubscriptions = new HashSet<string>(_unsubscriptions);
_unsubscriptions.Clear();
}
if (!subscriptions.Any() && !unsubscriptions.Any())
{
continue;
}
_logger.Verbose("Publishing {0} added and {1} removed subscriptions", subscriptions.Count, unsubscriptions.Count);
foreach (var unsubscription in unsubscriptions)
{
_reconnectSubscriptions.Remove(unsubscription);
}
foreach (var subscription in subscriptions)
{
_reconnectSubscriptions[subscription.Topic] = subscription.QualityOfServiceLevel;
}
var addedTopicFilters = new List<MqttTopicFilter>();
foreach (var subscription in subscriptions)
{
addedTopicFilters.Add(subscription);
if (addedTopicFilters.Count == Options.MaxTopicFiltersInSubscribeUnsubscribePackets)
{
await SendSubscribeUnsubscribe(addedTopicFilters, null).ConfigureAwait(false);
addedTopicFilters.Clear();
}
}
await SendSubscribeUnsubscribe(addedTopicFilters, null).ConfigureAwait(false);
var removedTopicFilters = new List<string>();
foreach (var unSub in unsubscriptions)
{
removedTopicFilters.Add(unSub);
if (removedTopicFilters.Count == Options.MaxTopicFiltersInSubscribeUnsubscribePackets)
{
await SendSubscribeUnsubscribe(null, removedTopicFilters).ConfigureAwait(false);
removedTopicFilters.Clear();
}
}
await SendSubscribeUnsubscribe(null, removedTopicFilters).ConfigureAwait(false);
}
}
async Task<ReconnectionResult> ReconnectIfRequiredAsync(CancellationToken cancellationToken)
{
if (InternalClient.IsConnected)
{
return ReconnectionResult.StillConnected;
}
MqttClientConnectResult connectResult = null;
try
{
using (var connectTimeout = new CancellationTokenSource(Options.ClientOptions.Timeout))
{
connectResult = await InternalClient.ConnectAsync(Options.ClientOptions, connectTimeout.Token).ConfigureAwait(false);
}
if (connectResult.ResultCode != MqttClientConnectResultCode.Success)
{
throw new MqttCommunicationException($"Client connected but server denied connection with reason '{connectResult.ResultCode}'.");
}
return connectResult.IsSessionPresent ? ReconnectionResult.Recovered : ReconnectionResult.Reconnected;
}
catch (Exception exception)
{
await _connectingFailedEvent.InvokeAsync(new ConnectingFailedEventArgs(connectResult, exception));
return ReconnectionResult.NotConnected;
}
}
async Task SendSubscribeUnsubscribe(List<MqttTopicFilter> addedSubscriptions, List<string> removedSubscriptions)
{
try
{
if (removedSubscriptions != null && removedSubscriptions.Any())
{
var unsubscribeOptionsBuilder = new MqttClientUnsubscribeOptionsBuilder();
foreach (var removedSubscription in removedSubscriptions)
{
unsubscribeOptionsBuilder.WithTopicFilter(removedSubscription);
}
await InternalClient.UnsubscribeAsync(unsubscribeOptionsBuilder.Build()).ConfigureAwait(false);
}
if (addedSubscriptions != null && addedSubscriptions.Any())
{
var subscribeOptionsBuilder = new MqttClientSubscribeOptionsBuilder();
foreach (var addedSubscription in addedSubscriptions)
{
subscribeOptionsBuilder.WithTopicFilter(addedSubscription);
}
await InternalClient.SubscribeAsync(subscribeOptionsBuilder.Build()).ConfigureAwait(false);
}
}
catch (Exception exception)
{
await HandleSubscriptionExceptionAsync(exception).ConfigureAwait(false);
}
}
void StartPublishing()
{
StopPublishing();
var cancellationTokenSource = new CancellationTokenSource();
var cancellationToken = cancellationTokenSource.Token;
_publishingCancellationToken = cancellationTokenSource;
Task.Run(() => PublishQueuedMessagesAsync(cancellationToken), cancellationToken).RunInBackground(_logger);
}
void StopMaintainingConnection()
{
try
{
_connectionCancellationToken?.Cancel(false);
}
finally
{
_connectionCancellationToken?.Dispose();
_connectionCancellationToken = null;
}
}
void StopPublishing()
{
try
{
_publishingCancellationToken?.Cancel(false);
}
finally
{
_publishingCancellationToken?.Dispose();
_publishingCancellationToken = null;
}
}
async Task TryMaintainConnectionAsync(CancellationToken cancellationToken)
{
try
{
var oldConnectionState = InternalClient.IsConnected;
var connectionState = await ReconnectIfRequiredAsync(cancellationToken).ConfigureAwait(false);
if (connectionState == ReconnectionResult.NotConnected)
{
StopPublishing();
await Task.Delay(Options.AutoReconnectDelay, cancellationToken).ConfigureAwait(false);
}
else if (connectionState == ReconnectionResult.Reconnected)
{
await PublishReconnectSubscriptionsAsync().ConfigureAwait(false);
StartPublishing();
}
else if (connectionState == ReconnectionResult.Recovered)
{
StartPublishing();
}
else if (connectionState == ReconnectionResult.StillConnected)
{
await PublishSubscriptionsAsync(Options.ConnectionCheckInterval, cancellationToken).ConfigureAwait(false);
}
if (oldConnectionState != InternalClient.IsConnected)
{
await _connectionStateChangedEvent.InvokeAsync(EventArgs.Empty).ConfigureAwait(false);
}
}
catch (OperationCanceledException)
{
}
catch (MqttCommunicationException exception)
{
_logger.Warning(exception, "Communication error while maintaining connection.");
}
catch (Exception exception)
{
_logger.Error(exception, "Error exception while maintaining connection.");
}
}
async Task TryPublishQueuedMessageAsync(ManagedMqttApplicationMessage message)
{
Exception transmitException = null;
@@ -463,220 +752,12 @@ namespace MQTTnet.Extensions.ManagedClient
}
finally
{
var eventHandler = ApplicationMessageProcessedHandler;
if (eventHandler != null)
if (_applicationMessageProcessedEvent.HasHandlers)
{
var eventArguments = new ApplicationMessageProcessedEventArgs(message, transmitException);
await eventHandler.HandleApplicationMessageProcessedAsync(eventArguments).ConfigureAwait(false);
var eventArgs = new ApplicationMessageProcessedEventArgs(message, transmitException);
await _applicationMessageProcessedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
}
}
}
async Task PublishSubscriptionsAsync(TimeSpan timeout, CancellationToken cancellationToken)
{
var endTime = DateTime.UtcNow + timeout;
while (await _subscriptionsQueuedSignal.WaitAsync(GetRemainingTime(endTime), cancellationToken).ConfigureAwait(false))
{
List<MqttTopicFilter> subscriptions;
HashSet<string> unsubscriptions;
lock (_subscriptions)
{
subscriptions = _subscriptions.Select(i => new MqttTopicFilter
{
Topic = i.Key,
QualityOfServiceLevel = i.Value
}).ToList();
_subscriptions.Clear();
unsubscriptions = new HashSet<string>(_unsubscriptions);
_unsubscriptions.Clear();
}
if (!subscriptions.Any() && !unsubscriptions.Any())
{
continue;
}
_logger.Verbose("Publishing {0} added and {1} removed subscriptions", subscriptions.Count, unsubscriptions.Count);
foreach (var unsubscription in unsubscriptions)
{
_reconnectSubscriptions.Remove(unsubscription);
}
foreach (var subscription in subscriptions)
{
_reconnectSubscriptions[subscription.Topic] = subscription.QualityOfServiceLevel;
}
var addedTopicFilters = new List<MqttTopicFilter>();
foreach (var subscription in subscriptions)
{
addedTopicFilters.Add(subscription);
if (addedTopicFilters.Count == Options.MaxTopicFiltersInSubscribeUnsubscribePackets)
{
await SendSubscribeUnsubscribe(addedTopicFilters, null).ConfigureAwait(false);
addedTopicFilters.Clear();
}
}
await SendSubscribeUnsubscribe(addedTopicFilters, null).ConfigureAwait(false);
var removedTopicFilters = new List<string>();
foreach (var unSub in unsubscriptions)
{
removedTopicFilters.Add(unSub);
if (removedTopicFilters.Count == Options.MaxTopicFiltersInSubscribeUnsubscribePackets)
{
await SendSubscribeUnsubscribe(null, removedTopicFilters).ConfigureAwait(false);
removedTopicFilters.Clear();
}
}
await SendSubscribeUnsubscribe(null, removedTopicFilters).ConfigureAwait(false);
}
}
async Task SendSubscribeUnsubscribe(List<MqttTopicFilter> addedSubscriptions, List<string> removedSubscriptions)
{
try
{
if (removedSubscriptions != null && removedSubscriptions.Any())
{
await InternalClient.UnsubscribeAsync(removedSubscriptions.ToArray()).ConfigureAwait(false);
}
if (addedSubscriptions != null && addedSubscriptions.Any())
{
await InternalClient.SubscribeAsync(addedSubscriptions.ToArray()).ConfigureAwait(false);
}
}
catch (Exception exception)
{
await HandleSubscriptionExceptionAsync(exception).ConfigureAwait(false);
}
}
async Task PublishReconnectSubscriptionsAsync()
{
_logger.Info("Publishing subscriptions at reconnect");
try
{
if (_reconnectSubscriptions.Any())
{
var subscriptions = _reconnectSubscriptions.Select(i => new MqttTopicFilter
{
Topic = i.Key,
QualityOfServiceLevel = i.Value
});
var topicFilters = new List<MqttTopicFilter>();
foreach (var sub in subscriptions)
{
topicFilters.Add(sub);
if (topicFilters.Count == Options.MaxTopicFiltersInSubscribeUnsubscribePackets)
{
await SendSubscribeUnsubscribe(topicFilters, null).ConfigureAwait(false);
topicFilters.Clear();
}
}
await SendSubscribeUnsubscribe(topicFilters, null).ConfigureAwait(false);
}
}
catch (Exception exception)
{
await HandleSubscriptionExceptionAsync(exception).ConfigureAwait(false);
}
}
async Task HandleSubscriptionExceptionAsync(Exception exception)
{
_logger.Warning(exception, "Synchronizing subscriptions failed.");
var synchronizingSubscriptionsFailedHandler = SynchronizingSubscriptionsFailedHandler;
if (SynchronizingSubscriptionsFailedHandler != null)
{
await synchronizingSubscriptionsFailedHandler.HandleSynchronizingSubscriptionsFailedAsync(new ManagedProcessFailedEventArgs(exception)).ConfigureAwait(false);
}
}
async Task<ReconnectionResult> ReconnectIfRequiredAsync(CancellationToken cancellationToken)
{
if (InternalClient.IsConnected)
{
return ReconnectionResult.StillConnected;
}
try
{
var result = await InternalClient.ConnectAsync(Options.ClientOptions, cancellationToken).ConfigureAwait(false);
return result.IsSessionPresent ? ReconnectionResult.Recovered : ReconnectionResult.Reconnected;
}
catch (Exception exception)
{
var connectingFailedHandler = ConnectingFailedHandler;
if (connectingFailedHandler != null)
{
await connectingFailedHandler.HandleConnectingFailedAsync(new ManagedProcessFailedEventArgs(exception)).ConfigureAwait(false);
}
return ReconnectionResult.NotConnected;
}
}
void StartPublishing()
{
if (_publishingCancellationToken != null)
{
StopPublishing();
}
var cancellationTokenSource = new CancellationTokenSource();
var cancellationToken = cancellationTokenSource.Token;
_publishingCancellationToken = cancellationTokenSource;
Task.Run(() => PublishQueuedMessagesAsync(cancellationToken), cancellationToken).RunInBackground(_logger);
}
void StopPublishing()
{
try
{
_publishingCancellationToken?.Cancel(false);
}
finally
{
_publishingCancellationToken?.Dispose();
_publishingCancellationToken = null;
}
}
void StopMaintainingConnection()
{
try
{
_connectionCancellationToken?.Cancel(false);
}
finally
{
_connectionCancellationToken?.Dispose();
_connectionCancellationToken = null;
}
}
static TimeSpan GetRemainingTime(DateTime endTime)
{
var remainingTime = endTime - DateTime.UtcNow;
return remainingTime < TimeSpan.Zero ? TimeSpan.Zero : remainingTime;
}
}
}
@@ -1,232 +1,78 @@
using MQTTnet.Client.Connecting;
using MQTTnet.Client.Disconnecting;
using MQTTnet.Client.Publishing;
using MQTTnet.Client.Receiving;
using MQTTnet.Protocol;
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Packets;
using MQTTnet.Protocol;
namespace MQTTnet.Extensions.ManagedClient
{
public static class ManagedMqttClientExtensions
{
public static IManagedMqttClient UseConnectedHandler(this IManagedMqttClient client, Func<MqttClientConnectedEventArgs, Task> handler)
public static Task EnqueueAsync(
this ManagedMqttClient managedMqttClient,
string topic,
string payload = null,
MqttQualityOfServiceLevel qualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce,
bool retain = false)
{
if (client == null) throw new ArgumentNullException(nameof(client));
if (handler == null)
if (managedMqttClient == null)
{
return client.UseConnectedHandler((IMqttClientConnectedHandler)null);
throw new ArgumentNullException(nameof(managedMqttClient));
}
return client.UseConnectedHandler(new MqttClientConnectedHandlerDelegate(handler));
}
public static IManagedMqttClient UseConnectedHandler(this IManagedMqttClient client, Action<MqttClientConnectedEventArgs> handler)
{
if (client == null) throw new ArgumentNullException(nameof(client));
if (handler == null)
if (topic == null)
{
return client.UseConnectedHandler((IMqttClientConnectedHandler)null);
throw new ArgumentNullException(nameof(topic));
}
return client.UseConnectedHandler(new MqttClientConnectedHandlerDelegate(handler));
}
public static IManagedMqttClient UseConnectedHandler(this IManagedMqttClient client, IMqttClientConnectedHandler handler)
{
if (client == null) throw new ArgumentNullException(nameof(client));
client.ConnectedHandler = handler;
return client;
}
public static IManagedMqttClient UseDisconnectedHandler(this IManagedMqttClient client, Func<MqttClientDisconnectedEventArgs, Task> handler)
{
if (client == null) throw new ArgumentNullException(nameof(client));
if (handler == null)
{
return client.UseDisconnectedHandler((IMqttClientDisconnectedHandler)null);
}
return client.UseDisconnectedHandler(new MqttClientDisconnectedHandlerDelegate(handler));
}
public static IManagedMqttClient UseDisconnectedHandler(this IManagedMqttClient client, Action<MqttClientDisconnectedEventArgs> handler)
{
if (client == null) throw new ArgumentNullException(nameof(client));
if (handler == null)
{
return client.UseDisconnectedHandler((IMqttClientDisconnectedHandler)null);
}
return client.UseDisconnectedHandler(new MqttClientDisconnectedHandlerDelegate(handler));
}
public static IManagedMqttClient UseDisconnectedHandler(this IManagedMqttClient client, IMqttClientDisconnectedHandler handler)
{
if (client == null) throw new ArgumentNullException(nameof(client));
client.DisconnectedHandler = handler;
return client;
}
public static TReceiver UseApplicationMessageReceivedHandler<TReceiver>(this TReceiver receiver, Func<MqttApplicationMessageReceivedEventArgs, Task> handler)
where TReceiver : IApplicationMessageReceiver
{
if (receiver == null) throw new ArgumentNullException(nameof(receiver));
if (handler == null)
{
return receiver.UseApplicationMessageReceivedHandler((IMqttApplicationMessageReceivedHandler)null);
}
return receiver.UseApplicationMessageReceivedHandler(new MqttApplicationMessageReceivedHandlerDelegate(handler));
}
public static TReceiver UseApplicationMessageReceivedHandler<TReceiver>(this TReceiver receiver, Action<MqttApplicationMessageReceivedEventArgs> handler)
where TReceiver : IApplicationMessageReceiver
{
if (receiver == null) throw new ArgumentNullException(nameof(receiver));
if (handler == null)
{
return receiver.UseApplicationMessageReceivedHandler((IMqttApplicationMessageReceivedHandler)null);
}
return receiver.UseApplicationMessageReceivedHandler(new MqttApplicationMessageReceivedHandlerDelegate(handler));
}
public static TReceiver UseApplicationMessageReceivedHandler<TReceiver>(this TReceiver receiver, IMqttApplicationMessageReceivedHandler handler)
where TReceiver : IApplicationMessageReceiver
{
if (receiver == null) throw new ArgumentNullException(nameof(receiver));
receiver.ApplicationMessageReceivedHandler = handler;
return receiver;
}
public static Task SubscribeAsync(this IManagedMqttClient client, params MqttTopicFilter[] topicFilters)
{
if (client == null) throw new ArgumentNullException(nameof(client));
return client.SubscribeAsync(topicFilters);
}
public static Task SubscribeAsync(this IManagedMqttClient client, string topic, MqttQualityOfServiceLevel qualityOfServiceLevel)
{
if (client == null) throw new ArgumentNullException(nameof(client));
if (topic == null) throw new ArgumentNullException(nameof(topic));
return client.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic(topic).WithQualityOfServiceLevel(qualityOfServiceLevel).Build());
}
public static Task SubscribeAsync(this IManagedMqttClient client, string topic)
{
if (client == null) throw new ArgumentNullException(nameof(client));
if (topic == null) throw new ArgumentNullException(nameof(topic));
return client.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic(topic).Build());
}
public static Task UnsubscribeAsync(this IManagedMqttClient client, params string[] topicFilters)
{
if (client == null) throw new ArgumentNullException(nameof(client));
return client.UnsubscribeAsync(topicFilters);
}
public static async Task PublishAsync(this IApplicationMessagePublisher publisher, IEnumerable<MqttApplicationMessage> applicationMessages)
{
if (publisher == null) throw new ArgumentNullException(nameof(publisher));
if (applicationMessages == null) throw new ArgumentNullException(nameof(applicationMessages));
foreach (var applicationMessage in applicationMessages)
{
await publisher.PublishAsync(applicationMessage).ConfigureAwait(false);
}
}
public static Task<MqttClientPublishResult> PublishAsync(this IApplicationMessagePublisher publisher, MqttApplicationMessage applicationMessage)
{
if (publisher == null) throw new ArgumentNullException(nameof(publisher));
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
return publisher.PublishAsync(applicationMessage, CancellationToken.None);
}
public static async Task PublishAsync(this IApplicationMessagePublisher publisher, params MqttApplicationMessage[] applicationMessages)
{
if (publisher == null) throw new ArgumentNullException(nameof(publisher));
if (applicationMessages == null) throw new ArgumentNullException(nameof(applicationMessages));
foreach (var applicationMessage in applicationMessages)
{
await publisher.PublishAsync(applicationMessage, CancellationToken.None).ConfigureAwait(false);
}
}
public static Task<MqttClientPublishResult> PublishAsync(this IApplicationMessagePublisher publisher, string topic)
{
if (publisher == null) throw new ArgumentNullException(nameof(publisher));
if (topic == null) throw new ArgumentNullException(nameof(topic));
return publisher.PublishAsync(builder => builder
.WithTopic(topic));
}
public static Task<MqttClientPublishResult> PublishAsync(this IApplicationMessagePublisher publisher, string topic, string payload)
{
if (publisher == null) throw new ArgumentNullException(nameof(publisher));
if (topic == null) throw new ArgumentNullException(nameof(topic));
return publisher.PublishAsync(builder => builder
.WithTopic(topic)
.WithPayload(payload));
}
public static Task<MqttClientPublishResult> PublishAsync(this IApplicationMessagePublisher publisher, string topic, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel)
{
if (publisher == null) throw new ArgumentNullException(nameof(publisher));
if (topic == null) throw new ArgumentNullException(nameof(topic));
return publisher.PublishAsync(builder => builder
.WithTopic(topic)
.WithPayload(payload)
.WithQualityOfServiceLevel(qualityOfServiceLevel));
}
public static Task<MqttClientPublishResult> PublishAsync(this IApplicationMessagePublisher publisher, string topic, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel, bool retain)
{
if (publisher == null) throw new ArgumentNullException(nameof(publisher));
if (topic == null) throw new ArgumentNullException(nameof(topic));
return publisher.PublishAsync(builder => builder
.WithTopic(topic)
var applicationMessage = new MqttApplicationMessageBuilder().WithTopic(topic)
.WithPayload(payload)
.WithRetainFlag(retain)
.WithQualityOfServiceLevel(qualityOfServiceLevel)
.WithRetainFlag(retain));
.Build();
return managedMqttClient.EnqueueAsync(applicationMessage);
}
public static Task<MqttClientPublishResult> PublishAsync(this IApplicationMessagePublisher publisher, Func<MqttApplicationMessageBuilder, MqttApplicationMessageBuilder> builder, CancellationToken cancellationToken)
public static Task SubscribeAsync(
this ManagedMqttClient managedMqttClient,
string topic,
MqttQualityOfServiceLevel qualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce)
{
if (publisher == null) throw new ArgumentNullException(nameof(publisher));
if (managedMqttClient == null)
{
throw new ArgumentNullException(nameof(managedMqttClient));
}
var message = builder(new MqttApplicationMessageBuilder()).Build();
return publisher.PublishAsync(message, cancellationToken);
if (topic == null)
{
throw new ArgumentNullException(nameof(topic));
}
return managedMqttClient.SubscribeAsync(
new List<MqttTopicFilter>
{
new MqttTopicFilterBuilder().WithTopic(topic).WithQualityOfServiceLevel(qualityOfServiceLevel).Build()
});
}
public static Task<MqttClientPublishResult> PublishAsync(this IApplicationMessagePublisher publisher, Func<MqttApplicationMessageBuilder, MqttApplicationMessageBuilder> builder)
public static Task UnsubscribeAsync(this ManagedMqttClient managedMqttClient, string topic)
{
if (publisher == null) throw new ArgumentNullException(nameof(publisher));
if (managedMqttClient == null)
{
throw new ArgumentNullException(nameof(managedMqttClient));
}
var message = builder(new MqttApplicationMessageBuilder()).Build();
return publisher.PublishAsync(message, CancellationToken.None);
if (topic == null)
{
throw new ArgumentNullException(nameof(topic));
}
return managedMqttClient.UnsubscribeAsync(new List<string> { topic });
}
}
}
}
@@ -1,12 +1,16 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using MQTTnet.Client.Options;
using MQTTnet.Client;
using MQTTnet.Server;
namespace MQTTnet.Extensions.ManagedClient
{
public sealed class ManagedMqttClientOptions : IManagedMqttClientOptions
public sealed class ManagedMqttClientOptions
{
public IMqttClientOptions ClientOptions { get; set; }
public MqttClientOptions ClientOptions { get; set; }
public TimeSpan AutoReconnectDelay { get; set; } = TimeSpan.FromSeconds(5);
@@ -18,6 +22,10 @@ namespace MQTTnet.Extensions.ManagedClient
public MqttPendingMessagesOverflowStrategy PendingMessagesOverflowStrategy { get; set; } = MqttPendingMessagesOverflowStrategy.DropNewMessage;
/// <summary>
/// Defines the maximum amount of topic filters which will be sent in a SUBSCRIBE/UNSUBSCRIBE packet.
/// Amazon AWS limits this number to 8. The default is int.MaxValue.
/// </summary>
public int MaxTopicFiltersInSubscribeUnsubscribePackets { get; set; } = int.MaxValue;
}
}
@@ -1,5 +1,9 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using MQTTnet.Client.Options;
using MQTTnet.Client;
using MQTTnet.Server;
namespace MQTTnet.Extensions.ManagedClient
@@ -33,7 +37,7 @@ namespace MQTTnet.Extensions.ManagedClient
return this;
}
public ManagedMqttClientOptionsBuilder WithClientOptions(IMqttClientOptions value)
public ManagedMqttClientOptionsBuilder WithClientOptions(MqttClientOptions value)
{
if (_clientOptionsBuilder != null)
{
@@ -1,4 +1,8 @@
using System;
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
@@ -1,4 +1,8 @@
using System;
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
namespace MQTTnet.Extensions.ManagedClient
{
@@ -1,18 +1,28 @@
using System;
using MQTTnet.Diagnostics.Logger;
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using MQTTnet.Client;
using MQTTnet.Diagnostics;
namespace MQTTnet.Extensions.ManagedClient
{
public static class MqttFactoryExtensions
{
public static IManagedMqttClient CreateManagedMqttClient(this IMqttFactory factory)
public static ManagedMqttClient CreateManagedMqttClient(this MqttFactory factory, MqttClient mqttClient = null)
{
if (factory == null) throw new ArgumentNullException(nameof(factory));
return new ManagedMqttClient(factory.CreateMqttClient(), factory.DefaultLogger);
if (mqttClient == null)
{
return new ManagedMqttClient(factory.CreateMqttClient(), factory.DefaultLogger);
}
return new ManagedMqttClient(mqttClient, factory.DefaultLogger);
}
public static IManagedMqttClient CreateManagedMqttClient(this IMqttFactory factory, IMqttNetLogger logger)
public static ManagedMqttClient CreateManagedMqttClient(this MqttFactory factory, IMqttNetLogger logger)
{
if (factory == null) throw new ArgumentNullException(nameof(factory));
if (logger == null) throw new ArgumentNullException(nameof(logger));
@@ -1,4 +1,8 @@
namespace MQTTnet.Extensions.ManagedClient
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace MQTTnet.Extensions.ManagedClient
{
public enum ReconnectionResult
{
@@ -1,4 +1,8 @@
using System;
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Threading.Tasks;
namespace MQTTnet.Extensions.ManagedClient
@@ -1,14 +0,0 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Protocol;
namespace MQTTnet.Extensions.Rpc
{
public interface IMqttRpcClient : IDisposable
{
Task<byte[]> ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel);
Task<byte[]> ExecuteAsync(string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, CancellationToken cancellationToken);
}
}
@@ -1,40 +1,72 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;net5.0</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' AND '$(SolutionName)' != 'MQTTnet.noUWP' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
<Product />
<Company />
<Authors />
<PackageId />
<GeneratePackageOnBuild>False</GeneratePackageOnBuild>
<SignAssembly>false</SignAssembly>
<DelaySign>false</DelaySign>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>
<PropertyGroup>
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
<AssemblyName>MQTTnet.Extensions.Rpc</AssemblyName>
<RootNamespace>MQTTnet.Extensions.Rpc</RootNamespace>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<Company>The contributors of MQTTnet</Company>
<Product>MQTTnet</Product>
<Description>This is an extension library which allows executing synchronous device calls including a response using MQTTnet.</Description>
<Authors>The contributors of MQTTnet</Authors>
<PackageId>MQTTnet.Extensions.Rpc</PackageId>
<SignAssembly>false</SignAssembly>
<DelaySign>false</DelaySign>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<Copyright>Christian Kratky 2016-2022</Copyright>
<PackageProjectUrl>https://github.com/dotnet/MQTTnet</PackageProjectUrl>
<RepositoryUrl>https://github.com/dotnet/MQTTnet.git</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageTags>MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin Blazor</PackageTags>
<NeutralLanguage>en-US</NeutralLanguage>
<EnableNETAnalyzers>false</EnableNETAnalyzers>
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
<PackageIcon>nuget.png</PackageIcon>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<PackageReleaseNotes>For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).</PackageReleaseNotes>
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0'">
<CopyLocalLockFileAssemblies>false</CopyLocalLockFileAssemblies>
<NugetTargetMoniker>UAP,Version=v10.0</NugetTargetMoniker>
<TargetPlatformIdentifier>UAP</TargetPlatformIdentifier>
<TargetPlatformVersion>10.0.18362.0</TargetPlatformVersion>
<TargetPlatformMinVersion>10.0.10240.0</TargetPlatformMinVersion>
<TargetFrameworkIdentifier>.NETCore</TargetFrameworkIdentifier>
<TargetFrameworkVersion>v5.0</TargetFrameworkVersion>
<DefineConstants>$(DefineConstants);WINDOWS_UWP</DefineConstants>
<DefaultLanguage>en</DefaultLanguage>
<LanguageTargets>$(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets</LanguageTargets>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0'">
<CopyLocalLockFileAssemblies>false</CopyLocalLockFileAssemblies>
<NugetTargetMoniker>UAP,Version=v10.0</NugetTargetMoniker>
<TargetPlatformIdentifier>UAP</TargetPlatformIdentifier>
<TargetPlatformVersion>10.0.18362.0</TargetPlatformVersion>
<TargetPlatformMinVersion>10.0.10240.0</TargetPlatformMinVersion>
<TargetFrameworkIdentifier>.NETCore</TargetFrameworkIdentifier>
<TargetFrameworkVersion>v5.0</TargetFrameworkVersion>
<DefineConstants>$(DefineConstants);WINDOWS_UWP</DefineConstants>
<DefaultLanguage>en</DefaultLanguage>
<LanguageTargets>$(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets</LanguageTargets>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
<None Include="..\..\Images\nuget.png">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MQTTnet\MQTTnet.csproj" />
</ItemGroup>
<ItemGroup>
<None Include="..\..\LICENSE">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MQTTnet\MQTTnet.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,3 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=options/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=options_005Ctopicgeneration/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
@@ -0,0 +1,37 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using MQTTnet.Client;
namespace MQTTnet.Extensions.Rpc
{
public static class MqttFactoryExtensions
{
public static MqttRpcClient CreateMqttRpcClient(this MqttFactory factory, MqttClient mqttClient)
{
return factory.CreateMqttRpcClient(mqttClient, new MqttRpcClientOptions
{
TopicGenerationStrategy = new DefaultMqttRpcClientTopicGenerationStrategy()
});
}
public static MqttRpcClient CreateMqttRpcClient(this MqttFactory factory, MqttClient mqttClient, MqttRpcClientOptions rpcClientOptions)
{
if (factory == null) throw new ArgumentNullException(nameof(factory));
if (mqttClient == null)
{
throw new ArgumentNullException(nameof(mqttClient));
}
if (rpcClientOptions == null)
{
throw new ArgumentNullException(nameof(rpcClientOptions));
}
return new MqttRpcClient(mqttClient, rpcClientOptions);
}
}
}
+13 -27
View File
@@ -1,39 +1,30 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using MQTTnet.Client;
using MQTTnet.Exceptions;
using MQTTnet.Extensions.Rpc.Options;
using MQTTnet.Extensions.Rpc.Options.TopicGeneration;
using MQTTnet.Protocol;
using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Client.Subscribing;
using MQTTnet.Implementations;
namespace MQTTnet.Extensions.Rpc
{
public sealed class MqttRpcClient : IMqttRpcClient
public sealed class MqttRpcClient : IDisposable
{
readonly ConcurrentDictionary<string, TaskCompletionSource<byte[]>> _waitingCalls = new ConcurrentDictionary<string, TaskCompletionSource<byte[]>>();
readonly IMqttClient _mqttClient;
readonly IMqttRpcClientOptions _options;
readonly RpcAwareApplicationMessageReceivedHandler _applicationMessageReceivedHandler;
[Obsolete("Use MqttRpcClient(IMqttClient mqttClient, IMqttRpcClientOptions options).")]
public MqttRpcClient(IMqttClient mqttClient) : this(mqttClient, new MqttRpcClientOptions())
{
}
public MqttRpcClient(IMqttClient mqttClient, IMqttRpcClientOptions options)
readonly MqttClient _mqttClient;
readonly MqttRpcClientOptions _options;
public MqttRpcClient(MqttClient mqttClient, MqttRpcClientOptions options)
{
_mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
_options = options ?? throw new ArgumentNullException(nameof(options));
_applicationMessageReceivedHandler = new RpcAwareApplicationMessageReceivedHandler(
mqttClient.ApplicationMessageReceivedHandler,
HandleApplicationMessageReceivedAsync);
_mqttClient.ApplicationMessageReceivedHandler = _applicationMessageReceivedHandler;
_mqttClient.ApplicationMessageReceivedAsync += HandleApplicationMessageReceivedAsync;
}
public async Task<byte[]> ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel)
@@ -59,12 +50,7 @@ namespace MQTTnet.Extensions.Rpc
public async Task<byte[]> ExecuteAsync(string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, CancellationToken cancellationToken)
{
if (methodName == null) throw new ArgumentNullException(nameof(methodName));
if (!(_mqttClient.ApplicationMessageReceivedHandler is RpcAwareApplicationMessageReceivedHandler))
{
throw new InvalidOperationException("The application message received handler was modified.");
}
var topicNames = _options.TopicGenerationStrategy.CreateRpcTopics(new TopicGenerationContext
{
MethodName = methodName,
@@ -121,7 +107,7 @@ namespace MQTTnet.Extensions.Rpc
finally
{
_waitingCalls.TryRemove(responseTopic, out _);
await _mqttClient.UnsubscribeAsync(responseTopic).ConfigureAwait(false);
}
}
@@ -147,7 +133,7 @@ namespace MQTTnet.Extensions.Rpc
public void Dispose()
{
_mqttClient.ApplicationMessageReceivedHandler = _applicationMessageReceivedHandler.OriginalHandler;
_mqttClient.ApplicationMessageReceivedAsync -= HandleApplicationMessageReceivedAsync;
foreach (var tcs in _waitingCalls)
{
@@ -1,4 +1,8 @@
using System;
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Text;
using System.Threading.Tasks;
using MQTTnet.Protocol;
@@ -7,7 +11,7 @@ namespace MQTTnet.Extensions.Rpc
{
public static class MqttRpcClientExtensions
{
public static Task<byte[]> ExecuteAsync(this IMqttRpcClient client, TimeSpan timeout, string methodName, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel)
public static Task<byte[]> ExecuteAsync(this MqttRpcClient client, TimeSpan timeout, string methodName, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel)
{
if (client == null) throw new ArgumentNullException(nameof(client));
@@ -1,9 +0,0 @@
using MQTTnet.Extensions.Rpc.Options.TopicGeneration;
namespace MQTTnet.Extensions.Rpc.Options
{
public interface IMqttRpcClientOptions
{
IMqttRpcClientTopicGenerationStrategy TopicGenerationStrategy { get; set; }
}
}
@@ -1,8 +1,10 @@
using MQTTnet.Extensions.Rpc.Options.TopicGeneration;
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace MQTTnet.Extensions.Rpc.Options
namespace MQTTnet.Extensions.Rpc
{
public sealed class MqttRpcClientOptions : IMqttRpcClientOptions
public sealed class MqttRpcClientOptions
{
public IMqttRpcClientTopicGenerationStrategy TopicGenerationStrategy { get; set; } = new DefaultMqttRpcClientTopicGenerationStrategy();
}
@@ -1,7 +1,10 @@
using MQTTnet.Extensions.Rpc.Options.TopicGeneration;
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
namespace MQTTnet.Extensions.Rpc.Options
namespace MQTTnet.Extensions.Rpc
{
public sealed class MqttRpcClientOptionsBuilder
{
@@ -14,7 +17,7 @@ namespace MQTTnet.Extensions.Rpc.Options
return this;
}
public IMqttRpcClientOptions Build()
public MqttRpcClientOptions Build()
{
return new MqttRpcClientOptions
{
@@ -1,4 +1,8 @@
namespace MQTTnet.Extensions.Rpc.Options
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace MQTTnet.Extensions.Rpc
{
public sealed class MqttRpcTopicPair
{
@@ -1,6 +1,10 @@
using System;
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace MQTTnet.Extensions.Rpc.Options.TopicGeneration
using System;
namespace MQTTnet.Extensions.Rpc
{
public sealed class DefaultMqttRpcClientTopicGenerationStrategy : IMqttRpcClientTopicGenerationStrategy
{
@@ -1,4 +1,8 @@
namespace MQTTnet.Extensions.Rpc.Options.TopicGeneration
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace MQTTnet.Extensions.Rpc
{
public interface IMqttRpcClientTopicGenerationStrategy
{

Some files were not shown because too many files have changed in this diff Show More