mirror of
https://github.com/dotnet/MQTTnet.git
synced 2026-10-15 00:15:15 +08:00
Compare commits
51
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
06842366b8 | ||
|
|
9cf9a714cb | ||
|
|
16f8765562 | ||
|
|
07b72e75ed | ||
|
|
419d7622d8 | ||
|
|
71140d12c7 | ||
|
|
a2957180fa | ||
|
|
ffb455b5b4 | ||
|
|
076039abc7 | ||
|
|
61cbaa06a8 | ||
|
|
1114fa041d | ||
|
|
535a4e010c | ||
|
|
37c1eecce5 | ||
|
|
e791d9aba4 | ||
|
|
5ebc5fcb7e | ||
|
|
51979fa5a4 | ||
|
|
7ac7f256ef | ||
|
|
e7ccd897b0 | ||
|
|
d5d72ac37d | ||
|
|
a5ef10fddb | ||
|
|
c97bd82334 | ||
|
|
75ebf116cf | ||
|
|
2f7f55e3f5 | ||
|
|
e43e7f8e36 | ||
|
|
247988e633 | ||
|
|
93a9d8d1db | ||
|
|
24c418cf43 | ||
|
|
948c7c2b69 | ||
|
|
1cdc343e6d | ||
|
|
27771f0146 | ||
|
|
e7de2b913a | ||
|
|
3dd3279656 | ||
|
|
c5934bf18b | ||
|
|
7715bf37dc | ||
|
|
9dbe1e4b12 | ||
|
|
f13b3eb6bb | ||
|
|
8eb884fa5d | ||
|
|
9f81a2bb71 | ||
|
|
e2f8d0f0ac | ||
|
|
981ea02149 | ||
|
|
8566fd5395 | ||
|
|
8089c6bdbc | ||
|
|
ef5b6c9b32 | ||
|
|
4aa4a12e47 | ||
|
|
6bf2b4eaca | ||
|
|
6ef6b57064 | ||
|
|
7d4027958c | ||
|
|
bdb74642c2 | ||
|
|
63d85184e1 | ||
|
|
0506857f0d | ||
|
|
6417baa081 |
@@ -3,8 +3,8 @@ name: CI
|
||||
on: [push, pull_request]
|
||||
|
||||
env:
|
||||
VERSION: "5.0.0.${{github.run_number}}"
|
||||
PACKAGE_SUFFIX: "-RC"
|
||||
VERSION: "5.1.0.${{github.run_number}}"
|
||||
PACKAGE_SUFFIX: ""
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -21,8 +21,15 @@ jobs:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Load strong name certificate
|
||||
run: |
|
||||
echo "$SNC_BASE64" | base64 --decode > "${{ github.workspace }}/certificate.snk"
|
||||
shell: bash
|
||||
env:
|
||||
SNC_BASE64: ${{ secrets.SNC_BASE64 }}
|
||||
|
||||
- name: Build package
|
||||
run: dotnet build MQTTnet.sln --configuration Release /p:FileVersion=${{ env.VERSION }} /p:AssemblyVersion=${{ env.VERSION }} /p:PackageVersion=${{ env.VERSION }}${{ env.PACKAGE_SUFFIX }}
|
||||
run: dotnet build MQTTnet.sln --configuration Release /p:FileVersion=${{ env.VERSION }} /p:AssemblyVersion=${{ env.VERSION }} /p:PackageVersion=${{ env.VERSION }}${{ env.PACKAGE_SUFFIX }} /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=${{ github.workspace }}/certificate.snk
|
||||
|
||||
- name: Upload nuget packages
|
||||
uses: actions/upload-artifact@v4
|
||||
@@ -46,7 +53,7 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Execute tests
|
||||
run: dotnet test --framework net8.0 Source/MQTTnet.Tests/MQTTnet.Tests.csproj
|
||||
run: dotnet test --framework net8.0 --configuration Release Source/MQTTnet.Tests/MQTTnet.Tests.csproj
|
||||
|
||||
sign:
|
||||
needs: build
|
||||
@@ -107,7 +114,8 @@ jobs:
|
||||
run: dotnet nuget push ${{ github.workspace }}/nugets/**/*.nupkg -k ${{ secrets.MYGET_API_KEY }} -s https://www.myget.org/F/mqttnet/api/v3/index.json --skip-duplicate
|
||||
|
||||
publish-nuget:
|
||||
if: ${{ github.event_name == 'published' }}
|
||||
# Tag schema is 'v5.0.0-rc' etc.
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
needs: [sign, test]
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Product>MQTTnet</Product>
|
||||
<Authors>The contributors of MQTTnet</Authors>
|
||||
<Company>The contributors of MQTTnet</Company>
|
||||
<Copyright>Copyright (c) .NET Foundation and Contributors</Copyright>
|
||||
|
||||
<DelaySign>false</DelaySign>
|
||||
<SignAssembly>false</SignAssembly>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
|
||||
<NeutralLanguage>en-US</NeutralLanguage>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<NoWarn>1591;NETSDK1138;NU1803;NU1901;NU1902</NoWarn>
|
||||
|
||||
<NuGetAuditMode>all</NuGetAuditMode>
|
||||
<NuGetAudit>true</NuGetAudit>
|
||||
<NuGetAuditLevel>low</NuGetAuditLevel>
|
||||
|
||||
<EnableNETAnalyzers>true</EnableNETAnalyzers>
|
||||
<AnalysisLevel>latest</AnalysisLevel>
|
||||
<AnalysisMode>recommended</AnalysisMode>
|
||||
<AnalysisModeStyle>default</AnalysisModeStyle>
|
||||
|
||||
<TestingPlatformShowTestsFailure>true</TestingPlatformShowTestsFailure>
|
||||
<TestingPlatformDotnetTestSupport>true</TestingPlatformDotnetTestSupport>
|
||||
<EnableMSTestRunner>true</EnableMSTestRunner>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(GITHUB_ACTIONS)' == 'true'">
|
||||
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(GeneratePackageOnBuild)'=='true'">
|
||||
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
||||
<PackageIcon>nuget.png</PackageIcon>
|
||||
<PackageProjectUrl>https://github.com/dotnet/MQTTnet</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/dotnet/MQTTnet.git</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<IncludeSymbols>true</IncludeSymbols>
|
||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||
<EmbedUntrackedSources>true</EmbedUntrackedSources>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup Condition="'$(GeneratePackageOnBuild)'=='true'">
|
||||
<None Include="README.md" Pack="true" PackagePath="" Condition="Exists('README.md')" />
|
||||
<None Include="..\..\Images\nuget.png" Pack="true" PackagePath="" />
|
||||
<None Include="..\..\LICENSE" Pack="true" PackagePath="" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(IncludeSymbols)'=='true'">
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All"/>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
+8
-3
@@ -7,11 +7,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet", "Source\MQTTnet\M
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{B3F60ECB-45BA-4C66-8903-8BB89CA67998}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
.github\workflows\ci.yml = .github\workflows\ci.yml
|
||||
CODE-OF-CONDUCT.md = CODE-OF-CONDUCT.md
|
||||
Directory.Build.props = Directory.Build.props
|
||||
LICENSE = LICENSE
|
||||
README.md = README.md
|
||||
Source\ReleaseNotes.md = Source\ReleaseNotes.md
|
||||
.github\workflows\ci.yml = .github\workflows\ci.yml
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.AspNetCore", "Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj", "{F10C4060-F7EE-4A83-919F-FF723E72F94A}"
|
||||
@@ -32,6 +33,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Extensions.TopicTem
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MQTTnet.Server", "Source\MQTTnet.Server\MQTTnet.Server.csproj", "{C876EFFD-C5AD-4E26-BD9F-4C48252C02BC}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MQTTnet.PowerShell", "Source\MQTTnet.PowerShell\MQTTnet.PowerShell.csproj", "{37B129C5-0231-4EB5-AF13-77959068911C}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -78,6 +81,10 @@ Global
|
||||
{C876EFFD-C5AD-4E26-BD9F-4C48252C02BC}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C876EFFD-C5AD-4E26-BD9F-4C48252C02BC}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{C876EFFD-C5AD-4E26-BD9F-4C48252C02BC}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{37B129C5-0231-4EB5-AF13-77959068911C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{37B129C5-0231-4EB5-AF13-77959068911C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{37B129C5-0231-4EB5-AF13-77959068911C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{37B129C5-0231-4EB5-AF13-77959068911C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -85,6 +92,4 @@ Global
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {07536672-5CBC-4BE3-ACE0-708A431A7894}
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
<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:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertIfStatementToSwitchStatement/@EntryIndexedValue">DO_NOT_SHOW</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=InconsistentNaming/@EntryIndexedValue">SUGGESTION</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UncatchableException/@EntryIndexedValue">DO_NOT_SHOW</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UnthrowableException/@EntryIndexedValue">DO_NOT_SHOW</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_FOR/@EntryValue">Required</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_FOREACH/@EntryValue">Required</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_IFELSE/@EntryValue">Required</s:String>
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
<br/>
|
||||
</p>
|
||||
|
||||
[](https://www.nuget.org/packages/MQTTnet)
|
||||
[](https://www.nuget.org/packages/MQTTnet)
|
||||
[](https://github.com/dotnet/MQTTnet/actions/workflows/ci.yml)
|
||||
[](https://www.myget.org/feed/mqttnet/package/nuget/MQTTnet)
|
||||
[](https://www.myget.org/feed/mqttnet/package/nuget/MQTTnet)
|
||||

|
||||
[](https://gitter.im/MQTTnet/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
[](https://raw.githubusercontent.com/dotnet/MQTTnet/master/LICENSE)
|
||||
|
||||
@@ -8,431 +8,17 @@
|
||||
|
||||
using System.Security.Authentication;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Text;
|
||||
using MQTTnet.Formatter;
|
||||
using MQTTnet.Protocol;
|
||||
using MQTTnet.Samples.Helpers;
|
||||
using MQTTnet.Server;
|
||||
using MQTTnet.Server.EnhancedAuthentication;
|
||||
|
||||
namespace MQTTnet.Samples.Client;
|
||||
|
||||
public static class Client_Connection_Samples
|
||||
{
|
||||
public static async Task Clean_Disconnect()
|
||||
{
|
||||
/*
|
||||
* This sample disconnects in a clean way. This will send a MQTT DISCONNECT packet
|
||||
* to the server and close the connection afterwards.
|
||||
*
|
||||
* See sample _Connect_Client_ for more details.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
{
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
// This will send the DISCONNECT packet. Calling _Dispose_ without DisconnectAsync the
|
||||
// connection is closed in a "not clean" way. See MQTT specification for more details.
|
||||
await mqttClient.DisconnectAsync(new MqttClientDisconnectOptionsBuilder().WithReason(MqttClientDisconnectOptionsReason.NormalDisconnection).Build());
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task Connect_Client()
|
||||
{
|
||||
/*
|
||||
* This sample creates a simple MQTT client and connects to a public broker.
|
||||
*
|
||||
* Always dispose the client when it is no longer used.
|
||||
* The default version of MQTT is 3.1.1.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
{
|
||||
// Use builder classes where possible in this project.
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||
|
||||
// This will throw an exception if the server is not available.
|
||||
// The result from this message returns additional data which was sent
|
||||
// from the server. Please refer to the MQTT protocol specification for details.
|
||||
var response = await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
Console.WriteLine("The MQTT client is connected.");
|
||||
|
||||
response.DumpToConsole();
|
||||
|
||||
// Send a clean disconnect to the server by calling _DisconnectAsync_. Without this the TCP connection
|
||||
// gets dropped and the server will handle this as a non clean disconnect (see MQTT spec for details).
|
||||
var mqttClientDisconnectOptions = mqttFactory.CreateClientDisconnectOptionsBuilder().Build();
|
||||
|
||||
await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task Connect_Client_Timeout()
|
||||
{
|
||||
/*
|
||||
* This sample creates a simple MQTT client and connects to an invalid broker using a timeout.
|
||||
*
|
||||
* This is a modified version of the sample _Connect_Client_! See other sample for more details.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
{
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1").Build();
|
||||
|
||||
try
|
||||
{
|
||||
using (var timeoutToken = new CancellationTokenSource(TimeSpan.FromSeconds(1)))
|
||||
{
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, timeoutToken.Token);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
Console.WriteLine("Timeout while connecting.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task Connect_Client_Using_MQTTv5()
|
||||
{
|
||||
/*
|
||||
* This sample creates a simple MQTT client and connects to a public broker using MQTTv5.
|
||||
*
|
||||
* This is a modified version of the sample _Connect_Client_! See other sample for more details.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
{
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").WithProtocolVersion(MqttProtocolVersion.V500).Build();
|
||||
|
||||
// In MQTTv5 the response contains much more information.
|
||||
var response = await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
Console.WriteLine("The MQTT client is connected.");
|
||||
|
||||
response.DumpToConsole();
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task Connect_Client_Using_TLS_1_2()
|
||||
{
|
||||
/*
|
||||
* This sample creates a simple MQTT client and connects to a public broker using TLS 1.2 encryption.
|
||||
*
|
||||
* This is a modified version of the sample _Connect_Client_! See other sample for more details.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
{
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("mqtt.fluux.io")
|
||||
.WithTlsOptions(
|
||||
o =>
|
||||
{
|
||||
// The used public broker sometimes has invalid certificates. This sample accepts all
|
||||
// certificates. This should not be used in live environments.
|
||||
o.WithCertificateValidationHandler(_ => true);
|
||||
|
||||
// The default value is determined by the OS. Set manually to force version.
|
||||
o.WithSslProtocols(SslProtocols.Tls12);
|
||||
})
|
||||
.Build();
|
||||
|
||||
using (var timeout = new CancellationTokenSource(5000))
|
||||
{
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, timeout.Token);
|
||||
|
||||
Console.WriteLine("The MQTT client is connected.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task Connect_Client_Using_WebSockets()
|
||||
{
|
||||
/*
|
||||
* This sample creates a simple MQTT client and connects to a public broker using a WebSocket connection.
|
||||
*
|
||||
* This is a modified version of the sample _Connect_Client_! See other sample for more details.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
{
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("broker.hivemq.com:8000/mqtt")).Build();
|
||||
|
||||
var response = await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
Console.WriteLine("The MQTT client is connected.");
|
||||
|
||||
response.DumpToConsole();
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task Connect_Client_With_TLS_Encryption()
|
||||
{
|
||||
/*
|
||||
* This sample creates a simple MQTT client and connects to a public broker with enabled TLS encryption.
|
||||
*
|
||||
* This is a modified version of the sample _Connect_Client_! See other sample for more details.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
{
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883)
|
||||
.WithTlsOptions(
|
||||
o => o.WithCertificateValidationHandler(
|
||||
// The used public broker sometimes has invalid certificates. This sample accepts all
|
||||
// certificates. This should not be used in live environments.
|
||||
_ => true))
|
||||
.Build();
|
||||
|
||||
// In MQTTv5 the response contains much more information.
|
||||
using (var timeout = new CancellationTokenSource(5000))
|
||||
{
|
||||
var response = await mqttClient.ConnectAsync(mqttClientOptions, timeout.Token);
|
||||
|
||||
Console.WriteLine("The MQTT client is connected.");
|
||||
|
||||
response.DumpToConsole();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task Connect_With_Amazon_AWS()
|
||||
{
|
||||
/*
|
||||
* This sample creates a simple MQTT client and connects to an Amazon Web Services broker.
|
||||
*
|
||||
* The broker requires special settings which are set here.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
{
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("amazon.web.services.broker")
|
||||
// Disabling packet fragmentation is very important!
|
||||
.WithoutPacketFragmentation()
|
||||
.Build();
|
||||
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
Console.WriteLine("The MQTT client is connected.");
|
||||
|
||||
await mqttClient.DisconnectAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task Disconnect_Clean()
|
||||
{
|
||||
/*
|
||||
* This sample disconnects from the server with sending a DISCONNECT packet.
|
||||
* This way of disconnecting is treated as a clean disconnect which will not
|
||||
* trigger sending the last will etc.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
{
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
// Calling _DisconnectAsync_ will send a DISCONNECT packet before closing the connection.
|
||||
// Using a reason code requires MQTT version 5.0.0!
|
||||
await mqttClient.DisconnectAsync(MqttClientDisconnectOptionsReason.ImplementationSpecificError);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task Disconnect_Non_Clean()
|
||||
{
|
||||
/*
|
||||
* This sample disconnects from the server without sending a DISCONNECT packet.
|
||||
* This way of disconnecting is treated as a non clean disconnect which will
|
||||
* trigger sending the last will etc.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
var mqttClient = mqttFactory.CreateMqttClient();
|
||||
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
// Calling _Dispose_ or use of a _using_ statement will close the transport connection
|
||||
// without sending a DISCONNECT packet to the server.
|
||||
mqttClient.Dispose();
|
||||
}
|
||||
|
||||
public static async Task Inspect_Certificate_Validation_Errors()
|
||||
{
|
||||
/*
|
||||
* This sample prints the certificate information while connection. This data can be used to decide whether a connection is secure or not
|
||||
* including the reason for that status.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
{
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("mqtt.fluux.io", 8883)
|
||||
.WithTlsOptions(
|
||||
o =>
|
||||
{
|
||||
o.WithCertificateValidationHandler(
|
||||
eventArgs =>
|
||||
{
|
||||
eventArgs.Certificate.Subject.DumpToConsole();
|
||||
eventArgs.Certificate.GetExpirationDateString().DumpToConsole();
|
||||
eventArgs.Chain.ChainPolicy.RevocationMode.DumpToConsole();
|
||||
eventArgs.Chain.ChainStatus.DumpToConsole();
|
||||
eventArgs.SslPolicyErrors.DumpToConsole();
|
||||
return true;
|
||||
});
|
||||
})
|
||||
.Build();
|
||||
|
||||
// In MQTTv5 the response contains much more information.
|
||||
using (var timeout = new CancellationTokenSource(5000))
|
||||
{
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, timeout.Token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task Ping_Server()
|
||||
{
|
||||
/*
|
||||
* This sample sends a PINGREQ packet to the server and waits for a reply.
|
||||
*
|
||||
* This is only supported in MQTTv5.0.0+.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
{
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
// This will throw an exception if the server does not reply.
|
||||
await mqttClient.PingAsync(CancellationToken.None);
|
||||
|
||||
Console.WriteLine("The MQTT server replied to the ping request.");
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task Reconnect_Using_Event()
|
||||
{
|
||||
/*
|
||||
* This sample shows how to reconnect when the connection was dropped.
|
||||
* This approach uses one of the events from the client.
|
||||
* This approach has a risk of dead locks! Consider using the timer approach (see sample).
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
{
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||
|
||||
mqttClient.DisconnectedAsync += async e =>
|
||||
{
|
||||
if (e.ClientWasConnected)
|
||||
{
|
||||
// Use the current options as the new options.
|
||||
await mqttClient.ConnectAsync(mqttClient.Options);
|
||||
}
|
||||
};
|
||||
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Reconnect_Using_Timer()
|
||||
{
|
||||
/*
|
||||
* This sample shows how to reconnect when the connection was dropped.
|
||||
* This approach uses a custom Task/Thread which will monitor the connection status.
|
||||
* This is the recommended way but requires more custom code!
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
{
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||
|
||||
_ = Task.Run(
|
||||
async () =>
|
||||
{
|
||||
// User proper cancellation and no while(true).
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
// This code will also do the very first connect! So no call to _ConnectAsync_ is required in the first place.
|
||||
if (!await mqttClient.TryPingAsync())
|
||||
{
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
// Subscribe to topics when session is clean etc.
|
||||
Console.WriteLine("The MQTT client is connected.");
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Handle the exception properly (logging etc.).
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Check the connection state every 5 seconds and perform a reconnect if required.
|
||||
await Task.Delay(TimeSpan.FromSeconds(5));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Console.WriteLine("Press <Enter> to exit");
|
||||
Console.ReadLine();
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task ConnectTls_WithCaFile()
|
||||
{
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
X509Certificate2Collection caChain = new X509Certificate2Collection();
|
||||
caChain.ImportFromPem(mosquitto_org); // from https://test.mosquitto.org/ssl/mosquitto.org.crt
|
||||
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
{
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder()
|
||||
.WithTcpServer("test.mosquitto.org", 8883)
|
||||
.WithTlsOptions(new MqttClientTlsOptionsBuilder()
|
||||
.WithTrustChain(caChain)
|
||||
.Build())
|
||||
.Build();
|
||||
|
||||
var connAck = await mqttClient.ConnectAsync(mqttClientOptions);
|
||||
Console.WriteLine("Connected to test.moquitto.org:8883 with CaFile mosquitto.org.crt: " + connAck.ResultCode);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
const string mosquitto_org = @"
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIEAzCCAuugAwIBAgIUBY1hlCGvdj4NhBXkZ/uLUZNILAwwDQYJKoZIhvcNAQEL
|
||||
@@ -460,4 +46,477 @@ m/XriWr/Cq4h/JfB7NTsezVslgkBaoU=
|
||||
-----END CERTIFICATE-----
|
||||
";
|
||||
|
||||
public static async Task Clean_Disconnect()
|
||||
{
|
||||
/*
|
||||
* This sample disconnects in a clean way. This will send a MQTT DISCONNECT packet
|
||||
* to the server and close the connection afterward.
|
||||
*
|
||||
* See sample _Connect_Client_ for more details.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
// This will send the DISCONNECT packet. Calling _Dispose_ without DisconnectAsync the
|
||||
// connection is closed in a "not clean" way. See MQTT specification for more details.
|
||||
await mqttClient.DisconnectAsync(new MqttClientDisconnectOptionsBuilder().WithReason(MqttClientDisconnectOptionsReason.NormalDisconnection).Build());
|
||||
}
|
||||
|
||||
public static async Task Connect()
|
||||
{
|
||||
/*
|
||||
* This sample creates a simple MQTT client and connects to a public broker.
|
||||
*
|
||||
* Always dispose the client when it is no longer used.
|
||||
* The default version of MQTT is 3.1.1.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||
// Use builder classes where possible in this project.
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||
|
||||
// This will throw an exception if the server is not available.
|
||||
// The result from this message returns additional data which was sent
|
||||
// from the server. Please refer to the MQTT protocol specification for details.
|
||||
var response = await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
Console.WriteLine("The MQTT client is connected.");
|
||||
|
||||
response.DumpToConsole();
|
||||
|
||||
// Send a clean disconnect to the server by calling _DisconnectAsync_. Without this the TCP connection
|
||||
// gets dropped and the server will handle this as a non clean disconnect (see MQTT spec for details).
|
||||
var mqttClientDisconnectOptions = mqttFactory.CreateClientDisconnectOptionsBuilder().Build();
|
||||
|
||||
await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None);
|
||||
}
|
||||
|
||||
public static async Task Connect_Using_Enhanced_Authentication()
|
||||
{
|
||||
/*
|
||||
* This sample uses enhanced authentication (Kerberos) when creating the connection.
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* Server part...
|
||||
*/
|
||||
|
||||
var mqttServerFactory = new MqttServerFactory();
|
||||
|
||||
var serverOptions = mqttServerFactory.CreateServerOptionsBuilder().WithDefaultEndpoint().Build();
|
||||
var server = mqttServerFactory.CreateMqttServer(serverOptions);
|
||||
|
||||
server.ValidatingConnectionAsync += async args =>
|
||||
{
|
||||
if (args.AuthenticationMethod == "GS2-KRB5")
|
||||
{
|
||||
var result = await args.ExchangeEnhancedAuthenticationAsync(new ExchangeEnhancedAuthenticationOptions(), args.CancellationToken);
|
||||
|
||||
Console.WriteLine($"Received AUTH data from client: {Encoding.UTF8.GetString(result.AuthenticationData)}");
|
||||
|
||||
var authOptions = mqttServerFactory.CreateExchangeExtendedAuthenticationOptionsBuilder().WithAuthenticationData("reply context token").Build();
|
||||
|
||||
result = await args.ExchangeEnhancedAuthenticationAsync(authOptions, args.CancellationToken);
|
||||
|
||||
Console.WriteLine($"Received AUTH data from client: {Encoding.UTF8.GetString(result.AuthenticationData)}");
|
||||
|
||||
args.ResponseAuthenticationData = "outcome of authentication"u8.ToArray();
|
||||
|
||||
// Authentication DONE!
|
||||
args.ReasonCode = MqttConnectReasonCode.Success; // Also the default!
|
||||
}
|
||||
else
|
||||
{
|
||||
args.ReasonCode = MqttConnectReasonCode.BadAuthenticationMethod;
|
||||
}
|
||||
};
|
||||
|
||||
await server.StartAsync();
|
||||
|
||||
/*
|
||||
* Client part...
|
||||
*/
|
||||
|
||||
var mqttClientFactory = new MqttClientFactory();
|
||||
|
||||
// Use Kerberos sample from the MQTT RFC.
|
||||
var kerberosAuthenticationHandler = new SampleClientKerberosAuthenticationHandler();
|
||||
|
||||
var clientOptions = mqttClientFactory.CreateClientOptionsBuilder()
|
||||
.WithTcpServer("localhost")
|
||||
.WithProtocolVersion(MqttProtocolVersion.V500)
|
||||
.WithEnhancedAuthenticationHandler(kerberosAuthenticationHandler)
|
||||
.WithEnhancedAuthentication("GS2-KRB5")
|
||||
.Build();
|
||||
|
||||
var client = mqttClientFactory.CreateMqttClient();
|
||||
|
||||
var result = await client.ConnectAsync(clientOptions);
|
||||
|
||||
Console.WriteLine($"Client connect result: {result.ResultCode}");
|
||||
}
|
||||
|
||||
public static async Task Connect_Using_MQTTv5()
|
||||
{
|
||||
/*
|
||||
* This sample creates a simple MQTT client and connects to a public broker using MQTTv5.
|
||||
*
|
||||
* This is a modified version of the sample _Connect_Client_! See other sample for more details.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").WithProtocolVersion(MqttProtocolVersion.V500).Build();
|
||||
|
||||
// In MQTTv5 the response contains much more information.
|
||||
var response = await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
Console.WriteLine("The MQTT client is connected.");
|
||||
|
||||
response.DumpToConsole();
|
||||
}
|
||||
|
||||
public static async Task Connect_Using_TLS_1_2()
|
||||
{
|
||||
/*
|
||||
* This sample creates a simple MQTT client and connects to a public broker using TLS 1.2 encryption.
|
||||
*
|
||||
* This is a modified version of the sample _Connect_Client_! See other sample for more details.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("mqtt.fluux.io")
|
||||
.WithTlsOptions(
|
||||
o =>
|
||||
{
|
||||
// The used public broker sometimes has invalid certificates. This sample accepts all
|
||||
// certificates. This should not be used in live environments.
|
||||
o.WithCertificateValidationHandler(_ => true);
|
||||
|
||||
// The default value is determined by the OS. Set manually to force version.
|
||||
o.WithSslProtocols(SslProtocols.Tls12);
|
||||
})
|
||||
.Build();
|
||||
|
||||
using var timeout = new CancellationTokenSource(5000);
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, timeout.Token);
|
||||
|
||||
Console.WriteLine("The MQTT client is connected.");
|
||||
}
|
||||
|
||||
public static async Task Connect_Using_TLS_Encryption()
|
||||
{
|
||||
/*
|
||||
* This sample creates a simple MQTT client and connects to a public broker with enabled TLS encryption.
|
||||
*
|
||||
* This is a modified version of the sample _Connect_Client_! See other sample for more details.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883)
|
||||
.WithTlsOptions(
|
||||
o => o.WithCertificateValidationHandler(
|
||||
// The used public broker sometimes has invalid certificates. This sample accepts all
|
||||
// certificates. This should not be used in live environments.
|
||||
_ => true))
|
||||
.Build();
|
||||
|
||||
// In MQTTv5 the response contains much more information.
|
||||
using var timeout = new CancellationTokenSource(5000);
|
||||
var response = await mqttClient.ConnectAsync(mqttClientOptions, timeout.Token);
|
||||
|
||||
Console.WriteLine("The MQTT client is connected.");
|
||||
|
||||
response.DumpToConsole();
|
||||
}
|
||||
|
||||
public static async Task Connect_Using_TLS_With_CA_File()
|
||||
{
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
var caChain = new X509Certificate2Collection();
|
||||
caChain.ImportFromPem(mosquitto_org); // from https://test.mosquitto.org/ssl/mosquitto.org.crt
|
||||
|
||||
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883)
|
||||
.WithTlsOptions(new MqttClientTlsOptionsBuilder().WithTrustChain(caChain).Build())
|
||||
.Build();
|
||||
|
||||
var connAck = await mqttClient.ConnectAsync(mqttClientOptions);
|
||||
Console.WriteLine("Connected to test.moquitto.org:8883 with CaFile mosquitto.org.crt: " + connAck.ResultCode);
|
||||
}
|
||||
|
||||
public static async Task Connect_Using_WebSockets()
|
||||
{
|
||||
/*
|
||||
* This sample creates a simple MQTT client and connects to a public broker using a WebSocket connection.
|
||||
*
|
||||
* This is a modified version of the sample _Connect_Client_! See other sample for more details.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("broker.hivemq.com:8000/mqtt")).Build();
|
||||
|
||||
var response = await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
Console.WriteLine("The MQTT client is connected.");
|
||||
|
||||
response.DumpToConsole();
|
||||
}
|
||||
|
||||
public static async Task Connect_With_Amazon_AWS()
|
||||
{
|
||||
/*
|
||||
* This sample creates a simple MQTT client and connects to an Amazon Web Services broker.
|
||||
*
|
||||
* The broker requires special settings which are set here.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("amazon.web.services.broker")
|
||||
// Disabling packet fragmentation is very important!
|
||||
.WithoutPacketFragmentation()
|
||||
.Build();
|
||||
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
Console.WriteLine("The MQTT client is connected.");
|
||||
|
||||
await mqttClient.DisconnectAsync();
|
||||
}
|
||||
|
||||
public static async Task Disconnect_Clean()
|
||||
{
|
||||
/*
|
||||
* This sample disconnects from the server with sending a DISCONNECT packet.
|
||||
* This way of disconnecting is treated as a clean disconnect which will not
|
||||
* trigger sending the last will etc.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
// Calling _DisconnectAsync_ will send a DISCONNECT packet before closing the connection.
|
||||
// Using a reason code requires MQTT version 5.0.0!
|
||||
await mqttClient.DisconnectAsync(MqttClientDisconnectOptionsReason.ImplementationSpecificError);
|
||||
}
|
||||
|
||||
public static async Task Disconnect_Non_Clean()
|
||||
{
|
||||
/*
|
||||
* This sample disconnects from the server without sending a DISCONNECT packet.
|
||||
* This way of disconnecting is treated as a non-clean disconnect which will
|
||||
* trigger sending the last will etc.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
var mqttClient = mqttFactory.CreateMqttClient();
|
||||
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
// Calling _Dispose_ or use of a _using_ statement will close the transport connection
|
||||
// without sending a DISCONNECT packet to the server.
|
||||
mqttClient.Dispose();
|
||||
}
|
||||
|
||||
public static async Task Inspect_Certificate_Validation_Errors()
|
||||
{
|
||||
/*
|
||||
* This sample prints the certificate information while connection. This data can be used to decide whether a connection is secure or not
|
||||
* including the reason for that status.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("mqtt.fluux.io", 8883)
|
||||
.WithTlsOptions(
|
||||
o =>
|
||||
{
|
||||
o.WithCertificateValidationHandler(
|
||||
eventArgs =>
|
||||
{
|
||||
eventArgs.Certificate.Subject.DumpToConsole();
|
||||
eventArgs.Certificate.GetExpirationDateString().DumpToConsole();
|
||||
eventArgs.Chain.ChainPolicy.RevocationMode.DumpToConsole();
|
||||
eventArgs.Chain.ChainStatus.DumpToConsole();
|
||||
eventArgs.SslPolicyErrors.DumpToConsole();
|
||||
return true;
|
||||
});
|
||||
})
|
||||
.Build();
|
||||
|
||||
// In MQTTv5 the response contains much more information.
|
||||
using var timeout = new CancellationTokenSource(5000);
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, timeout.Token);
|
||||
}
|
||||
|
||||
public static async Task Ping_Server()
|
||||
{
|
||||
/*
|
||||
* This sample sends a PINGREQ packet to the server and waits for a reply.
|
||||
*
|
||||
* This is only supported in MQTTv5.0.0+.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
// This will throw an exception if the server does not reply.
|
||||
await mqttClient.PingAsync(CancellationToken.None);
|
||||
|
||||
Console.WriteLine("The MQTT server replied to the ping request.");
|
||||
}
|
||||
|
||||
public static async Task Reconnect_Using_Event()
|
||||
{
|
||||
/*
|
||||
* This sample shows how to reconnect when the connection was dropped.
|
||||
* This approach uses one of the events from the client.
|
||||
* This approach has a risk of deadlocks! Consider using the timer approach (see sample).
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||
|
||||
mqttClient.DisconnectedAsync += async e =>
|
||||
{
|
||||
if (e.ClientWasConnected)
|
||||
{
|
||||
// Use the current options as the new options.
|
||||
await mqttClient.ConnectAsync(mqttClientOptions);
|
||||
}
|
||||
};
|
||||
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
}
|
||||
|
||||
public static void Reconnect_Using_Timer()
|
||||
{
|
||||
/*
|
||||
* This sample shows how to reconnect when the connection was dropped.
|
||||
* This approach uses a custom Task/Thread which will monitor the connection status.
|
||||
* This is the recommended way but requires more custom code!
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||
|
||||
_ = Task.Run(
|
||||
async () =>
|
||||
{
|
||||
// User proper cancellation and no while(true).
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
// This code will also do the very first connect! So no call to _ConnectAsync_ is required in the first place.
|
||||
if (!await mqttClient.TryPingAsync())
|
||||
{
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
// Subscribe to topics when session is clean etc.
|
||||
Console.WriteLine("The MQTT client is connected.");
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Handle the exception properly (logging etc.).
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Check the connection state every 5 seconds and perform a reconnect if required.
|
||||
await Task.Delay(TimeSpan.FromSeconds(5));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Console.WriteLine("Press <Enter> to exit");
|
||||
Console.ReadLine();
|
||||
}
|
||||
|
||||
public static async Task Timeout()
|
||||
{
|
||||
/*
|
||||
* This sample creates a simple MQTT client and connects to an invalid broker using a timeout.
|
||||
*
|
||||
* This is a modified version of the sample _Connect_Client_! See other sample for more details.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1").Build();
|
||||
|
||||
try
|
||||
{
|
||||
using var timeoutToken = new CancellationTokenSource(TimeSpan.FromSeconds(1));
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, timeoutToken.Token);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
Console.WriteLine("Timeout while connecting.");
|
||||
}
|
||||
}
|
||||
|
||||
sealed class SampleClientKerberosAuthenticationHandler : IMqttEnhancedAuthenticationHandler
|
||||
{
|
||||
public async Task HandleEnhancedAuthenticationAsync(MqttEnhancedAuthenticationEventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs.AuthenticationMethod != "GS2-KRB5")
|
||||
{
|
||||
throw new InvalidOperationException("Wrong authentication method");
|
||||
}
|
||||
|
||||
var sendOptions = new SendMqttEnhancedAuthenticationDataOptions
|
||||
{
|
||||
Data = "initial context token"u8.ToArray()
|
||||
};
|
||||
|
||||
await eventArgs.SendAsync(sendOptions);
|
||||
|
||||
var response = await eventArgs.ReceiveAsync(CancellationToken.None);
|
||||
|
||||
Console.WriteLine($"Received AUTH data from server: {Encoding.UTF8.GetString(response.AuthenticationData)}");
|
||||
|
||||
// No further data is required, but we have to fulfil the exchange.
|
||||
sendOptions = new SendMqttEnhancedAuthenticationDataOptions
|
||||
{
|
||||
Data = []
|
||||
};
|
||||
|
||||
await eventArgs.SendAsync(sendOptions, CancellationToken.None);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,25 +23,23 @@ public static class Client_Publish_Samples
|
||||
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
{
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder()
|
||||
.WithTcpServer("broker.hivemq.com")
|
||||
.Build();
|
||||
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder()
|
||||
.WithTcpServer("broker.hivemq.com")
|
||||
.Build();
|
||||
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
var applicationMessage = new MqttApplicationMessageBuilder()
|
||||
.WithTopic("samples/temperature/living_room")
|
||||
.WithPayload("19.5")
|
||||
.Build();
|
||||
var applicationMessage = new MqttApplicationMessageBuilder()
|
||||
.WithTopic("samples/temperature/living_room")
|
||||
.WithPayload("19.5")
|
||||
.Build();
|
||||
|
||||
await mqttClient.PublishAsync(applicationMessage, CancellationToken.None);
|
||||
await mqttClient.PublishAsync(applicationMessage, CancellationToken.None);
|
||||
|
||||
await mqttClient.DisconnectAsync();
|
||||
await mqttClient.DisconnectAsync();
|
||||
|
||||
Console.WriteLine("MQTT application message is published.");
|
||||
}
|
||||
Console.WriteLine("MQTT application message is published.");
|
||||
}
|
||||
|
||||
public static async Task Publish_Multiple_Application_Messages()
|
||||
@@ -54,38 +52,36 @@ public static class Client_Publish_Samples
|
||||
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
{
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder()
|
||||
.WithTcpServer("broker.hivemq.com")
|
||||
.Build();
|
||||
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder()
|
||||
.WithTcpServer("broker.hivemq.com")
|
||||
.Build();
|
||||
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
var applicationMessage = new MqttApplicationMessageBuilder()
|
||||
.WithTopic("samples/temperature/living_room")
|
||||
.WithPayload("19.5")
|
||||
.Build();
|
||||
var applicationMessage = new MqttApplicationMessageBuilder()
|
||||
.WithTopic("samples/temperature/living_room")
|
||||
.WithPayload("19.5")
|
||||
.Build();
|
||||
|
||||
await mqttClient.PublishAsync(applicationMessage, CancellationToken.None);
|
||||
await mqttClient.PublishAsync(applicationMessage, CancellationToken.None);
|
||||
|
||||
applicationMessage = new MqttApplicationMessageBuilder()
|
||||
.WithTopic("samples/temperature/living_room")
|
||||
.WithPayload("20.0")
|
||||
.Build();
|
||||
applicationMessage = new MqttApplicationMessageBuilder()
|
||||
.WithTopic("samples/temperature/living_room")
|
||||
.WithPayload("20.0")
|
||||
.Build();
|
||||
|
||||
await mqttClient.PublishAsync(applicationMessage, CancellationToken.None);
|
||||
await mqttClient.PublishAsync(applicationMessage, CancellationToken.None);
|
||||
|
||||
applicationMessage = new MqttApplicationMessageBuilder()
|
||||
.WithTopic("samples/temperature/living_room")
|
||||
.WithPayload("21.0")
|
||||
.Build();
|
||||
applicationMessage = new MqttApplicationMessageBuilder()
|
||||
.WithTopic("samples/temperature/living_room")
|
||||
.WithPayload("21.0")
|
||||
.Build();
|
||||
|
||||
await mqttClient.PublishAsync(applicationMessage, CancellationToken.None);
|
||||
await mqttClient.PublishAsync(applicationMessage, CancellationToken.None);
|
||||
|
||||
await mqttClient.DisconnectAsync();
|
||||
await mqttClient.DisconnectAsync();
|
||||
|
||||
Console.WriteLine("MQTT application message is published.");
|
||||
}
|
||||
Console.WriteLine("MQTT application message is published.");
|
||||
}
|
||||
}
|
||||
@@ -26,32 +26,30 @@ public static class Client_Subscribe_Samples
|
||||
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||
|
||||
// Setup message handling before connecting so that queued messages
|
||||
// are also handled properly. When there is no event handler attached all
|
||||
// received messages get lost.
|
||||
mqttClient.ApplicationMessageReceivedAsync += e =>
|
||||
{
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||
Console.WriteLine("Received application message.");
|
||||
e.DumpToConsole();
|
||||
|
||||
// Setup message handling before connecting so that queued messages
|
||||
// are also handled properly. When there is no event handler attached all
|
||||
// received messages get lost.
|
||||
mqttClient.ApplicationMessageReceivedAsync += e =>
|
||||
{
|
||||
Console.WriteLine("Received application message.");
|
||||
e.DumpToConsole();
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder().WithTopicTemplate(sampleTemplate.WithParameter("id", "2")).Build();
|
||||
|
||||
var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder().WithTopicTemplate(sampleTemplate.WithParameter("id", "2")).Build();
|
||||
await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None);
|
||||
|
||||
await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None);
|
||||
Console.WriteLine("MQTT client subscribed to topic.");
|
||||
|
||||
Console.WriteLine("MQTT client subscribed to topic.");
|
||||
|
||||
Console.WriteLine("Press enter to exit.");
|
||||
Console.ReadLine();
|
||||
}
|
||||
Console.WriteLine("Press enter to exit.");
|
||||
Console.ReadLine();
|
||||
}
|
||||
|
||||
public static async Task Send_Responses()
|
||||
@@ -62,36 +60,34 @@ public static class Client_Subscribe_Samples
|
||||
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||
mqttClient.ApplicationMessageReceivedAsync += delegate(MqttApplicationMessageReceivedEventArgs args)
|
||||
{
|
||||
mqttClient.ApplicationMessageReceivedAsync += delegate(MqttApplicationMessageReceivedEventArgs args)
|
||||
{
|
||||
// Do some work with the message...
|
||||
// Do some work with the message...
|
||||
|
||||
// Now respond to the broker with a reason code other than success.
|
||||
args.ReasonCode = MqttApplicationMessageReceivedReasonCode.ImplementationSpecificError;
|
||||
args.ResponseReasonString = "That did not work!";
|
||||
// Now respond to the broker with a reason code other than success.
|
||||
args.ReasonCode = MqttApplicationMessageReceivedReasonCode.ImplementationSpecificError;
|
||||
args.ResponseReasonString = "That did not work!";
|
||||
|
||||
// User properties require MQTT v5!
|
||||
args.ResponseUserProperties.Add(new MqttUserProperty("My", "Data"));
|
||||
// User properties require MQTT v5!
|
||||
args.ResponseUserProperties.Add(new MqttUserProperty("My", "Data"));
|
||||
|
||||
// Now the broker will resend the message again.
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
// Now the broker will resend the message again.
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder().WithTopicTemplate(sampleTemplate.WithParameter("id", "1")).Build();
|
||||
var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder().WithTopicTemplate(sampleTemplate.WithParameter("id", "1")).Build();
|
||||
|
||||
var response = await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None);
|
||||
var response = await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None);
|
||||
|
||||
Console.WriteLine("MQTT client subscribed to topic.");
|
||||
Console.WriteLine("MQTT client subscribed to topic.");
|
||||
|
||||
// The response contains additional data sent by the server after subscribing.
|
||||
response.DumpToConsole();
|
||||
}
|
||||
// The response contains additional data sent by the server after subscribing.
|
||||
response.DumpToConsole();
|
||||
}
|
||||
|
||||
public static async Task Subscribe_Multiple_Topics()
|
||||
@@ -102,27 +98,25 @@ public static class Client_Subscribe_Samples
|
||||
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
{
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
// Create the subscribe options including several topics with different options.
|
||||
// It is also possible to all of these topics using a dedicated call of _SubscribeAsync_ per topic.
|
||||
var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder()
|
||||
.WithTopicTemplate(sampleTemplate.WithParameter("id", "1"))
|
||||
.WithTopicTemplate(sampleTemplate.WithParameter("id", "2"), noLocal: true)
|
||||
.WithTopicTemplate(sampleTemplate.WithParameter("id", "3"), retainHandling: MqttRetainHandling.SendAtSubscribe)
|
||||
.Build();
|
||||
// Create the subscribe options including several topics with different options.
|
||||
// It is also possible to all of these topics using a dedicated call of _SubscribeAsync_ per topic.
|
||||
var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder()
|
||||
.WithTopicTemplate(sampleTemplate.WithParameter("id", "1"))
|
||||
.WithTopicTemplate(sampleTemplate.WithParameter("id", "2"), noLocal: true)
|
||||
.WithTopicTemplate(sampleTemplate.WithParameter("id", "3"), retainHandling: MqttRetainHandling.SendAtSubscribe)
|
||||
.Build();
|
||||
|
||||
var response = await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None);
|
||||
var response = await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None);
|
||||
|
||||
Console.WriteLine("MQTT client subscribed to topics.");
|
||||
Console.WriteLine("MQTT client subscribed to topics.");
|
||||
|
||||
// The response contains additional data sent by the server after subscribing.
|
||||
response.DumpToConsole();
|
||||
}
|
||||
// The response contains additional data sent by the server after subscribing.
|
||||
response.DumpToConsole();
|
||||
}
|
||||
|
||||
public static async Task Subscribe_Topic()
|
||||
@@ -133,24 +127,22 @@ public static class Client_Subscribe_Samples
|
||||
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
{
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder().WithTopicTemplate(sampleTemplate.WithParameter("id", "1")).Build();
|
||||
var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder().WithTopicTemplate(sampleTemplate.WithParameter("id", "1")).Build();
|
||||
|
||||
var response = await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None);
|
||||
var response = await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None);
|
||||
|
||||
Console.WriteLine("MQTT client subscribed to topic.");
|
||||
Console.WriteLine("MQTT client subscribed to topic.");
|
||||
|
||||
// The response contains additional data sent by the server after subscribing.
|
||||
response.DumpToConsole();
|
||||
}
|
||||
// The response contains additional data sent by the server after subscribing.
|
||||
response.DumpToConsole();
|
||||
}
|
||||
|
||||
static void ConcurrentProcessingDisableAutoAcknowledge(CancellationToken shutdownToken, IMqttClient mqttClient)
|
||||
static void ConcurrentProcessingDisableAutoAcknowledge(IMqttClient mqttClient, CancellationToken shutdownToken)
|
||||
{
|
||||
/*
|
||||
* This sample shows how to achieve concurrent processing and not have message AutoAcknowledged
|
||||
@@ -178,7 +170,7 @@ public static class Client_Subscribe_Samples
|
||||
};
|
||||
}
|
||||
|
||||
static void ConcurrentProcessingWithLimit(CancellationToken shutdownToken, IMqttClient mqttClient)
|
||||
static void ConcurrentProcessingWithLimit(IMqttClient mqttClient, CancellationToken shutdownToken)
|
||||
{
|
||||
/*
|
||||
* This sample shows how to achieve concurrent processing, with:
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
// ReSharper disable UnusedMember.Global
|
||||
// ReSharper disable InconsistentNaming
|
||||
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using MQTTnet.Diagnostics.Logger;
|
||||
|
||||
@@ -26,17 +27,15 @@ public static class Logger_Samples
|
||||
.WithTcpServer("broker.hivemq.com")
|
||||
.Build();
|
||||
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
{
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
Console.WriteLine("MQTT client is connected.");
|
||||
Console.WriteLine("MQTT client is connected.");
|
||||
|
||||
var mqttClientDisconnectOptions = mqttFactory.CreateClientDisconnectOptionsBuilder()
|
||||
.Build();
|
||||
var mqttClientDisconnectOptions = mqttFactory.CreateClientDisconnectOptionsBuilder()
|
||||
.Build();
|
||||
|
||||
await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None);
|
||||
}
|
||||
await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None);
|
||||
}
|
||||
|
||||
public static async Task Use_Event_Logger()
|
||||
@@ -50,10 +49,10 @@ public static class Logger_Samples
|
||||
// The logger ID is optional but can be set do distinguish different logger instances.
|
||||
var mqttEventLogger = new MqttNetEventLogger("MyCustomLogger");
|
||||
|
||||
mqttEventLogger.LogMessagePublished += (sender, args) =>
|
||||
mqttEventLogger.LogMessagePublished += (_, args) =>
|
||||
{
|
||||
var output = new StringBuilder();
|
||||
output.AppendLine($">> [{args.LogMessage.Timestamp:O}] [{args.LogMessage.ThreadId}] [{args.LogMessage.Source}] [{args.LogMessage.Level}]: {args.LogMessage.Message}");
|
||||
output.AppendLine(CultureInfo.InvariantCulture, $">> [{args.LogMessage.Timestamp:O}] [{args.LogMessage.ThreadId}] [{args.LogMessage.Source}] [{args.LogMessage.Level}]: {args.LogMessage.Message}");
|
||||
if (args.LogMessage.Exception != null)
|
||||
{
|
||||
output.AppendLine(args.LogMessage.Exception.ToString());
|
||||
@@ -68,17 +67,15 @@ public static class Logger_Samples
|
||||
.WithTcpServer("broker.hivemq.com")
|
||||
.Build();
|
||||
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
{
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
Console.WriteLine("MQTT client is connected.");
|
||||
Console.WriteLine("MQTT client is connected.");
|
||||
|
||||
var mqttClientDisconnectOptions = mqttFactory.CreateClientDisconnectOptionsBuilder()
|
||||
.Build();
|
||||
var mqttClientDisconnectOptions = mqttFactory.CreateClientDisconnectOptionsBuilder()
|
||||
.Build();
|
||||
|
||||
await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None);
|
||||
}
|
||||
await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None);
|
||||
}
|
||||
|
||||
sealed class MyLogger : IMqttNetLogger
|
||||
|
||||
@@ -20,23 +20,21 @@ public static class PackageInspection_Samples
|
||||
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
{
|
||||
var mqttClientOptions = mqttFactory.CreateClientOptionsBuilder()
|
||||
.WithTcpServer("broker.hivemq.com")
|
||||
.Build();
|
||||
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||
var mqttClientOptions = mqttFactory.CreateClientOptionsBuilder()
|
||||
.WithTcpServer("broker.hivemq.com")
|
||||
.Build();
|
||||
|
||||
mqttClient.InspectPacketAsync += OnInspectPacket;
|
||||
mqttClient.InspectPacketAsync += OnInspectPacket;
|
||||
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
Console.WriteLine("MQTT client is connected.");
|
||||
Console.WriteLine("MQTT client is connected.");
|
||||
|
||||
var mqttClientDisconnectOptions = mqttFactory.CreateClientDisconnectOptionsBuilder()
|
||||
.Build();
|
||||
var mqttClientDisconnectOptions = mqttFactory.CreateClientDisconnectOptionsBuilder()
|
||||
.Build();
|
||||
|
||||
await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None);
|
||||
}
|
||||
await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None);
|
||||
}
|
||||
|
||||
static Task OnInspectPacket(InspectMqttPacketEventArgs eventArgs)
|
||||
|
||||
@@ -8,17 +8,19 @@ namespace MQTTnet.Samples.Helpers;
|
||||
|
||||
internal static class ObjectExtensions
|
||||
{
|
||||
static readonly JsonSerializerOptions SerializerOptions = new()
|
||||
{
|
||||
WriteIndented = true
|
||||
};
|
||||
|
||||
public static TObject DumpToConsole<TObject>(this TObject @object)
|
||||
{
|
||||
var output = "NULL";
|
||||
if (@object != null)
|
||||
{
|
||||
output = JsonSerializer.Serialize(@object, new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true
|
||||
});
|
||||
output = JsonSerializer.Serialize(@object, SerializerOptions);
|
||||
}
|
||||
|
||||
|
||||
Console.WriteLine($"[{@object?.GetType().Name}]:\r\n{output}");
|
||||
return @object;
|
||||
}
|
||||
|
||||
@@ -2,19 +2,10 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
||||
<EnableNETAnalyzers>false</EnableNETAnalyzers>
|
||||
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
|
||||
<NoWarn>1591;NETSDK1138;NU1803;NU1901;NU1902</NoWarn>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<NuGetAuditMode>all</NuGetAuditMode>
|
||||
<NuGetAudit>true</NuGetAudit>
|
||||
<NuGetAuditLevel>low</NuGetAuditLevel>
|
||||
<AnalysisLevel>latest-Recommended</AnalysisLevel>
|
||||
<NoWarn>$(NoWarn);CA1707</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
+4
-3
@@ -2,6 +2,7 @@
|
||||
// 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.Globalization;
|
||||
using System.Reflection;
|
||||
|
||||
Console.BackgroundColor = ConsoleColor.White;
|
||||
@@ -28,7 +29,7 @@ Console.Write("Please choose sample class (press Enter to continue): ");
|
||||
Console.ResetColor();
|
||||
|
||||
var input = Console.ReadLine();
|
||||
var selectedIndex = int.Parse(input ?? "0");
|
||||
var selectedIndex = int.Parse(input ?? "0", CultureInfo.InvariantCulture);
|
||||
var selectedSampleClass = sampleClasses[selectedIndex];
|
||||
var sampleMethods = selectedSampleClass.GetMethods(BindingFlags.Static | BindingFlags.Public).OrderBy(m => m.Name).ToList();
|
||||
|
||||
@@ -45,7 +46,7 @@ Console.Write("Please choose sample (press Enter to continue): ");
|
||||
Console.ResetColor();
|
||||
|
||||
input = Console.ReadLine();
|
||||
selectedIndex = int.Parse(input ?? "0");
|
||||
selectedIndex = int.Parse(input ?? "0", CultureInfo.InvariantCulture);
|
||||
var selectedSampleMethod = sampleMethods[selectedIndex];
|
||||
|
||||
Console.WriteLine();
|
||||
@@ -58,7 +59,7 @@ try
|
||||
{
|
||||
if (selectedSampleMethod.Invoke(null, null) is Task task)
|
||||
{
|
||||
await task;
|
||||
await task;
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
|
||||
@@ -26,23 +26,21 @@ public static class RpcClient_Samples
|
||||
// The RPC client is an addon for the existing client. So we need a regular client
|
||||
// which is wrapped later.
|
||||
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder()
|
||||
.WithTcpServer("broker.hivemq.com")
|
||||
.Build();
|
||||
|
||||
await mqttClient.ConnectAsync(mqttClientOptions);
|
||||
|
||||
using (var mqttRpcClient = mqttFactory.CreateMqttRpcClient(mqttClient))
|
||||
{
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder()
|
||||
.WithTcpServer("broker.hivemq.com")
|
||||
.Build();
|
||||
|
||||
await mqttClient.ConnectAsync(mqttClientOptions);
|
||||
|
||||
using (var mqttRpcClient = mqttFactory.CreateMqttRpcClient(mqttClient))
|
||||
{
|
||||
// Access to a fully featured application message is not supported for RPC calls!
|
||||
// The method will throw an exception when the response was not received in time.
|
||||
await mqttRpcClient.ExecuteAsync(TimeSpan.FromSeconds(2), "ping", "", MqttQualityOfServiceLevel.AtMostOnce);
|
||||
}
|
||||
|
||||
Console.WriteLine("The RPC call was successful.");
|
||||
// Access to a fully featured application message is not supported for RPC calls!
|
||||
// The method will throw an exception when the response was not received in time.
|
||||
await mqttRpcClient.ExecuteAsync(TimeSpan.FromSeconds(2), "ping", "", MqttQualityOfServiceLevel.AtMostOnce);
|
||||
}
|
||||
|
||||
Console.WriteLine("The RPC call was successful.");
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
// ReSharper disable EmptyConstructor
|
||||
// ReSharper disable MemberCanBeMadeStatic.Local
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
@@ -52,14 +53,14 @@ public static class Server_ASP_NET_Samples
|
||||
// Inject other services via constructor.
|
||||
}
|
||||
|
||||
public Task OnClientConnected(ClientConnectedEventArgs eventArgs)
|
||||
public static Task OnClientConnected(ClientConnectedEventArgs eventArgs)
|
||||
{
|
||||
Console.WriteLine($"Client '{eventArgs.ClientId}' connected.");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
|
||||
public Task ValidateConnection(ValidatingConnectionEventArgs eventArgs)
|
||||
public static Task ValidateConnection(ValidatingConnectionEventArgs eventArgs)
|
||||
{
|
||||
Console.WriteLine($"Client '{eventArgs.ClientId}' wants to connect. Accepting!");
|
||||
return Task.CompletedTask;
|
||||
@@ -68,6 +69,7 @@ public static class Server_ASP_NET_Samples
|
||||
|
||||
sealed class Startup
|
||||
{
|
||||
[SuppressMessage("Performance", "CA1822:Mark members as static")]
|
||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment environment, MqttController mqttController)
|
||||
{
|
||||
app.UseRouting();
|
||||
@@ -88,11 +90,12 @@ public static class Server_ASP_NET_Samples
|
||||
* Attach event handlers etc. if required.
|
||||
*/
|
||||
|
||||
server.ValidatingConnectionAsync += mqttController.ValidateConnection;
|
||||
server.ClientConnectedAsync += mqttController.OnClientConnected;
|
||||
server.ValidatingConnectionAsync += MqttController.ValidateConnection;
|
||||
server.ClientConnectedAsync += MqttController.OnClientConnected;
|
||||
});
|
||||
}
|
||||
|
||||
[SuppressMessage("Performance", "CA1822:Mark members as static")]
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddHostedMqttServer(
|
||||
|
||||
@@ -25,28 +25,26 @@ public static class Server_Diagnostics_Samples
|
||||
var mqttServerFactory = new MqttServerFactory();
|
||||
var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
|
||||
|
||||
using (var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions))
|
||||
using var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions);
|
||||
// Attach the event handler.
|
||||
mqttServer.ClientAcknowledgedPublishPacketAsync += e =>
|
||||
{
|
||||
// Attach the event handler.
|
||||
mqttServer.ClientAcknowledgedPublishPacketAsync += e =>
|
||||
{
|
||||
Console.WriteLine($"Client '{e.ClientId}' acknowledged packet {e.PublishPacket.PacketIdentifier} with topic '{e.PublishPacket.Topic}'");
|
||||
Console.WriteLine($"Client '{e.ClientId}' acknowledged packet {e.PublishPacket.PacketIdentifier} with topic '{e.PublishPacket.Topic}'");
|
||||
|
||||
// It is also possible to read additional data from the client response. This requires casting the response packet.
|
||||
var qos1AcknowledgePacket = e.AcknowledgePacket as MqttPubAckPacket;
|
||||
Console.WriteLine($"QoS 1 reason code: {qos1AcknowledgePacket?.ReasonCode}");
|
||||
// It is also possible to read additional data from the client response. This requires casting the response packet.
|
||||
var qos1AcknowledgePacket = e.AcknowledgePacket as MqttPubAckPacket;
|
||||
Console.WriteLine($"QoS 1 reason code: {qos1AcknowledgePacket?.ReasonCode}");
|
||||
|
||||
var qos2AcknowledgePacket = e.AcknowledgePacket as MqttPubCompPacket;
|
||||
Console.WriteLine($"QoS 2 reason code: {qos1AcknowledgePacket?.ReasonCode}");
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
var qos2AcknowledgePacket = e.AcknowledgePacket as MqttPubCompPacket;
|
||||
Console.WriteLine($"QoS 2 reason code: {qos2AcknowledgePacket?.ReasonCode}");
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
await mqttServer.StartAsync();
|
||||
await mqttServer.StartAsync();
|
||||
|
||||
Console.WriteLine("Press Enter to exit.");
|
||||
Console.ReadLine();
|
||||
Console.WriteLine("Press Enter to exit.");
|
||||
Console.ReadLine();
|
||||
|
||||
await mqttServer.StopAsync();
|
||||
}
|
||||
await mqttServer.StopAsync();
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@
|
||||
// ReSharper disable MemberCanBeMadeStatic.Local
|
||||
|
||||
using MQTTnet.Internal;
|
||||
using MQTTnet.Protocol;
|
||||
using MQTTnet.Server;
|
||||
|
||||
namespace MQTTnet.Samples.Server;
|
||||
@@ -25,23 +26,49 @@ public static class Server_Intercepting_Samples
|
||||
var mqttServerFactory = new MqttServerFactory();
|
||||
var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
|
||||
|
||||
using (var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions))
|
||||
using var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions);
|
||||
mqttServer.InterceptingPublishAsync += args =>
|
||||
{
|
||||
mqttServer.InterceptingPublishAsync += args =>
|
||||
// Here we only change the topic of the received application message.
|
||||
// but also changing the payload etc. is required. Changing the QoS after
|
||||
// transmitting is not supported and makes no sense at all.
|
||||
args.ApplicationMessage.Topic += "/manipulated";
|
||||
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
await mqttServer.StartAsync();
|
||||
|
||||
Console.WriteLine("Press Enter to exit.");
|
||||
Console.ReadLine();
|
||||
await mqttServer.StopAsync();
|
||||
}
|
||||
|
||||
public static async Task Intercept_Subscription()
|
||||
{
|
||||
/*
|
||||
* This sample starts a simple MQTT server which does not allow to subscribe to '#'
|
||||
* Start the server and try to subscribe to '#' with a client of choice.
|
||||
*/
|
||||
|
||||
var mqttServerFactory = new MqttServerFactory();
|
||||
var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
|
||||
|
||||
using var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions);
|
||||
mqttServer.InterceptingSubscriptionAsync += args =>
|
||||
{
|
||||
if (args.TopicFilter.Topic.Equals("#", StringComparison.Ordinal))
|
||||
{
|
||||
// Here we only change the topic of the received application message.
|
||||
// but also changing the payload etc. is required. Changing the QoS after
|
||||
// transmitting is not supported and makes no sense at all.
|
||||
args.ApplicationMessage.Topic += "/manipulated";
|
||||
args.Response.ReasonCode = MqttSubscribeReasonCode.NotAuthorized;
|
||||
}
|
||||
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
await mqttServer.StartAsync();
|
||||
await mqttServer.StartAsync();
|
||||
|
||||
Console.WriteLine("Press Enter to exit.");
|
||||
Console.ReadLine();
|
||||
await mqttServer.StopAsync();
|
||||
}
|
||||
Console.WriteLine("Press Enter to exit.");
|
||||
Console.ReadLine();
|
||||
await mqttServer.StopAsync();
|
||||
}
|
||||
}
|
||||
@@ -29,64 +29,62 @@ public static class Server_Retained_Messages_Samples
|
||||
// Due to security reasons the "default" endpoint (which is unencrypted) is not enabled by default!
|
||||
var mqttServerOptions = mqttServerFactory.CreateServerOptionsBuilder().WithDefaultEndpoint().Build();
|
||||
|
||||
using (var server = mqttServerFactory.CreateMqttServer(mqttServerOptions))
|
||||
using var server = mqttServerFactory.CreateMqttServer(mqttServerOptions);
|
||||
// Make sure that the server will load the retained messages.
|
||||
server.LoadingRetainedMessageAsync += async eventArgs =>
|
||||
{
|
||||
// Make sure that the server will load the retained messages.
|
||||
server.LoadingRetainedMessageAsync += async eventArgs =>
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
var models = await JsonSerializer.DeserializeAsync<List<MqttRetainedMessageModel>>(File.OpenRead(storePath)) ?? new List<MqttRetainedMessageModel>();
|
||||
var retainedMessages = models.Select(m => m.ToApplicationMessage()).ToList();
|
||||
var models = await JsonSerializer.DeserializeAsync<List<MqttRetainedMessageModel>>(File.OpenRead(storePath)) ?? new List<MqttRetainedMessageModel>();
|
||||
var retainedMessages = models.Select(m => m.ToApplicationMessage()).ToList();
|
||||
|
||||
eventArgs.LoadedRetainedMessages = retainedMessages;
|
||||
Console.WriteLine("Retained messages loaded.");
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
// Ignore because nothing is stored yet.
|
||||
Console.WriteLine("No retained messages stored yet.");
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Console.WriteLine(exception);
|
||||
}
|
||||
};
|
||||
|
||||
// Make sure to persist the changed retained messages.
|
||||
server.RetainedMessageChangedAsync += async eventArgs =>
|
||||
eventArgs.LoadedRetainedMessages = retainedMessages;
|
||||
Console.WriteLine("Retained messages loaded.");
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
try
|
||||
{
|
||||
// This sample uses the property _StoredRetainedMessages_ which will contain all(!) retained messages.
|
||||
// The event args also contain the affected retained message (property ChangedRetainedMessage). This can be
|
||||
// used to write all retained messages to dedicated files etc. Then all files must be loaded and a full list
|
||||
// of retained messages must be provided in the loaded event.
|
||||
|
||||
var models = eventArgs.StoredRetainedMessages.Select(MqttRetainedMessageModel.Create);
|
||||
|
||||
var buffer = JsonSerializer.SerializeToUtf8Bytes(models);
|
||||
await File.WriteAllBytesAsync(storePath, buffer);
|
||||
Console.WriteLine("Retained messages saved.");
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Console.WriteLine(exception);
|
||||
}
|
||||
};
|
||||
|
||||
// Make sure to clear the retained messages when they are all deleted via API.
|
||||
server.RetainedMessagesClearedAsync += _ =>
|
||||
// Ignore because nothing is stored yet.
|
||||
Console.WriteLine("No retained messages stored yet.");
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
File.Delete(storePath);
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
Console.WriteLine(exception);
|
||||
}
|
||||
};
|
||||
|
||||
await server.StartAsync();
|
||||
// Make sure to persist the changed retained messages.
|
||||
server.RetainedMessageChangedAsync += async eventArgs =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// This sample uses the property _StoredRetainedMessages_ which will contain all(!) retained messages.
|
||||
// The event args also contain the affected retained message (property ChangedRetainedMessage). This can be
|
||||
// used to write all retained messages to dedicated files etc. Then all files must be loaded and a full list
|
||||
// of retained messages must be provided in the loaded event.
|
||||
|
||||
Console.WriteLine("Press Enter to exit.");
|
||||
Console.ReadLine();
|
||||
}
|
||||
var models = eventArgs.StoredRetainedMessages.Select(MqttRetainedMessageModel.Create);
|
||||
|
||||
var buffer = JsonSerializer.SerializeToUtf8Bytes(models);
|
||||
await File.WriteAllBytesAsync(storePath, buffer);
|
||||
Console.WriteLine("Retained messages saved.");
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Console.WriteLine(exception);
|
||||
}
|
||||
};
|
||||
|
||||
// Make sure to clear the retained messages when they are all deleted via API.
|
||||
server.RetainedMessagesClearedAsync += _ =>
|
||||
{
|
||||
File.Delete(storePath);
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
|
||||
await server.StartAsync();
|
||||
|
||||
Console.WriteLine("Press Enter to exit.");
|
||||
Console.ReadLine();
|
||||
}
|
||||
|
||||
sealed class MqttRetainedMessageModel
|
||||
@@ -102,10 +100,7 @@ public static class Server_Retained_Messages_Samples
|
||||
|
||||
public static MqttRetainedMessageModel Create(MqttApplicationMessage message)
|
||||
{
|
||||
if (message == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(message));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(message);
|
||||
|
||||
return new MqttRetainedMessageModel
|
||||
{
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
// ReSharper disable UnusedMember.Global
|
||||
// ReSharper disable InconsistentNaming
|
||||
|
||||
using System.Globalization;
|
||||
using MQTTnet.Diagnostics.Logger;
|
||||
using MQTTnet.Protocol;
|
||||
using MQTTnet.Server;
|
||||
@@ -22,17 +23,15 @@ public static class Server_Simple_Samples
|
||||
* See _Run_Minimal_Server_ for more information.
|
||||
*/
|
||||
|
||||
using (var mqttServer = await StartMqttServer())
|
||||
{
|
||||
// Let the client connect.
|
||||
await Task.Delay(TimeSpan.FromSeconds(5));
|
||||
using var mqttServer = await StartMqttServer();
|
||||
// Let the client connect.
|
||||
await Task.Delay(TimeSpan.FromSeconds(5));
|
||||
|
||||
// Now disconnect the client (if connected).
|
||||
var affectedClient = (await mqttServer.GetClientsAsync()).FirstOrDefault(c => c.Id == "MyClient");
|
||||
if (affectedClient != null)
|
||||
{
|
||||
await affectedClient.DisconnectAsync();
|
||||
}
|
||||
// Now disconnect the client (if connected).
|
||||
var affectedClient = (await mqttServer.GetClientsAsync()).FirstOrDefault(c => c.Id == "MyClient");
|
||||
if (affectedClient != null)
|
||||
{
|
||||
await affectedClient.DisconnectAsync();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,18 +43,16 @@ public static class Server_Simple_Samples
|
||||
* See _Run_Minimal_Server_ for more information.
|
||||
*/
|
||||
|
||||
using (var mqttServer = await StartMqttServer())
|
||||
{
|
||||
// Create a new message using the builder as usual.
|
||||
var message = new MqttApplicationMessageBuilder().WithTopic("HelloWorld").WithPayload("Test").Build();
|
||||
using var mqttServer = await StartMqttServer();
|
||||
// Create a new message using the builder as usual.
|
||||
var message = new MqttApplicationMessageBuilder().WithTopic("HelloWorld").WithPayload("Test").Build();
|
||||
|
||||
// Now inject the new message at the broker.
|
||||
await mqttServer.InjectApplicationMessage(
|
||||
new InjectedMqttApplicationMessage(message)
|
||||
{
|
||||
SenderClientId = "SenderClientId"
|
||||
});
|
||||
}
|
||||
// Now inject the new message at the broker.
|
||||
await mqttServer.InjectApplicationMessage(
|
||||
new InjectedMqttApplicationMessage(message)
|
||||
{
|
||||
SenderClientId = "SenderClientId"
|
||||
});
|
||||
}
|
||||
|
||||
public static async Task Run_Minimal_Server()
|
||||
@@ -77,16 +74,14 @@ public static class Server_Simple_Samples
|
||||
// .WithDefaultEndpointPort(1234)
|
||||
// .Build();
|
||||
|
||||
using (var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions))
|
||||
{
|
||||
await mqttServer.StartAsync();
|
||||
using var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions);
|
||||
await mqttServer.StartAsync();
|
||||
|
||||
Console.WriteLine("Press Enter to exit.");
|
||||
Console.ReadLine();
|
||||
Console.WriteLine("Press Enter to exit.");
|
||||
Console.ReadLine();
|
||||
|
||||
// Stop and dispose the MQTT server if it is no longer needed!
|
||||
await mqttServer.StopAsync();
|
||||
}
|
||||
// Stop and dispose the MQTT server if it is no longer needed!
|
||||
await mqttServer.StopAsync();
|
||||
}
|
||||
|
||||
public static async Task Run_Server_With_Logging()
|
||||
@@ -103,16 +98,14 @@ public static class Server_Simple_Samples
|
||||
|
||||
var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
|
||||
|
||||
using (var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions))
|
||||
{
|
||||
await mqttServer.StartAsync();
|
||||
using var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions);
|
||||
await mqttServer.StartAsync();
|
||||
|
||||
Console.WriteLine("Press Enter to exit.");
|
||||
Console.ReadLine();
|
||||
Console.WriteLine("Press Enter to exit.");
|
||||
Console.ReadLine();
|
||||
|
||||
// Stop and dispose the MQTT server if it is no longer needed!
|
||||
await mqttServer.StopAsync();
|
||||
}
|
||||
// Stop and dispose the MQTT server if it is no longer needed!
|
||||
await mqttServer.StopAsync();
|
||||
}
|
||||
|
||||
public static async Task Validating_Connections()
|
||||
@@ -127,37 +120,35 @@ public static class Server_Simple_Samples
|
||||
|
||||
var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
|
||||
|
||||
using (var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions))
|
||||
using var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions);
|
||||
// Setup connection validation before starting the server so that there is
|
||||
// no change to connect without valid credentials.
|
||||
mqttServer.ValidatingConnectionAsync += e =>
|
||||
{
|
||||
// Setup connection validation before starting the server so that there is
|
||||
// no change to connect without valid credentials.
|
||||
mqttServer.ValidatingConnectionAsync += e =>
|
||||
if (e.ClientId != "ValidClientId")
|
||||
{
|
||||
if (e.ClientId != "ValidClientId")
|
||||
{
|
||||
e.ReasonCode = MqttConnectReasonCode.ClientIdentifierNotValid;
|
||||
}
|
||||
e.ReasonCode = MqttConnectReasonCode.ClientIdentifierNotValid;
|
||||
}
|
||||
|
||||
if (e.UserName != "ValidUser")
|
||||
{
|
||||
e.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword;
|
||||
}
|
||||
if (e.UserName != "ValidUser")
|
||||
{
|
||||
e.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword;
|
||||
}
|
||||
|
||||
if (e.Password != "SecretPassword")
|
||||
{
|
||||
e.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword;
|
||||
}
|
||||
if (e.Password != "SecretPassword")
|
||||
{
|
||||
e.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword;
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
|
||||
await mqttServer.StartAsync();
|
||||
await mqttServer.StartAsync();
|
||||
|
||||
Console.WriteLine("Press Enter to exit.");
|
||||
Console.ReadLine();
|
||||
Console.WriteLine("Press Enter to exit.");
|
||||
Console.ReadLine();
|
||||
|
||||
await mqttServer.StopAsync();
|
||||
}
|
||||
await mqttServer.StopAsync();
|
||||
}
|
||||
|
||||
static async Task<MqttServer> StartMqttServer()
|
||||
@@ -171,7 +162,7 @@ public static class Server_Simple_Samples
|
||||
return server;
|
||||
}
|
||||
|
||||
class ConsoleLogger : IMqttNetLogger
|
||||
sealed class ConsoleLogger : IMqttNetLogger
|
||||
{
|
||||
readonly object _consoleSyncRoot = new();
|
||||
|
||||
@@ -201,7 +192,7 @@ public static class Server_Simple_Samples
|
||||
|
||||
if (parameters?.Length > 0)
|
||||
{
|
||||
message = string.Format(message, parameters);
|
||||
message = string.Format(CultureInfo.InvariantCulture, message, parameters);
|
||||
}
|
||||
|
||||
lock (_consoleSyncRoot)
|
||||
|
||||
@@ -31,16 +31,14 @@ public static class Server_TLS_Samples
|
||||
|
||||
var mqttServerOptions = new MqttServerOptionsBuilder().WithEncryptionCertificate(certificate).WithEncryptedEndpoint().Build();
|
||||
|
||||
using (var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions))
|
||||
{
|
||||
await mqttServer.StartAsync();
|
||||
using var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions);
|
||||
await mqttServer.StartAsync();
|
||||
|
||||
Console.WriteLine("Press Enter to exit.");
|
||||
Console.ReadLine();
|
||||
Console.WriteLine("Press Enter to exit.");
|
||||
Console.ReadLine();
|
||||
|
||||
// Stop and dispose the MQTT server if it is no longer needed!
|
||||
await mqttServer.StopAsync();
|
||||
}
|
||||
// Stop and dispose the MQTT server if it is no longer needed!
|
||||
await mqttServer.StopAsync();
|
||||
}
|
||||
|
||||
static X509Certificate2 CreateSelfSignedCertificate(string oid)
|
||||
@@ -50,26 +48,22 @@ public static class Server_TLS_Samples
|
||||
sanBuilder.AddIpAddress(IPAddress.IPv6Loopback);
|
||||
sanBuilder.AddDnsName("localhost");
|
||||
|
||||
using (var rsa = RSA.Create())
|
||||
{
|
||||
var certRequest = new CertificateRequest("CN=localhost", rsa, HashAlgorithmName.SHA512, RSASignaturePadding.Pkcs1);
|
||||
using var rsa = RSA.Create();
|
||||
var certRequest = new CertificateRequest("CN=localhost", rsa, HashAlgorithmName.SHA512, RSASignaturePadding.Pkcs1);
|
||||
|
||||
certRequest.CertificateExtensions.Add(
|
||||
new X509KeyUsageExtension(X509KeyUsageFlags.DataEncipherment | X509KeyUsageFlags.KeyEncipherment | X509KeyUsageFlags.DigitalSignature, false));
|
||||
certRequest.CertificateExtensions.Add(
|
||||
new X509KeyUsageExtension(X509KeyUsageFlags.DataEncipherment | X509KeyUsageFlags.KeyEncipherment | X509KeyUsageFlags.DigitalSignature, false));
|
||||
|
||||
certRequest.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(new OidCollection { new(oid) }, false));
|
||||
certRequest.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(new OidCollection { new(oid) }, false));
|
||||
|
||||
certRequest.CertificateExtensions.Add(sanBuilder.Build());
|
||||
certRequest.CertificateExtensions.Add(sanBuilder.Build());
|
||||
|
||||
using (var certificate = certRequest.CreateSelfSigned(DateTimeOffset.Now.AddMinutes(-10), DateTimeOffset.Now.AddMinutes(10)))
|
||||
{
|
||||
var pfxCertificate = new X509Certificate2(
|
||||
certificate.Export(X509ContentType.Pfx),
|
||||
(string)null!,
|
||||
X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable);
|
||||
using var certificate = certRequest.CreateSelfSigned(DateTimeOffset.Now.AddMinutes(-10), DateTimeOffset.Now.AddMinutes(10));
|
||||
var pfxCertificate = new X509Certificate2(
|
||||
certificate.Export(X509ContentType.Pfx),
|
||||
(string)null!,
|
||||
X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable);
|
||||
|
||||
return pfxCertificate;
|
||||
}
|
||||
}
|
||||
return pfxCertificate;
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,10 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<IsPackable>false</IsPackable>
|
||||
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
||||
<EnableNETAnalyzers>false</EnableNETAnalyzers>
|
||||
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
|
||||
<NoWarn>1591;NETSDK1138;NU1803;NU1901;NU1902</NoWarn>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<NuGetAuditMode>all</NuGetAuditMode>
|
||||
<NuGetAudit>true</NuGetAudit>
|
||||
<NuGetAuditLevel>low</NuGetAuditLevel>
|
||||
<AnalysisLevel>latest-Recommended</AnalysisLevel>
|
||||
<NoWarn>$(NoWarn);CA1707</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -4,20 +4,22 @@
|
||||
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
|
||||
namespace MQTTnet.AspTestApp.Pages
|
||||
namespace MQTTnet.AspTestApp.Pages;
|
||||
|
||||
public partial class IndexModel : PageModel
|
||||
{
|
||||
public class IndexModel : PageModel
|
||||
readonly ILogger<IndexModel> _logger;
|
||||
|
||||
public IndexModel(ILogger<IndexModel> logger)
|
||||
{
|
||||
private readonly ILogger<IndexModel> _logger;
|
||||
|
||||
public IndexModel(ILogger<IndexModel> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void OnGet()
|
||||
{
|
||||
|
||||
}
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void OnGet()
|
||||
{
|
||||
LogOnGet();
|
||||
}
|
||||
|
||||
[LoggerMessage(Level = LogLevel.Information, Message = "OnGet")]
|
||||
private partial void LogOnGet();
|
||||
}
|
||||
@@ -33,10 +33,8 @@ public static class ApplicationBuilderExtensions
|
||||
}
|
||||
|
||||
var adapter = app.ApplicationServices.GetRequiredService<MqttWebSocketServerAdapter>();
|
||||
using (var webSocket = await context.WebSockets.AcceptWebSocketAsync(subProtocol).ConfigureAwait(false))
|
||||
{
|
||||
await adapter.RunWebSocketConnectionAsync(webSocket, context);
|
||||
}
|
||||
using var webSocket = await context.WebSockets.AcceptWebSocketAsync(subProtocol).ConfigureAwait(false);
|
||||
await adapter.RunWebSocketConnectionAsync(webSocket, context);
|
||||
});
|
||||
|
||||
return app;
|
||||
|
||||
@@ -4,13 +4,12 @@
|
||||
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
|
||||
namespace MQTTnet.AspNetCore
|
||||
namespace MQTTnet.AspNetCore;
|
||||
|
||||
public static class ConnectionBuilderExtensions
|
||||
{
|
||||
public static class ConnectionBuilderExtensions
|
||||
public static IConnectionBuilder UseMqtt(this IConnectionBuilder builder)
|
||||
{
|
||||
public static IConnectionBuilder UseMqtt(this IConnectionBuilder builder)
|
||||
{
|
||||
return builder.UseConnectionHandler<MqttConnectionHandler>();
|
||||
}
|
||||
return builder.UseConnectionHandler<MqttConnectionHandler>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,22 +6,17 @@ using System;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
|
||||
namespace MQTTnet.AspNetCore
|
||||
namespace MQTTnet.AspNetCore;
|
||||
|
||||
public static class EndpointRouterExtensions
|
||||
{
|
||||
public static class EndpointRouterExtensions
|
||||
public static void MapMqtt(this IEndpointRouteBuilder endpoints, string pattern)
|
||||
{
|
||||
public static void MapMqtt(this IEndpointRouteBuilder endpoints, string pattern)
|
||||
ArgumentNullException.ThrowIfNull(endpoints);
|
||||
|
||||
endpoints.MapConnectionHandler<MqttConnectionHandler>(pattern, options =>
|
||||
{
|
||||
if (endpoints == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(endpoints));
|
||||
}
|
||||
|
||||
endpoints.MapConnectionHandler<MqttConnectionHandler>(pattern, options =>
|
||||
{
|
||||
options.WebSockets.SubProtocolSelector = MqttSubProtocolSelector.SelectSubProtocol;
|
||||
});
|
||||
}
|
||||
options.WebSockets.SubProtocolSelector = MqttSubProtocolSelector.SelectSubProtocol;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,60 +1,23 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<AssemblyName>MQTTnet.AspNetCore</AssemblyName>
|
||||
<RootNamespace>MQTTnet.AspNetCore</RootNamespace>
|
||||
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
||||
<Company>The contributors of MQTTnet</Company>
|
||||
<Product>MQTTnet</Product>
|
||||
<IsAotCompatible>true</IsAotCompatible>
|
||||
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<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>Copyright (c) .NET Foundation and Contributors</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>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<PackageReleaseNotes>For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).</PackageReleaseNotes>
|
||||
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<NoWarn>1591;NETSDK1138;NU1803;NU1901;NU1902</NoWarn>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<NuGetAuditMode>all</NuGetAuditMode>
|
||||
<NuGetAudit>true</NuGetAudit>
|
||||
<NuGetAuditLevel>low</NuGetAuditLevel>
|
||||
<NuGetAuditLevel>low</NuGetAuditLevel>
|
||||
<AnalysisLevel>latest-Recommended</AnalysisLevel>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="..\..\Images\nuget.png">
|
||||
<Pack>True</Pack>
|
||||
<PackagePath>\</PackagePath>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All"/>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App"/>
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release'">
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MQTTnet.Server\MQTTnet.Server.csproj"/>
|
||||
<ProjectReference Include="..\MQTTnet\MQTTnet.csproj"/>
|
||||
|
||||
@@ -7,28 +7,27 @@ using MQTTnet.Adapter;
|
||||
using MQTTnet.Diagnostics.Logger;
|
||||
using MQTTnet.Formatter;
|
||||
|
||||
namespace MQTTnet.AspNetCore
|
||||
namespace MQTTnet.AspNetCore;
|
||||
|
||||
public sealed class MqttClientConnectionContextFactory : IMqttClientAdapterFactory
|
||||
{
|
||||
public sealed class MqttClientConnectionContextFactory : IMqttClientAdapterFactory
|
||||
public IMqttChannelAdapter CreateClientAdapter(MqttClientOptions options, MqttPacketInspector packetInspector, IMqttNetLogger logger)
|
||||
{
|
||||
public IMqttChannelAdapter CreateClientAdapter(MqttClientOptions options, MqttPacketInspector packetInspector, IMqttNetLogger logger)
|
||||
ArgumentNullException.ThrowIfNull(options);
|
||||
|
||||
switch (options.ChannelOptions)
|
||||
{
|
||||
if (options == null) throw new ArgumentNullException(nameof(options));
|
||||
|
||||
switch (options.ChannelOptions)
|
||||
case MqttClientTcpOptions tcpOptions:
|
||||
{
|
||||
case MqttClientTcpOptions tcpOptions:
|
||||
{
|
||||
var tcpConnection = new SocketConnection(tcpOptions.RemoteEndpoint);
|
||||
var tcpConnection = new SocketConnection(tcpOptions.RemoteEndpoint);
|
||||
|
||||
var formatter = new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(4096, 65535));
|
||||
return new MqttConnectionContext(formatter, tcpConnection);
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
var formatter = new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(4096, 65535));
|
||||
return new MqttConnectionContext(formatter, tcpConnection);
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -61,27 +61,6 @@ public sealed class MqttConnectionContext : IMqttChannelAdapter
|
||||
}
|
||||
}
|
||||
|
||||
public string Endpoint
|
||||
{
|
||||
get
|
||||
{
|
||||
// mqtt over tcp
|
||||
if (_connection.RemoteEndPoint != null)
|
||||
{
|
||||
return _connection.RemoteEndPoint.ToString();
|
||||
}
|
||||
|
||||
// mqtt over websocket
|
||||
var httpFeature = _connection.Features.Get<IHttpConnectionFeature>();
|
||||
if (httpFeature?.RemoteIpAddress != null)
|
||||
{
|
||||
return new IPEndPoint(httpFeature.RemoteIpAddress, httpFeature.RemotePort).ToString();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsSecureConnection
|
||||
{
|
||||
get
|
||||
@@ -104,8 +83,50 @@ public sealed class MqttConnectionContext : IMqttChannelAdapter
|
||||
}
|
||||
}
|
||||
|
||||
public EndPoint LocalEndPoint
|
||||
{
|
||||
get
|
||||
{
|
||||
// mqtt over tcp
|
||||
if (_connection.LocalEndPoint != null)
|
||||
{
|
||||
return _connection.LocalEndPoint;
|
||||
}
|
||||
|
||||
// mqtt over websocket
|
||||
var httpFeature = _connection.Features.Get<IHttpConnectionFeature>();
|
||||
if (httpFeature?.LocalIpAddress != null)
|
||||
{
|
||||
return new IPEndPoint(httpFeature.LocalIpAddress, httpFeature.LocalPort);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public MqttPacketFormatterAdapter PacketFormatterAdapter { get; }
|
||||
|
||||
public EndPoint RemoteEndPoint
|
||||
{
|
||||
get
|
||||
{
|
||||
// mqtt over tcp
|
||||
if (_connection.RemoteEndPoint != null)
|
||||
{
|
||||
return _connection.RemoteEndPoint;
|
||||
}
|
||||
|
||||
// mqtt over websocket
|
||||
var httpFeature = _connection.Features.Get<IHttpConnectionFeature>();
|
||||
if (httpFeature?.RemoteIpAddress != null)
|
||||
{
|
||||
return new IPEndPoint(httpFeature.RemoteIpAddress, httpFeature.RemotePort);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ConnectAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (_connection is SocketConnection tcp && !tcp.IsConnected)
|
||||
@@ -232,8 +253,8 @@ public sealed class MqttConnectionContext : IMqttChannelAdapter
|
||||
var span = output.GetSpan(buffer.Length);
|
||||
|
||||
buffer.Packet.AsSpan().CopyTo(span);
|
||||
int offset = buffer.Packet.Count;
|
||||
buffer.Payload.CopyTo(destination: span.Slice(offset));
|
||||
var offset = buffer.Packet.Count;
|
||||
buffer.Payload.CopyTo(span.Slice(offset));
|
||||
output.Advance(buffer.Length);
|
||||
}
|
||||
}
|
||||
@@ -33,13 +33,11 @@ public sealed class MqttConnectionHandler : ConnectionHandler, IMqttServerAdapte
|
||||
}
|
||||
|
||||
var formatter = new MqttPacketFormatterAdapter(new MqttBufferWriter(_serverOptions.WriterBufferSize, _serverOptions.WriterBufferSizeMax));
|
||||
using (var adapter = new MqttConnectionContext(formatter, connection))
|
||||
using var adapter = new MqttConnectionContext(formatter, connection);
|
||||
var clientHandler = ClientHandler;
|
||||
if (clientHandler != null)
|
||||
{
|
||||
var clientHandler = ClientHandler;
|
||||
if (clientHandler != null)
|
||||
{
|
||||
await clientHandler(adapter).ConfigureAwait(false);
|
||||
}
|
||||
await clientHandler(adapter).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,10 +13,7 @@ public static class MqttSubProtocolSelector
|
||||
{
|
||||
public static string SelectSubProtocol(HttpRequest request)
|
||||
{
|
||||
if (request == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(request));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
|
||||
string subProtocol = null;
|
||||
if (request.Headers.TryGetValue("Sec-WebSocket-Protocol", out var requestedSubProtocolValues))
|
||||
@@ -29,12 +26,9 @@ public static class MqttSubProtocolSelector
|
||||
|
||||
public static string SelectSubProtocol(IList<string> requestedSubProtocolValues)
|
||||
{
|
||||
if (requestedSubProtocolValues == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(requestedSubProtocolValues));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(requestedSubProtocolValues);
|
||||
|
||||
// Order the protocols to also match "mqtt", "mqttv-3.1", "mqttv-3.11" etc.
|
||||
return requestedSubProtocolValues.OrderByDescending(p => p.Length).FirstOrDefault(p => p.ToLower().StartsWith("mqtt"));
|
||||
return requestedSubProtocolValues.OrderByDescending(p => p.Length).FirstOrDefault(p => p.ToLowerInvariant().StartsWith("mqtt", StringComparison.InvariantCulture));
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.WebSockets;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
@@ -26,12 +27,10 @@ public sealed class MqttWebSocketServerAdapter : IMqttServerAdapter
|
||||
|
||||
public async Task RunWebSocketConnectionAsync(WebSocket webSocket, HttpContext httpContext)
|
||||
{
|
||||
if (webSocket == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(webSocket));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(webSocket);
|
||||
|
||||
var endpoint = $"{httpContext.Connection.RemoteIpAddress}:{httpContext.Connection.RemotePort}";
|
||||
var remoteAddress = httpContext.Connection.RemoteIpAddress;
|
||||
var remoteEndPoint = remoteAddress == null ? null : new IPEndPoint(remoteAddress, httpContext.Connection.RemotePort);
|
||||
|
||||
var clientCertificate = await httpContext.Connection.GetClientCertificateAsync().ConfigureAwait(false);
|
||||
try
|
||||
@@ -42,12 +41,10 @@ public sealed class MqttWebSocketServerAdapter : IMqttServerAdapter
|
||||
if (clientHandler != null)
|
||||
{
|
||||
var formatter = new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535));
|
||||
var channel = new MqttWebSocketChannel(webSocket, endpoint, isSecureConnection, clientCertificate);
|
||||
var channel = new MqttWebSocketChannel(webSocket, remoteEndPoint, isSecureConnection, clientCertificate);
|
||||
|
||||
using (var channelAdapter = new MqttChannelAdapter(channel, formatter, _logger))
|
||||
{
|
||||
await clientHandler(channelAdapter).ConfigureAwait(false);
|
||||
}
|
||||
using var channelAdapter = new MqttChannelAdapter(channel, formatter, _logger);
|
||||
await clientHandler(channelAdapter).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
finally
|
||||
|
||||
@@ -22,10 +22,7 @@ public static class ReaderExtensions
|
||||
out SequencePosition observed,
|
||||
out int bytesRead)
|
||||
{
|
||||
if (formatter == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(formatter));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(formatter);
|
||||
|
||||
packet = null;
|
||||
consumed = input.Start;
|
||||
|
||||
@@ -16,15 +16,8 @@ public static class ServiceCollectionExtensions
|
||||
{
|
||||
public static IServiceCollection AddHostedMqttServer(this IServiceCollection services, MqttServerOptions options)
|
||||
{
|
||||
if (services == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(services));
|
||||
}
|
||||
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(services);
|
||||
ArgumentNullException.ThrowIfNull(options);
|
||||
|
||||
services.AddSingleton(options);
|
||||
services.AddHostedMqttServer();
|
||||
@@ -34,10 +27,7 @@ public static class ServiceCollectionExtensions
|
||||
|
||||
public static IServiceCollection AddHostedMqttServer(this IServiceCollection services, Action<MqttServerOptionsBuilder> configure)
|
||||
{
|
||||
if (services == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(services));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(services);
|
||||
|
||||
var serverOptionsBuilder = new MqttServerOptionsBuilder();
|
||||
|
||||
@@ -61,10 +51,7 @@ public static class ServiceCollectionExtensions
|
||||
|
||||
public static IServiceCollection AddHostedMqttServerWithServices(this IServiceCollection services, Action<AspNetMqttServerOptionsBuilder> configure)
|
||||
{
|
||||
if (services == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(services));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(services);
|
||||
|
||||
services.AddSingleton(
|
||||
s =>
|
||||
@@ -89,20 +76,14 @@ public static class ServiceCollectionExtensions
|
||||
|
||||
public static void AddMqttLogger(this IServiceCollection services, IMqttNetLogger logger)
|
||||
{
|
||||
if (services == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(services));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(services);
|
||||
|
||||
services.AddSingleton(logger);
|
||||
}
|
||||
|
||||
public static IServiceCollection AddMqttServer(this IServiceCollection serviceCollection, Action<MqttServerOptionsBuilder> configure = null)
|
||||
{
|
||||
if (serviceCollection is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(serviceCollection));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(serviceCollection);
|
||||
|
||||
serviceCollection.AddMqttConnectionHandler();
|
||||
serviceCollection.AddHostedMqttServer(configure);
|
||||
|
||||
@@ -12,34 +12,28 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace MQTTnet.AspNetCore;
|
||||
|
||||
public class SocketAwaitable : ICriticalNotifyCompletion
|
||||
public class SocketAwaitable(PipeScheduler ioScheduler) : ICriticalNotifyCompletion
|
||||
{
|
||||
static readonly Action _callbackCompleted = () =>
|
||||
static readonly Action CallbackCompleted = () =>
|
||||
{
|
||||
};
|
||||
|
||||
readonly PipeScheduler _ioScheduler;
|
||||
int _bytesTransferred;
|
||||
|
||||
Action _callback;
|
||||
SocketError _error;
|
||||
|
||||
public SocketAwaitable(PipeScheduler ioScheduler)
|
||||
{
|
||||
_ioScheduler = ioScheduler;
|
||||
}
|
||||
|
||||
public bool IsCompleted => ReferenceEquals(_callback, _callbackCompleted);
|
||||
public bool IsCompleted => ReferenceEquals(_callback, CallbackCompleted);
|
||||
|
||||
public void Complete(int bytesTransferred, SocketError socketError)
|
||||
{
|
||||
_error = socketError;
|
||||
_bytesTransferred = bytesTransferred;
|
||||
var continuation = Interlocked.Exchange(ref _callback, _callbackCompleted);
|
||||
var continuation = Interlocked.Exchange(ref _callback, CallbackCompleted);
|
||||
|
||||
if (continuation != null)
|
||||
{
|
||||
_ioScheduler.Schedule(state => ((Action)state)(), continuation);
|
||||
ioScheduler.Schedule(state => ((Action)state)(), continuation);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +44,7 @@ public class SocketAwaitable : ICriticalNotifyCompletion
|
||||
|
||||
public int GetResult()
|
||||
{
|
||||
Debug.Assert(ReferenceEquals(_callback, _callbackCompleted));
|
||||
Debug.Assert(ReferenceEquals(_callback, CallbackCompleted));
|
||||
|
||||
_callback = null;
|
||||
|
||||
@@ -64,7 +58,7 @@ public class SocketAwaitable : ICriticalNotifyCompletion
|
||||
|
||||
public void OnCompleted(Action continuation)
|
||||
{
|
||||
if (ReferenceEquals(_callback, _callbackCompleted) || ReferenceEquals(Interlocked.CompareExchange(ref _callback, continuation, null), _callbackCompleted))
|
||||
if (ReferenceEquals(_callback, CallbackCompleted) || ReferenceEquals(Interlocked.CompareExchange(ref _callback, continuation, null), CallbackCompleted))
|
||||
{
|
||||
Task.Run(continuation);
|
||||
}
|
||||
|
||||
@@ -18,11 +18,11 @@ namespace MQTTnet.AspNetCore;
|
||||
public sealed class SocketConnection : ConnectionContext
|
||||
{
|
||||
readonly EndPoint _endPoint;
|
||||
|
||||
volatile bool _aborted;
|
||||
IDuplexPipe _application;
|
||||
SocketReceiver _receiver;
|
||||
SocketSender _sender;
|
||||
|
||||
Socket _socket;
|
||||
|
||||
public SocketConnection(EndPoint endPoint)
|
||||
@@ -40,10 +40,13 @@ public sealed class SocketConnection : ConnectionContext
|
||||
}
|
||||
|
||||
public override string ConnectionId { get; set; }
|
||||
|
||||
public override IFeatureCollection Features { get; }
|
||||
|
||||
public bool IsConnected { get; private set; }
|
||||
|
||||
public override IDictionary<object, object> Items { get; set; }
|
||||
|
||||
public override IDuplexPipe Transport { get; set; }
|
||||
|
||||
public override ValueTask DisposeAsync()
|
||||
@@ -78,7 +81,7 @@ public sealed class SocketConnection : ConnectionContext
|
||||
IsConnected = true;
|
||||
}
|
||||
|
||||
Exception ConnectionAborted()
|
||||
static MqttCommunicationException ConnectionAborted()
|
||||
{
|
||||
return new MqttCommunicationException("Connection Aborted");
|
||||
}
|
||||
@@ -95,8 +98,7 @@ public sealed class SocketConnection : ConnectionContext
|
||||
{
|
||||
error = new MqttCommunicationException(ex);
|
||||
}
|
||||
catch (SocketException ex) when (ex.SocketErrorCode == SocketError.OperationAborted || ex.SocketErrorCode == SocketError.ConnectionAborted ||
|
||||
ex.SocketErrorCode == SocketError.Interrupted || ex.SocketErrorCode == SocketError.InvalidArgument)
|
||||
catch (SocketException ex) when (ex.SocketErrorCode is SocketError.OperationAborted or SocketError.ConnectionAborted or SocketError.Interrupted or SocketError.InvalidArgument)
|
||||
{
|
||||
if (!_aborted)
|
||||
{
|
||||
@@ -123,10 +125,10 @@ public sealed class SocketConnection : ConnectionContext
|
||||
{
|
||||
if (_aborted)
|
||||
{
|
||||
error = error ?? ConnectionAborted();
|
||||
error ??= ConnectionAborted();
|
||||
}
|
||||
|
||||
_application.Output.Complete(error);
|
||||
await _application.Output.CompleteAsync(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -216,12 +218,16 @@ public sealed class SocketConnection : ConnectionContext
|
||||
|
||||
var flushTask = _application.Output.FlushAsync();
|
||||
|
||||
FlushResult result;
|
||||
if (!flushTask.IsCompleted)
|
||||
{
|
||||
await flushTask;
|
||||
result = await flushTask;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = flushTask.GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
var result = flushTask.GetAwaiter().GetResult();
|
||||
if (result.IsCompleted)
|
||||
{
|
||||
// Pipe consumer is shut down, do we stop writing
|
||||
|
||||
@@ -8,7 +8,7 @@ using System.Net.Sockets;
|
||||
|
||||
namespace MQTTnet.AspNetCore;
|
||||
|
||||
public sealed class SocketReceiver
|
||||
public sealed class SocketReceiver : IDisposable
|
||||
{
|
||||
readonly SocketAwaitable _awaitable;
|
||||
readonly SocketAsyncEventArgs _eventArgs = new();
|
||||
@@ -33,4 +33,9 @@ public sealed class SocketReceiver
|
||||
|
||||
return _awaitable;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_eventArgs.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ using System.Runtime.InteropServices;
|
||||
|
||||
namespace MQTTnet.AspNetCore;
|
||||
|
||||
public sealed class SocketSender
|
||||
public sealed class SocketSender : IDisposable
|
||||
{
|
||||
readonly SocketAwaitable _awaitable;
|
||||
readonly SocketAsyncEventArgs _eventArgs = new();
|
||||
@@ -28,6 +28,11 @@ public sealed class SocketSender
|
||||
_eventArgs.Completed += (_, e) => ((SocketAwaitable)e.UserToken).Complete(e.BytesTransferred, e.SocketError);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_eventArgs.Dispose();
|
||||
}
|
||||
|
||||
public SocketAwaitable SendAsync(in ReadOnlySequence<byte> buffers)
|
||||
{
|
||||
if (buffers.IsSingleSegment)
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
[assembly: DoNotParallelize]
|
||||
@@ -9,57 +9,54 @@ using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using MQTTnet.Internal;
|
||||
|
||||
namespace MQTTnet.Benchmarks
|
||||
namespace MQTTnet.Benchmarks;
|
||||
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[MemoryDiagnoser]
|
||||
public class AsyncLockBenchmark : BaseBenchmark
|
||||
{
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[MemoryDiagnoser]
|
||||
public class AsyncLockBenchmark : BaseBenchmark
|
||||
[Benchmark]
|
||||
public async Task Synchronize_100_Tasks()
|
||||
{
|
||||
[Benchmark]
|
||||
public async Task Synchronize_100_Tasks()
|
||||
const int tasksCount = 100;
|
||||
|
||||
var tasks = new Task[tasksCount];
|
||||
var asyncLock = new AsyncLock();
|
||||
var globalI = 0;
|
||||
|
||||
for (var i = 0; i < tasksCount; i++)
|
||||
{
|
||||
const int tasksCount = 100;
|
||||
|
||||
var tasks = new Task[tasksCount];
|
||||
var asyncLock = new AsyncLock();
|
||||
var globalI = 0;
|
||||
|
||||
for (var i = 0; i < tasksCount; i++)
|
||||
{
|
||||
tasks[i] = Task.Run(
|
||||
async () =>
|
||||
{
|
||||
using (await asyncLock.EnterAsync().ConfigureAwait(false))
|
||||
{
|
||||
var localI = globalI;
|
||||
await Task.Delay(5).ConfigureAwait(false); // Increase the chance for wrong data.
|
||||
localI++;
|
||||
globalI = localI;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
|
||||
if (globalI != tasksCount)
|
||||
{
|
||||
throw new Exception($"Code is broken ({globalI})!");
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public async Task Wait_100_000_Times()
|
||||
{
|
||||
var asyncLock = new AsyncLock();
|
||||
|
||||
using (var cancellationToken = new CancellationTokenSource())
|
||||
{
|
||||
for (var i = 0; i < 100000; i++)
|
||||
tasks[i] = Task.Run(
|
||||
async () =>
|
||||
{
|
||||
using (await asyncLock.EnterAsync(cancellationToken.Token).ConfigureAwait(false))
|
||||
using (await asyncLock.EnterAsync().ConfigureAwait(false))
|
||||
{
|
||||
var localI = globalI;
|
||||
await Task.Delay(5).ConfigureAwait(false); // Increase the chance for wrong data.
|
||||
localI++;
|
||||
globalI = localI;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
|
||||
if (globalI != tasksCount)
|
||||
{
|
||||
throw new InvalidOperationException($"Code is broken ({globalI})!");
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public async Task Wait_100_000_Times()
|
||||
{
|
||||
var asyncLock = new AsyncLock();
|
||||
|
||||
using var cancellationToken = new CancellationTokenSource();
|
||||
for (var i = 0; i < 100000; i++)
|
||||
{
|
||||
using (await asyncLock.EnterAsync(cancellationToken.Token).ConfigureAwait(false))
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,8 @@
|
||||
// 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.Benchmarks
|
||||
namespace MQTTnet.Benchmarks;
|
||||
|
||||
public abstract class BaseBenchmark
|
||||
{
|
||||
public abstract class BaseBenchmark
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -12,79 +12,84 @@ using MQTTnet.Formatter;
|
||||
using MQTTnet.Packets;
|
||||
using MQTTnet.Tests.Mockups;
|
||||
|
||||
namespace MQTTnet.Benchmarks
|
||||
namespace MQTTnet.Benchmarks;
|
||||
|
||||
[MemoryDiagnoser]
|
||||
public sealed class ChannelAdapterBenchmark : BaseBenchmark, IDisposable
|
||||
{
|
||||
[MemoryDiagnoser]
|
||||
public sealed class ChannelAdapterBenchmark : BaseBenchmark
|
||||
MqttChannelAdapter _channelAdapter;
|
||||
int _iterations;
|
||||
MqttPublishPacket _packet;
|
||||
MemoryStream _stream;
|
||||
|
||||
[Benchmark]
|
||||
public void Receive_10000_Messages()
|
||||
{
|
||||
MqttChannelAdapter _channelAdapter;
|
||||
int _iterations;
|
||||
MqttPublishPacket _packet;
|
||||
MemoryStream _stream;
|
||||
_stream.Position = 0;
|
||||
|
||||
[Benchmark]
|
||||
public void Receive_10000_Messages()
|
||||
for (var i = 0; i < 10000; i++)
|
||||
{
|
||||
_stream.Position = 0;
|
||||
|
||||
for (var i = 0; i < 10000; i++)
|
||||
{
|
||||
_channelAdapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
_stream.Position = 0;
|
||||
_channelAdapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Send_10000_Messages()
|
||||
_stream.Position = 0;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Send_10000_Messages()
|
||||
{
|
||||
_stream.Position = 0;
|
||||
|
||||
for (var i = 0; i < 10000; i++)
|
||||
{
|
||||
_stream.Position = 0;
|
||||
|
||||
for (var i = 0; i < 10000; i++)
|
||||
{
|
||||
_channelAdapter.SendPacketAsync(_packet, CancellationToken.None).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
_stream.Position = 0;
|
||||
_channelAdapter.SendPacketAsync(_packet, CancellationToken.None).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
_stream.Position = 0;
|
||||
}
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
_packet = new MqttPublishPacket
|
||||
{
|
||||
_packet = new MqttPublishPacket
|
||||
{
|
||||
Topic = "A"
|
||||
};
|
||||
Topic = "A"
|
||||
};
|
||||
|
||||
var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535));
|
||||
var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535));
|
||||
|
||||
var serializedPacket = Join(serializer.Encode(_packet).Join());
|
||||
var serializedPacket = Join(serializer.Encode(_packet).Join());
|
||||
|
||||
_iterations = 10000;
|
||||
_iterations = 10000;
|
||||
|
||||
_stream = new MemoryStream(_iterations * serializedPacket.Length);
|
||||
_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 MemoryMqttChannel(_stream);
|
||||
|
||||
_channelAdapter = new MqttChannelAdapter(channel, serializer, new MqttNetEventLogger());
|
||||
for (var i = 0; i < _iterations; i++)
|
||||
{
|
||||
_stream.Write(serializedPacket, 0, serializedPacket.Length);
|
||||
}
|
||||
|
||||
static byte[] Join(params ArraySegment<byte>[] chunks)
|
||||
{
|
||||
var buffer = new MemoryStream();
|
||||
foreach (var chunk in chunks)
|
||||
{
|
||||
buffer.Write(chunk.Array, chunk.Offset, chunk.Count);
|
||||
}
|
||||
_stream.Position = 0;
|
||||
|
||||
return buffer.ToArray();
|
||||
var channel = new MemoryMqttChannel(_stream);
|
||||
|
||||
_channelAdapter = new MqttChannelAdapter(channel, serializer, new MqttNetEventLogger());
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_channelAdapter?.Dispose();
|
||||
_stream?.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -2,90 +2,90 @@
|
||||
// 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.Diagnostics;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using MQTTnet.Diagnostics.Logger;
|
||||
|
||||
namespace MQTTnet.Benchmarks
|
||||
namespace MQTTnet.Benchmarks;
|
||||
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[RPlotExporter]
|
||||
[MemoryDiagnoser]
|
||||
public class LoggerBenchmark : BaseBenchmark
|
||||
{
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[RPlotExporter]
|
||||
[MemoryDiagnoser]
|
||||
public class LoggerBenchmark : BaseBenchmark
|
||||
MqttNetNullLogger _nullLogger;
|
||||
MqttNetSourceLogger _sourceNullLogger;
|
||||
|
||||
MqttNetEventLogger _eventLogger;
|
||||
MqttNetSourceLogger _sourceEventLogger;
|
||||
|
||||
MqttNetEventLogger _eventLoggerNoListener;
|
||||
MqttNetSourceLogger _sourceEventLoggerNoListener;
|
||||
|
||||
bool _useHandler;
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
MqttNetNullLogger _nullLogger;
|
||||
MqttNetSourceLogger _sourceNullLogger;
|
||||
_nullLogger = new MqttNetNullLogger();
|
||||
_sourceNullLogger = _nullLogger.WithSource("Source");
|
||||
|
||||
MqttNetEventLogger _eventLogger;
|
||||
MqttNetSourceLogger _sourceEventLogger;
|
||||
_eventLogger = new MqttNetEventLogger();
|
||||
_eventLogger.LogMessagePublished += OnLogMessagePublished;
|
||||
_sourceEventLogger = _eventLogger.WithSource("Source");
|
||||
|
||||
MqttNetEventLogger _eventLoggerNoListener;
|
||||
MqttNetSourceLogger _sourceEventLoggerNoListener;
|
||||
_eventLoggerNoListener = new MqttNetEventLogger();
|
||||
_sourceEventLoggerNoListener = _eventLoggerNoListener.WithSource("Source");
|
||||
}
|
||||
|
||||
bool _useHandler;
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
void OnLogMessagePublished(object sender, MqttNetLogMessagePublishedEventArgs eventArgs)
|
||||
{
|
||||
if (_useHandler)
|
||||
{
|
||||
_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");
|
||||
}
|
||||
Debug.WriteLine(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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<OutputType>Exe</OutputType>
|
||||
<DebugType>Full</DebugType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
<EnableNETAnalyzers>false</EnableNETAnalyzers>
|
||||
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
|
||||
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
||||
<NoWarn>1591;NETSDK1138;NU1803;NU1901;NU1902;CS8892</NoWarn>
|
||||
<NoWarn>$(NoWarn);CS8892;CA1707;CA1051;CA1822</NoWarn>
|
||||
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
||||
<NuGetAuditMode>all</NuGetAuditMode>
|
||||
<NuGetAudit>true</NuGetAudit>
|
||||
<NuGetAuditLevel>low</NuGetAuditLevel>
|
||||
<AnalysisLevel>latest-Recommended</AnalysisLevel>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.13.12"/>
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.15.4" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -3,38 +3,38 @@ using BenchmarkDotNet.Jobs;
|
||||
using System;
|
||||
|
||||
|
||||
namespace MQTTnet.Benchmarks
|
||||
namespace MQTTnet.Benchmarks;
|
||||
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[RPlotExporter, RankColumn]
|
||||
[MemoryDiagnoser]
|
||||
public class MemoryCopyBenchmark
|
||||
{
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[RPlotExporter, RankColumn]
|
||||
[MemoryDiagnoser]
|
||||
public class MemoryCopyBenchmark
|
||||
const int MaxLength = 1024 * 8;
|
||||
|
||||
byte[] _source;
|
||||
byte[] _target;
|
||||
|
||||
[Params(64 - 1, 128 - 1, 256 - 1, 512 - 1, 1024 - 1, 2048 - 1, 5096 - 1)]
|
||||
public int Length { get; set; }
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
const int max_length = 1024 * 8;
|
||||
private byte[] source;
|
||||
private byte[] target;
|
||||
|
||||
[Params(64 - 1, 128 - 1, 256 - 1, 512 - 1, 1024 - 1, 2048 - 1, 5096 - 1)]
|
||||
public int Length { get; set; }
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
source = new byte[max_length];
|
||||
target = new byte[max_length];
|
||||
}
|
||||
|
||||
[Benchmark(Baseline = true)]
|
||||
public void Array_Copy()
|
||||
{
|
||||
Array.Copy(source, 0, target, 0, Length);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Memory_Copy()
|
||||
{
|
||||
MQTTnet.Internal.MqttMemoryHelper.Copy(source, 0, target, 0, Length);
|
||||
}
|
||||
|
||||
_source = new byte[MaxLength];
|
||||
_target = new byte[MaxLength];
|
||||
}
|
||||
|
||||
[Benchmark(Baseline = true)]
|
||||
public void Array_Copy()
|
||||
{
|
||||
Array.Copy(_source, 0, _target, 0, Length);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Memory_Copy()
|
||||
{
|
||||
Internal.MqttMemoryHelper.Copy(_source, 0, _target, 0, Length);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,11 +2,6 @@
|
||||
// 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 BenchmarkDotNet.Attributes;
|
||||
using MQTTnet.Packets;
|
||||
using MQTTnet.Server;
|
||||
@@ -14,7 +9,7 @@ using MQTTnet.Server;
|
||||
namespace MQTTnet.Benchmarks;
|
||||
|
||||
[MemoryDiagnoser]
|
||||
public class MessageDeliveryBenchmark : BaseBenchmark
|
||||
public sealed class MessageDeliveryBenchmark : BaseBenchmark, IDisposable
|
||||
{
|
||||
List<string> _allSubscribedTopics; // Keep track of the subset of topics that are subscribed
|
||||
CancellationTokenSource _cancellationTokenSource;
|
||||
@@ -23,22 +18,19 @@ public class MessageDeliveryBenchmark : BaseBenchmark
|
||||
int _messagesExpectedCount;
|
||||
int _messagesReceivedCount;
|
||||
Dictionary<string, IMqttClient> _mqttPublisherClientsByPublisherName;
|
||||
|
||||
MqttServer _mqttServer;
|
||||
List<IMqttClient> _mqttSubscriberClients;
|
||||
|
||||
[Params(1000, 10000)] public int _numPublishers;
|
||||
|
||||
[Params(5, 10, 20, 50)] public int _numSubscribedTopicsPerSubscriber;
|
||||
|
||||
[Params(10)] public int _numSubscribers;
|
||||
|
||||
[Params(1, 5)] public int _numTopicsPerPublisher;
|
||||
Dictionary<string, string> _publisherByTopic;
|
||||
List<MqttApplicationMessage> _topicPublishMessages;
|
||||
|
||||
Dictionary<string, List<string>> _topicsByPublisher;
|
||||
|
||||
[Params(1000, 10000)] public int NumPublishers;
|
||||
|
||||
[Params(5, 10, 20, 50)] public int NumSubscribedTopicsPerSubscriber;
|
||||
|
||||
[Params(10)] public int NumSubscribers;
|
||||
|
||||
[Params(1, 5)] public int NumTopicsPerPublisher;
|
||||
|
||||
[GlobalCleanup]
|
||||
public void Cleanup()
|
||||
{
|
||||
@@ -64,14 +56,11 @@ public class MessageDeliveryBenchmark : BaseBenchmark
|
||||
_mqttServer = null;
|
||||
}
|
||||
|
||||
/// <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;
|
||||
_messagesExpectedCount = _numSubscribedTopicsPerSubscriber * _numSubscribers;
|
||||
|
||||
// Loop for a while and exchange messages
|
||||
|
||||
@@ -82,8 +71,6 @@ public class MessageDeliveryBenchmark : BaseBenchmark
|
||||
// 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)
|
||||
{
|
||||
@@ -101,35 +88,29 @@ public class MessageDeliveryBenchmark : BaseBenchmark
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore all errors.
|
||||
}
|
||||
|
||||
_cancellationTokenSource.Dispose();
|
||||
|
||||
if (_messagesReceivedCount < _messagesExpectedCount)
|
||||
{
|
||||
throw new Exception(string.Format("Messages Received Count mismatch, expected {0}, received {1}", _messagesExpectedCount, _messagesReceivedCount));
|
||||
throw new InvalidOperationException($"Messages Received Count mismatch, expected {_messagesExpectedCount}, received {_messagesReceivedCount}");
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_cancellationTokenSource?.Dispose();
|
||||
_mqttServer?.Dispose();
|
||||
}
|
||||
|
||||
[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);
|
||||
}
|
||||
TopicGenerator.Generate(_numPublishers, _numTopicsPerPublisher, out _topicsByPublisher, out _, out _);
|
||||
|
||||
// Create server
|
||||
var serverFactory = new MqttServerFactory();
|
||||
@@ -139,7 +120,7 @@ public class MessageDeliveryBenchmark : BaseBenchmark
|
||||
_mqttServer.StartAsync().GetAwaiter().GetResult();
|
||||
|
||||
// Create publisher clients
|
||||
_mqttPublisherClientsByPublisherName = new Dictionary<string, IMqttClient>();
|
||||
_mqttPublisherClientsByPublisherName = [];
|
||||
foreach (var pt in _topicsByPublisher)
|
||||
{
|
||||
var publisherName = pt.Key;
|
||||
@@ -151,13 +132,13 @@ public class MessageDeliveryBenchmark : BaseBenchmark
|
||||
|
||||
// Create subscriber clients
|
||||
_mqttSubscriberClients = new List<IMqttClient>();
|
||||
for (var i = 0; i < NumSubscribers; i++)
|
||||
for (var i = 0; i < _numSubscribers; i++)
|
||||
{
|
||||
var mqttSubscriberClient = clientFactory.CreateMqttClient();
|
||||
_mqttSubscriberClients.Add(mqttSubscriberClient);
|
||||
|
||||
var subscriberOptions = new MqttClientOptionsBuilder().WithTcpServer("localhost").WithClientId("subscriber" + i).Build();
|
||||
mqttSubscriberClient.ApplicationMessageReceivedAsync += r =>
|
||||
mqttSubscriberClient.ApplicationMessageReceivedAsync += _ =>
|
||||
{
|
||||
// count messages and signal cancellation when expected message count is reached
|
||||
lock (_lockMsgCount)
|
||||
@@ -171,12 +152,13 @@ public class MessageDeliveryBenchmark : BaseBenchmark
|
||||
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
|
||||
mqttSubscriberClient.ConnectAsync(subscriberOptions).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
|
||||
var allTopics = new List<string>();
|
||||
_publisherByTopic = new Dictionary<string, string>();
|
||||
_publisherByTopic = [];
|
||||
foreach (var t in _topicsByPublisher)
|
||||
{
|
||||
foreach (var topic in t.Value)
|
||||
@@ -189,21 +171,18 @@ public class MessageDeliveryBenchmark : BaseBenchmark
|
||||
// Subscribe to NumSubscribedTopics topics spread across all topics
|
||||
_allSubscribedTopics = new List<string>();
|
||||
|
||||
var totalNumTopics = NumPublishers * NumTopicsPerPublisher;
|
||||
var topicIndexStep = totalNumTopics / (NumSubscribedTopicsPerSubscriber * NumSubscribers);
|
||||
if (topicIndexStep * NumSubscribedTopicsPerSubscriber * NumSubscribers != totalNumTopics)
|
||||
var totalNumTopics = _numPublishers * _numTopicsPerPublisher;
|
||||
var topicIndexStep = totalNumTopics / (_numSubscribedTopicsPerSubscriber * _numSubscribers);
|
||||
if (topicIndexStep * _numSubscribedTopicsPerSubscriber * _numSubscribers != totalNumTopics)
|
||||
{
|
||||
throw new 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));
|
||||
throw new InvalidOperationException(
|
||||
$"The total number of topics must be divisible by the number of subscribed topics across all subscribers. Total number of topics: {totalNumTopics}, topic step: {topicIndexStep}");
|
||||
}
|
||||
|
||||
var topicIndex = 0;
|
||||
foreach (var mqttSubscriber in _mqttSubscriberClients)
|
||||
{
|
||||
for (var i = 0; i < NumSubscribedTopicsPerSubscriber; ++i, topicIndex += topicIndexStep)
|
||||
for (var i = 0; i < _numSubscribedTopicsPerSubscriber; ++i, topicIndex += topicIndexStep)
|
||||
{
|
||||
var topic = allTopics[topicIndex];
|
||||
_allSubscribedTopics.Add(topic);
|
||||
|
||||
@@ -9,65 +9,64 @@ using Microsoft.AspNetCore.Hosting;
|
||||
using MQTTnet.AspNetCore;
|
||||
using MQTTnet.Diagnostics.Logger;
|
||||
|
||||
namespace MQTTnet.Benchmarks
|
||||
namespace MQTTnet.Benchmarks;
|
||||
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[MemoryDiagnoser]
|
||||
public class MessageProcessingMqttConnectionContextBenchmark : BaseBenchmark
|
||||
{
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[MemoryDiagnoser]
|
||||
public class MessageProcessingMqttConnectionContextBenchmark : BaseBenchmark
|
||||
IWebHost _host;
|
||||
IMqttClient _mqttClient;
|
||||
MqttApplicationMessage _message;
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
IWebHost _host;
|
||||
IMqttClient _mqttClient;
|
||||
MqttApplicationMessage _message;
|
||||
_host = WebHost.CreateDefaultBuilder()
|
||||
.UseKestrel(o => o.ListenAnyIP(1883, l => l.UseMqtt()))
|
||||
.ConfigureServices(services => {
|
||||
services
|
||||
.AddHostedMqttServer(mqttServerOptions => mqttServerOptions.WithoutDefaultEndpoint())
|
||||
.AddMqttConnectionHandler();
|
||||
})
|
||||
.Configure(app => {
|
||||
app.UseMqttServer(_ => {
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
});
|
||||
})
|
||||
.Build();
|
||||
|
||||
var factory = new MqttClientFactory();
|
||||
_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++)
|
||||
{
|
||||
_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 MqttClientFactory();
|
||||
_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();
|
||||
}
|
||||
_mqttClient.PublishAsync(_message).GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,36 +8,35 @@ using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using MQTTnet.Formatter;
|
||||
|
||||
namespace MQTTnet.Benchmarks
|
||||
namespace MQTTnet.Benchmarks;
|
||||
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[MemoryDiagnoser]
|
||||
public class MqttBufferReaderBenchmark
|
||||
{
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[MemoryDiagnoser]
|
||||
public class MqttBufferReaderBenchmark
|
||||
byte[] _buffer;
|
||||
int _bufferLength;
|
||||
|
||||
[GlobalSetup]
|
||||
public void GlobalSetup()
|
||||
{
|
||||
byte[] _buffer;
|
||||
int _bufferLength;
|
||||
var writer = new MqttBufferWriter(1024, 1024);
|
||||
writer.WriteString("hgfjkdfkjlghfdjghdfljkdfhgdlkjfshgsldkfjghsdflkjghdsflkjhrstiuoghlkfjbhnfbutghjoiöjhklötnbhtroliöuhbjntluiobkjzbhtdrlskbhtruhjkfthgbkftgjhgfiklhotriuöhbjtrsioöbtrsötrhträhtrühjtriüoätrhjtsrölbktrbnhtrulöbionhströloubinströoliubhnsöotrunbtöroisntröointröioujhgötiohjgötorshjnbgtöorihbnjtröoihbjntröobntröoibntrjhötrohjbtröoihntröoibnrtoiöbtrjnboöitrhjtnriohötrhjtöroihjtroöihjtroösibntsroönbotöirsbntöoihjntröoihntroöbtrboöitrnhoöitrhjntröoishbnjtröosbhtröbntriohjtröoijtöoitbjöotibjnhöotirhbjntroöibhnjrtoöibnhtroöibnhtörsbnhtöoirbnhtöroibntoörhjnbträöbtrbträbtrbtirbätrsibohjntrsöiobthnjiohjsrtoib");
|
||||
|
||||
[GlobalSetup]
|
||||
public void GlobalSetup()
|
||||
{
|
||||
var writer = new MqttBufferWriter(1024, 1024);
|
||||
writer.WriteString("hgfjkdfkjlghfdjghdfljkdfhgdlkjfshgsldkfjghsdflkjghdsflkjhrstiuoghlkfjbhnfbutghjoiöjhklötnbhtroliöuhbjntluiobkjzbhtdrlskbhtruhjkfthgbkftgjhgfiklhotriuöhbjtrsioöbtrsötrhträhtrühjtriüoätrhjtsrölbktrbnhtrulöbionhströloubinströoliubhnsöotrunbtöroisntröointröioujhgötiohjgötorshjnbgtöorihbnjtröoihbjntröobntröoibntrjhötrohjbtröoihntröoibnrtoiöbtrjnboöitrhjtnriohötrhjtöroihjtroöihjtroösibntsroönbotöirsbntöoihjntröoihntroöbtrboöitrnhoöitrhjntröoishbnjtröosbhtröbntriohjtröoijtöoitbjöotibjnhöotirhbjntroöibhnjrtoöibnhtroöibnhtörsbnhtöoirbnhtöroibntoörhjnbträöbtrbträbtrbtirbätrsibohjntrsöiobthnjiohjsrtoib");
|
||||
_buffer = writer.GetBuffer();
|
||||
_bufferLength = writer.Length;
|
||||
}
|
||||
|
||||
_buffer = writer.GetBuffer();
|
||||
_bufferLength = writer.Length;
|
||||
}
|
||||
[Benchmark]
|
||||
public void Use_Span()
|
||||
{
|
||||
var span = _buffer.AsSpan(0, _bufferLength);
|
||||
Encoding.UTF8.GetString(span);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Use_Span()
|
||||
{
|
||||
var span = _buffer.AsSpan(0, _bufferLength);
|
||||
Encoding.UTF8.GetString(span);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Use_Encoding()
|
||||
{
|
||||
Encoding.UTF8.GetString(_buffer, 0, _bufferLength);
|
||||
}
|
||||
[Benchmark]
|
||||
public void Use_Encoding()
|
||||
{
|
||||
Encoding.UTF8.GetString(_buffer, 0, _bufferLength);
|
||||
}
|
||||
}
|
||||
@@ -8,89 +8,88 @@ using BenchmarkDotNet.Jobs;
|
||||
using MQTTnet.Formatter;
|
||||
using MQTTnet.Tests.Mockups;
|
||||
|
||||
namespace MQTTnet.Benchmarks
|
||||
namespace MQTTnet.Benchmarks;
|
||||
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[MemoryDiagnoser]
|
||||
public class MqttPacketReaderWriterBenchmark : BaseBenchmark
|
||||
{
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[MemoryDiagnoser]
|
||||
public class MqttPacketReaderWriterBenchmark : BaseBenchmark
|
||||
readonly byte[] _demoPayload = new byte[1024];
|
||||
|
||||
byte[] _readPayload;
|
||||
|
||||
[GlobalCleanup]
|
||||
public void GlobalCleanup()
|
||||
{
|
||||
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.WriteBinary(_demoPayload);
|
||||
writer.WriteByte(0x01);
|
||||
writer.WriteByte(0x02);
|
||||
writer.WriteVariableByteInteger(5647382);
|
||||
writer.WriteString("A relative short string.");
|
||||
writer.WriteVariableByteInteger(8574489);
|
||||
writer.WriteBinary(_demoPayload);
|
||||
writer.WriteByte(2);
|
||||
writer.WriteByte(0x02);
|
||||
writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug");
|
||||
writer.WriteBinary(_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();
|
||||
}
|
||||
}
|
||||
|
||||
[GlobalSetup]
|
||||
public void GlobalSetup()
|
||||
[Benchmark]
|
||||
public void Write_100_000_Messages()
|
||||
{
|
||||
var writer = new MqttBufferWriter(4096, 65535);
|
||||
|
||||
for (var i = 0; i < 100000; i++)
|
||||
{
|
||||
TestEnvironment.EnableLogger = false;
|
||||
|
||||
var writer = new MqttBufferWriter(4096, 65535);
|
||||
writer.WriteString("A relative short string.");
|
||||
writer.WriteBinary(_demoPayload);
|
||||
writer.WriteByte(0x01);
|
||||
writer.WriteByte(0x02);
|
||||
writer.WriteVariableByteInteger(5647382);
|
||||
writer.WriteString("A relative short string.");
|
||||
writer.WriteVariableByteInteger(8574489);
|
||||
writer.WriteVariableByteInteger(8574589);
|
||||
writer.WriteBinary(_demoPayload);
|
||||
writer.WriteByte(2);
|
||||
writer.WriteByte(0x02);
|
||||
writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug");
|
||||
writer.WriteBinary(_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.WriteBinary(_demoPayload);
|
||||
writer.WriteByte(2);
|
||||
writer.WriteByte(0x02);
|
||||
writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug");
|
||||
writer.WriteBinary(_demoPayload);
|
||||
|
||||
writer.Reset(0);
|
||||
}
|
||||
writer.Reset(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,8 @@
|
||||
// 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.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using MQTTnet.Channel;
|
||||
@@ -19,9 +16,9 @@ namespace MQTTnet.Benchmarks;
|
||||
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[MemoryDiagnoser]
|
||||
public class MqttTcpChannelBenchmark : BaseBenchmark
|
||||
public sealed class MqttTcpChannelBenchmark : BaseBenchmark, IDisposable
|
||||
{
|
||||
IMqttChannel _clientChannel;
|
||||
MqttTcpChannel _clientChannel;
|
||||
MqttServer _mqttServer;
|
||||
IMqttChannel _serverChannel;
|
||||
|
||||
@@ -85,4 +82,11 @@ public class MqttTcpChannelBenchmark : BaseBenchmark
|
||||
await _serverChannel.WriteAsync(buffer, true, CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_clientChannel?.Dispose();
|
||||
_mqttServer?.Dispose();
|
||||
_serverChannel?.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -5,138 +5,135 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using BenchmarkDotNet.Loggers;
|
||||
using BenchmarkDotNet.Running;
|
||||
using MQTTnet.Diagnostics;
|
||||
|
||||
namespace MQTTnet.Benchmarks
|
||||
namespace MQTTnet.Benchmarks;
|
||||
|
||||
public static class Program
|
||||
{
|
||||
public static class Program
|
||||
static int _selectedBenchmarkIndex;
|
||||
static List<Type> _benchmarks;
|
||||
|
||||
public static void Main(string[] arguments)
|
||||
{
|
||||
static int _selectedBenchmarkIndex;
|
||||
static List<Type> _benchmarks;
|
||||
_benchmarks = CollectBenchmarks();
|
||||
HandleArguments(arguments);
|
||||
|
||||
public static void Main(string[] arguments)
|
||||
while (true)
|
||||
{
|
||||
_benchmarks = CollectBenchmarks();
|
||||
HandleArguments(arguments);
|
||||
RenderMenu();
|
||||
|
||||
while (true)
|
||||
var key = Console.ReadKey(true);
|
||||
|
||||
if (key.Key == ConsoleKey.DownArrow)
|
||||
{
|
||||
RenderMenu();
|
||||
|
||||
var key = Console.ReadKey(true);
|
||||
|
||||
if (key.Key == ConsoleKey.DownArrow)
|
||||
_selectedBenchmarkIndex++;
|
||||
if (_selectedBenchmarkIndex > _benchmarks.Count - 1)
|
||||
{
|
||||
_selectedBenchmarkIndex++;
|
||||
if (_selectedBenchmarkIndex > _benchmarks.Count - 1)
|
||||
{
|
||||
_selectedBenchmarkIndex = 0;
|
||||
}
|
||||
}
|
||||
else if (key.Key == ConsoleKey.UpArrow)
|
||||
{
|
||||
_selectedBenchmarkIndex--;
|
||||
if (_selectedBenchmarkIndex < 0)
|
||||
{
|
||||
_selectedBenchmarkIndex = _benchmarks.Count - 1;
|
||||
}
|
||||
}
|
||||
else if (key.Key == ConsoleKey.Escape)
|
||||
{
|
||||
Environment.Exit(0);
|
||||
}
|
||||
else if (key.Key == ConsoleKey.Enter)
|
||||
{
|
||||
break;
|
||||
_selectedBenchmarkIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
BenchmarkRunner.Run(_benchmarks[_selectedBenchmarkIndex]);
|
||||
|
||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
Console.WriteLine("Press any key to exit");
|
||||
Console.ReadLine();
|
||||
}
|
||||
|
||||
static List<Type> CollectBenchmarks()
|
||||
{
|
||||
var benchmarks = new List<Type>();
|
||||
|
||||
var types = typeof(Program).Assembly.GetExportedTypes();
|
||||
foreach (var type in types)
|
||||
else if (key.Key == ConsoleKey.UpArrow)
|
||||
{
|
||||
if (typeof(BaseBenchmark).IsAssignableFrom(type))
|
||||
_selectedBenchmarkIndex--;
|
||||
if (_selectedBenchmarkIndex < 0)
|
||||
{
|
||||
if (type != typeof(BaseBenchmark))
|
||||
{
|
||||
benchmarks.Add(type);
|
||||
}
|
||||
_selectedBenchmarkIndex = _benchmarks.Count - 1;
|
||||
}
|
||||
}
|
||||
|
||||
return benchmarks.OrderBy(b => b.Name).ToList();
|
||||
}
|
||||
|
||||
static void HandleArguments(string[] arguments)
|
||||
{
|
||||
if (arguments.Length == 0)
|
||||
else if (key.Key == ConsoleKey.Escape)
|
||||
{
|
||||
return;
|
||||
Environment.Exit(0);
|
||||
}
|
||||
|
||||
// Allow for preselection to avoid developer frustration.
|
||||
|
||||
if (int.TryParse(arguments[0], out var benchmarkIndex))
|
||||
else if (key.Key == ConsoleKey.Enter)
|
||||
{
|
||||
_selectedBenchmarkIndex = benchmarkIndex;
|
||||
return;
|
||||
}
|
||||
|
||||
_selectedBenchmarkIndex = _benchmarks.FindIndex(b => b.Name.Equals(arguments[0]));
|
||||
|
||||
if (_selectedBenchmarkIndex < 0)
|
||||
{
|
||||
_selectedBenchmarkIndex = 0;
|
||||
}
|
||||
|
||||
if (_selectedBenchmarkIndex > _benchmarks.Count - 1)
|
||||
{
|
||||
_selectedBenchmarkIndex = _benchmarks.Count - 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void RenderMenu()
|
||||
BenchmarkRunner.Run(_benchmarks[_selectedBenchmarkIndex]);
|
||||
|
||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
Console.WriteLine("Press any key to exit");
|
||||
Console.ReadLine();
|
||||
}
|
||||
|
||||
static List<Type> CollectBenchmarks()
|
||||
{
|
||||
var benchmarks = new List<Type>();
|
||||
|
||||
var types = typeof(Program).Assembly.GetExportedTypes();
|
||||
foreach (var type in types)
|
||||
{
|
||||
Console.Clear();
|
||||
|
||||
Console.WriteLine($"MQTTnet - Benchmarks");
|
||||
Console.WriteLine("-----------------------------------------------");
|
||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
Console.WriteLine("Press arrow keys for benchmark selection");
|
||||
Console.WriteLine("Press Enter for benchmark execution");
|
||||
Console.WriteLine("Press Esc for exit");
|
||||
Console.ForegroundColor = ConsoleColor.White;
|
||||
Console.WriteLine();
|
||||
|
||||
for (var index = 0; index < _benchmarks.Count; index++)
|
||||
if (typeof(BaseBenchmark).IsAssignableFrom(type))
|
||||
{
|
||||
var benchmark = _benchmarks[index];
|
||||
|
||||
if (_selectedBenchmarkIndex == index)
|
||||
if (type != typeof(BaseBenchmark))
|
||||
{
|
||||
Console.Write("-> ");
|
||||
benchmarks.Add(type);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.Write(" ");
|
||||
}
|
||||
|
||||
Console.WriteLine(benchmark.Name);
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
return benchmarks.OrderBy(b => b.Name).ToList();
|
||||
}
|
||||
|
||||
static void HandleArguments(string[] arguments)
|
||||
{
|
||||
if (arguments.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Allow for preselection to avoid developer frustration.
|
||||
|
||||
if (int.TryParse(arguments[0], out var benchmarkIndex))
|
||||
{
|
||||
_selectedBenchmarkIndex = benchmarkIndex;
|
||||
return;
|
||||
}
|
||||
|
||||
_selectedBenchmarkIndex = _benchmarks.FindIndex(b => b.Name.Equals(arguments[0], StringComparison.Ordinal));
|
||||
|
||||
if (_selectedBenchmarkIndex < 0)
|
||||
{
|
||||
_selectedBenchmarkIndex = 0;
|
||||
}
|
||||
|
||||
if (_selectedBenchmarkIndex > _benchmarks.Count - 1)
|
||||
{
|
||||
_selectedBenchmarkIndex = _benchmarks.Count - 1;
|
||||
}
|
||||
}
|
||||
|
||||
static void RenderMenu()
|
||||
{
|
||||
Console.Clear();
|
||||
|
||||
Console.WriteLine($"MQTTnet - Benchmarks");
|
||||
Console.WriteLine("-----------------------------------------------");
|
||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
Console.WriteLine("Press arrow keys for benchmark selection");
|
||||
Console.WriteLine("Press Enter for benchmark execution");
|
||||
Console.WriteLine("Press Esc for exit");
|
||||
Console.ForegroundColor = ConsoleColor.White;
|
||||
Console.WriteLine();
|
||||
|
||||
for (var index = 0; index < _benchmarks.Count; index++)
|
||||
{
|
||||
var benchmark = _benchmarks[index];
|
||||
|
||||
if (_selectedBenchmarkIndex == index)
|
||||
{
|
||||
Console.Write("-> ");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.Write(" ");
|
||||
}
|
||||
|
||||
Console.WriteLine(benchmark.Name);
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
}
|
||||
}
|
||||
@@ -1,240 +1,99 @@
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.AspNetCore;
|
||||
using MQTTnet.Exceptions;
|
||||
using MQTTnet.Formatter;
|
||||
using MQTTnet.Packets;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.IO;
|
||||
using System.IO.Pipelines;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MQTTnet.Benchmarks
|
||||
{
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[RPlotExporter, RankColumn]
|
||||
[MemoryDiagnoser]
|
||||
public class ReaderExtensionsBenchmark
|
||||
{
|
||||
MqttPacketFormatterAdapter mqttPacketFormatter;
|
||||
MemoryStream stream;
|
||||
namespace MQTTnet.Benchmarks;
|
||||
|
||||
[GlobalSetup]
|
||||
public void GlobalSetup()
|
||||
{
|
||||
mqttPacketFormatter = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535));
|
||||
var mqttMessage = new MqttApplicationMessageBuilder()
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[RPlotExporter, RankColumn]
|
||||
[MemoryDiagnoser]
|
||||
public sealed class ReaderExtensionsBenchmark : IDisposable, IAsyncDisposable
|
||||
{
|
||||
MqttPacketFormatterAdapter _mqttPacketFormatter;
|
||||
MemoryStream _stream;
|
||||
|
||||
[GlobalSetup]
|
||||
public void GlobalSetup()
|
||||
{
|
||||
_mqttPacketFormatter = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535));
|
||||
var mqttMessage = new MqttApplicationMessageBuilder()
|
||||
.WithTopic("topic")
|
||||
.WithPayload(new byte[10 * 1024])
|
||||
.Build();
|
||||
|
||||
var packet = MqttPublishPacketFactory.Create(mqttMessage);
|
||||
|
||||
var buffer = mqttPacketFormatter.Encode(packet);
|
||||
stream = new MemoryStream();
|
||||
stream.Write(buffer.Packet);
|
||||
stream.Write(buffer.Payload.ToArray());
|
||||
mqttPacketFormatter.Cleanup();
|
||||
}
|
||||
|
||||
[Benchmark(Baseline = true)]
|
||||
public async Task Before()
|
||||
{
|
||||
stream.Position = 0;
|
||||
var input = PipeReader.Create(stream);
|
||||
|
||||
while (true)
|
||||
{
|
||||
ReadResult readResult;
|
||||
var readTask = input.ReadAsync();
|
||||
if (readTask.IsCompleted)
|
||||
{
|
||||
readResult = readTask.Result;
|
||||
}
|
||||
else
|
||||
{
|
||||
readResult = await readTask.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
var buffer = readResult.Buffer;
|
||||
|
||||
var consumed = buffer.Start;
|
||||
var observed = buffer.Start;
|
||||
|
||||
try
|
||||
{
|
||||
if (!buffer.IsEmpty)
|
||||
{
|
||||
if (ReaderExtensions_Before.TryDecode(mqttPacketFormatter, buffer, out var packet, out consumed, out observed, out var received))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (readResult.IsCompleted)
|
||||
{
|
||||
throw new MqttCommunicationException("Connection Aborted");
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
// The buffer was sliced up to where it was consumed, so we can just advance to the start.
|
||||
// We mark examined as buffer.End so that if we didn't receive a full frame, we'll wait for more data
|
||||
// before yielding the read again.
|
||||
input.AdvanceTo(consumed, observed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public async Task After()
|
||||
{
|
||||
stream.Position = 0;
|
||||
var input = PipeReader.Create(stream);
|
||||
|
||||
while (true)
|
||||
{
|
||||
ReadResult readResult;
|
||||
var readTask = input.ReadAsync();
|
||||
if (readTask.IsCompleted)
|
||||
{
|
||||
readResult = readTask.Result;
|
||||
}
|
||||
else
|
||||
{
|
||||
readResult = await readTask.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
var buffer = readResult.Buffer;
|
||||
|
||||
var consumed = buffer.Start;
|
||||
var observed = buffer.Start;
|
||||
|
||||
try
|
||||
{
|
||||
if (!buffer.IsEmpty)
|
||||
{
|
||||
if (ReaderExtensions.TryDecode(mqttPacketFormatter, buffer, out var packet, out consumed, out observed, out var received))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (readResult.IsCompleted)
|
||||
{
|
||||
throw new MqttCommunicationException("Connection Aborted");
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
// The buffer was sliced up to where it was consumed, so we can just advance to the start.
|
||||
// We mark examined as buffer.End so that if we didn't receive a full frame, we'll wait for more data
|
||||
// before yielding the read again.
|
||||
input.AdvanceTo(consumed, observed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class ReaderExtensions_Before
|
||||
{
|
||||
public static bool TryDecode(MqttPacketFormatterAdapter formatter,
|
||||
in ReadOnlySequence<byte> input,
|
||||
out MqttPacket packet,
|
||||
out SequencePosition consumed,
|
||||
out SequencePosition observed,
|
||||
out int bytesRead)
|
||||
{
|
||||
if (formatter == null) throw new ArgumentNullException(nameof(formatter));
|
||||
|
||||
packet = null;
|
||||
consumed = input.Start;
|
||||
observed = input.End;
|
||||
bytesRead = 0;
|
||||
var copy = input;
|
||||
|
||||
if (copy.Length < 2)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var fixedHeader = copy.First.Span[0];
|
||||
if (!TryReadBodyLength(ref copy, out int headerLength, out var bodyLength))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (copy.Length < bodyLength)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var bodySlice = copy.Slice(0, bodyLength);
|
||||
var buffer = GetMemory(bodySlice).ToArray();
|
||||
|
||||
var receivedMqttPacket = new ReceivedMqttPacket(fixedHeader, new ArraySegment<byte>(buffer, 0, buffer.Length), buffer.Length + 2);
|
||||
|
||||
if (formatter.ProtocolVersion == MqttProtocolVersion.Unknown)
|
||||
{
|
||||
formatter.DetectProtocolVersion(receivedMqttPacket);
|
||||
}
|
||||
|
||||
packet = formatter.Decode(receivedMqttPacket);
|
||||
consumed = bodySlice.End;
|
||||
observed = bodySlice.End;
|
||||
bytesRead = headerLength + bodyLength;
|
||||
return true;
|
||||
}
|
||||
|
||||
static ReadOnlyMemory<byte> GetMemory(in ReadOnlySequence<byte> input)
|
||||
{
|
||||
if (input.IsSingleSegment)
|
||||
{
|
||||
return input.First;
|
||||
}
|
||||
|
||||
// Should be rare
|
||||
return input.ToArray();
|
||||
}
|
||||
|
||||
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;
|
||||
var value = 0;
|
||||
byte encodedByte;
|
||||
var index = 1;
|
||||
headerLength = 0;
|
||||
bodyLength = 0;
|
||||
|
||||
var temp = GetMemory(input.Slice(0, Math.Min(5, input.Length))).Span;
|
||||
|
||||
do
|
||||
{
|
||||
if (index == temp.Length)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
encodedByte = temp[index];
|
||||
index++;
|
||||
|
||||
value += (byte)(encodedByte & 127) * multiplier;
|
||||
if (multiplier > 128 * 128 * 128)
|
||||
{
|
||||
throw new MqttProtocolViolationException($"Remaining length is invalid (Data={string.Join(",", temp.Slice(1, index).ToArray())}).");
|
||||
}
|
||||
|
||||
multiplier *= 128;
|
||||
} while ((encodedByte & 128) != 0);
|
||||
|
||||
input = input.Slice(index);
|
||||
|
||||
headerLength = index;
|
||||
bodyLength = value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
var packet = MqttPublishPacketFactory.Create(mqttMessage);
|
||||
|
||||
var buffer = _mqttPacketFormatter.Encode(packet);
|
||||
_stream = new MemoryStream();
|
||||
_stream.Write(buffer.Packet);
|
||||
_stream.Write(buffer.Payload.ToArray());
|
||||
_mqttPacketFormatter.Cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public async Task After()
|
||||
{
|
||||
_stream.Position = 0;
|
||||
var input = PipeReader.Create(_stream);
|
||||
|
||||
while (true)
|
||||
{
|
||||
ReadResult readResult;
|
||||
var readTask = input.ReadAsync();
|
||||
if (readTask.IsCompleted)
|
||||
{
|
||||
readResult = readTask.Result;
|
||||
}
|
||||
else
|
||||
{
|
||||
readResult = await readTask.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
var buffer = readResult.Buffer;
|
||||
|
||||
var consumed = buffer.Start;
|
||||
var observed = buffer.Start;
|
||||
|
||||
try
|
||||
{
|
||||
if (!buffer.IsEmpty)
|
||||
{
|
||||
if (_mqttPacketFormatter.TryDecode(buffer, out _, out consumed, out observed, out _))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (readResult.IsCompleted)
|
||||
{
|
||||
throw new MqttCommunicationException("Connection Aborted");
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
// The buffer was sliced up to where it was consumed, so we can just advance to the start.
|
||||
// We mark examined as buffer.End so that if we didn't receive a full frame, we'll wait for more data
|
||||
// before yielding the read again.
|
||||
input.AdvanceTo(consumed, observed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_stream?.Dispose();
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
if (_stream != null)
|
||||
{
|
||||
await _stream.DisposeAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,28 +3,27 @@ using BenchmarkDotNet.Jobs;
|
||||
using MQTTnet.Tests.Mockups;
|
||||
using MQTTnet.Tests.Server;
|
||||
|
||||
namespace MQTTnet.Benchmarks
|
||||
namespace MQTTnet.Benchmarks;
|
||||
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[RPlotExporter, RankColumn]
|
||||
[MemoryDiagnoser]
|
||||
public class RoundtripProcessingBenchmark : BaseBenchmark
|
||||
{
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[RPlotExporter, RankColumn]
|
||||
[MemoryDiagnoser]
|
||||
public class RoundtripProcessingBenchmark : BaseBenchmark
|
||||
[GlobalSetup]
|
||||
public void GlobalSetup()
|
||||
{
|
||||
[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();
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -1,69 +1,78 @@
|
||||
using System.Buffers;
|
||||
using System.IO.Pipelines;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using MQTTnet.Formatter;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.IO;
|
||||
using System.IO.Pipelines;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MQTTnet.Benchmarks
|
||||
namespace MQTTnet.Benchmarks;
|
||||
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[RPlotExporter]
|
||||
[RankColumn]
|
||||
[MemoryDiagnoser]
|
||||
public sealed class SendPacketAsyncBenchmark : BaseBenchmark, IDisposable, IAsyncDisposable
|
||||
{
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[RPlotExporter, RankColumn]
|
||||
[MemoryDiagnoser]
|
||||
public class SendPacketAsyncBenchmark : BaseBenchmark
|
||||
MqttPacketBuffer _buffer;
|
||||
MemoryStream _stream;
|
||||
|
||||
[Benchmark]
|
||||
public async ValueTask After()
|
||||
{
|
||||
MemoryStream stream;
|
||||
MqttPacketBuffer buffer;
|
||||
_stream.Position = 0;
|
||||
var output = PipeWriter.Create(_stream);
|
||||
|
||||
[GlobalSetup]
|
||||
public void GlobalSetup()
|
||||
if (_buffer.Payload.Length == 0)
|
||||
{
|
||||
stream = new MemoryStream(1024);
|
||||
var packet = new ArraySegment<byte>(new byte[10]);
|
||||
buffer = new MqttPacketBuffer(packet);
|
||||
await output.WriteAsync(_buffer.Packet).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Benchmark(Baseline = true)]
|
||||
public async ValueTask Before()
|
||||
else
|
||||
{
|
||||
stream.Position = 0;
|
||||
var output = PipeWriter.Create(stream);
|
||||
|
||||
WritePacketBuffer(output, buffer);
|
||||
await output.FlushAsync();
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public async ValueTask After()
|
||||
{
|
||||
stream.Position = 0;
|
||||
var output = PipeWriter.Create(stream);
|
||||
|
||||
if (buffer.Payload.Length == 0)
|
||||
{
|
||||
await output.WriteAsync(buffer.Packet).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
WritePacketBuffer(output, buffer);
|
||||
await output.FlushAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void WritePacketBuffer(PipeWriter output, MqttPacketBuffer buffer)
|
||||
{
|
||||
// copy MqttPacketBuffer's Packet and Payload to the same buffer block of PipeWriter
|
||||
// MqttPacket will be transmitted within the bounds of a WebSocket frame after PipeWriter.FlushAsync
|
||||
|
||||
var span = output.GetSpan(buffer.Length);
|
||||
|
||||
buffer.Packet.AsSpan().CopyTo(span);
|
||||
buffer.Payload.CopyTo(span.Slice(buffer.Packet.Count));
|
||||
|
||||
output.Advance(buffer.Length);
|
||||
WritePacketBuffer(output, _buffer);
|
||||
await output.FlushAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark(Baseline = true)]
|
||||
public async ValueTask Before()
|
||||
{
|
||||
_stream.Position = 0;
|
||||
var output = PipeWriter.Create(_stream);
|
||||
|
||||
WritePacketBuffer(output, _buffer);
|
||||
await output.FlushAsync();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_stream?.Dispose();
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
if (_stream != null)
|
||||
{
|
||||
await _stream.DisposeAsync();
|
||||
}
|
||||
}
|
||||
|
||||
[GlobalSetup]
|
||||
public void GlobalSetup()
|
||||
{
|
||||
_stream = new MemoryStream(1024);
|
||||
var packet = new ArraySegment<byte>(new byte[10]);
|
||||
_buffer = new MqttPacketBuffer(packet);
|
||||
}
|
||||
|
||||
static void WritePacketBuffer(PipeWriter output, MqttPacketBuffer buffer)
|
||||
{
|
||||
// copy MqttPacketBuffer's Packet and Payload to the same buffer block of PipeWriter
|
||||
// MqttPacket will be transmitted within the bounds of a WebSocket frame after PipeWriter.FlushAsync
|
||||
|
||||
var span = output.GetSpan(buffer.Length);
|
||||
|
||||
buffer.Packet.AsSpan().CopyTo(span);
|
||||
buffer.Payload.CopyTo(span[buffer.Packet.Count..]);
|
||||
|
||||
output.Advance(buffer.Length);
|
||||
}
|
||||
}
|
||||
@@ -15,104 +15,106 @@ using MQTTnet.Formatter.V3;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using MQTTnet.Diagnostics.Logger;
|
||||
using System.Buffers;
|
||||
using System.Net;
|
||||
|
||||
namespace MQTTnet.Benchmarks
|
||||
namespace MQTTnet.Benchmarks;
|
||||
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[RPlotExporter]
|
||||
[MemoryDiagnoser]
|
||||
public class SerializerBenchmark : BaseBenchmark
|
||||
{
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[RPlotExporter]
|
||||
[MemoryDiagnoser]
|
||||
public class SerializerBenchmark : BaseBenchmark
|
||||
MqttPacket _packet;
|
||||
ArraySegment<byte> _serializedPacket;
|
||||
MqttV3PacketFormatter _serializer;
|
||||
MqttBufferWriter _bufferWriter;
|
||||
|
||||
[GlobalSetup]
|
||||
public void GlobalSetup()
|
||||
{
|
||||
MqttPacket _packet;
|
||||
ArraySegment<byte> _serializedPacket;
|
||||
IMqttPacketFormatter _serializer;
|
||||
MqttBufferWriter _bufferWriter;
|
||||
|
||||
[GlobalSetup]
|
||||
public void GlobalSetup()
|
||||
_packet = new MqttPublishPacket
|
||||
{
|
||||
_packet = new MqttPublishPacket
|
||||
{
|
||||
Topic = "A"
|
||||
};
|
||||
Topic = "A"
|
||||
};
|
||||
|
||||
_bufferWriter = new MqttBufferWriter(4096, 65535);
|
||||
_serializer = new MqttV3PacketFormatter(_bufferWriter, MqttProtocolVersion.V311);
|
||||
_serializedPacket = _serializer.Encode(_packet).Join();
|
||||
}
|
||||
_bufferWriter = new MqttBufferWriter(4096, 65535);
|
||||
_serializer = new MqttV3PacketFormatter(_bufferWriter, MqttProtocolVersion.V311);
|
||||
_serializedPacket = _serializer.Encode(_packet).Join();
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Serialize_10000_Messages()
|
||||
[Benchmark]
|
||||
public void Serialize10000Messages()
|
||||
{
|
||||
for (var i = 0; i < 10000; i++)
|
||||
{
|
||||
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)), 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(ReadOnlySequence<byte> buffer, bool isEndOfPacket, CancellationToken cancellationToken)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
_serializer.Encode(_packet);
|
||||
_bufferWriter.Cleanup();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Deserialize10000Messages()
|
||||
{
|
||||
var channel = new BenchmarkMqttChannel(_serializedPacket);
|
||||
var reader = new MqttChannelAdapter(channel, new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535)), new MqttNetEventLogger());
|
||||
|
||||
for (var i = 0; i < 10000; i++)
|
||||
{
|
||||
channel.Reset();
|
||||
|
||||
reader.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
|
||||
sealed class BenchmarkMqttChannel : IMqttChannel
|
||||
{
|
||||
readonly ArraySegment<byte> _buffer;
|
||||
int _position;
|
||||
|
||||
public BenchmarkMqttChannel(ArraySegment<byte> buffer)
|
||||
{
|
||||
_buffer = buffer;
|
||||
_position = _buffer.Offset;
|
||||
}
|
||||
|
||||
public EndPoint RemoteEndPoint { get; set; }
|
||||
|
||||
public EndPoint LocalEndPoint { get; set; }
|
||||
|
||||
public bool IsSecureConnection { get; }
|
||||
|
||||
public X509Certificate2 ClientCertificate { get; set; }
|
||||
|
||||
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(ReadOnlySequence<byte> buffer, bool isEndOfPacket, CancellationToken cancellationToken)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,34 +7,33 @@ using BenchmarkDotNet.Jobs;
|
||||
using MQTTnet.Tests.Mockups;
|
||||
using MQTTnet.Tests.Server;
|
||||
|
||||
namespace MQTTnet.Benchmarks
|
||||
namespace MQTTnet.Benchmarks;
|
||||
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[RPlotExporter, RankColumn]
|
||||
[MemoryDiagnoser]
|
||||
public class ServerProcessingBenchmark : BaseBenchmark
|
||||
{
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[RPlotExporter, RankColumn]
|
||||
[MemoryDiagnoser]
|
||||
public class ServerProcessingBenchmark : BaseBenchmark
|
||||
[GlobalSetup]
|
||||
public void GlobalSetup()
|
||||
{
|
||||
[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();
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,7 @@ public class SubscribeBenchmark : BaseBenchmark
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
TopicGenerator.Generate(NumPublishers, NumTopicsPerPublisher, out var topicsByPublisher, out var singleWildcardTopicsByPublisher, out var multiWildcardTopicsByPublisher);
|
||||
TopicGenerator.Generate(NumPublishers, NumTopicsPerPublisher, out var topicsByPublisher, out _, out _);
|
||||
_topics = topicsByPublisher.Values.First();
|
||||
|
||||
var serverOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
|
||||
|
||||
@@ -11,70 +11,69 @@ using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using MQTTnet.AspNetCore;
|
||||
|
||||
namespace MQTTnet.Benchmarks
|
||||
namespace MQTTnet.Benchmarks;
|
||||
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[MemoryDiagnoser]
|
||||
public class TcpPipesBenchmark : BaseBenchmark
|
||||
{
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[MemoryDiagnoser]
|
||||
public class TcpPipesBenchmark : BaseBenchmark
|
||||
IDuplexPipe _client;
|
||||
IDuplexPipe _server;
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
IDuplexPipe _client;
|
||||
IDuplexPipe _server;
|
||||
var server = new TcpListener(IPAddress.Any, 1883);
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
server.Start(1);
|
||||
|
||||
var task = Task.Run(() => server.AcceptSocket());
|
||||
|
||||
var clientConnection = new SocketConnection(new IPEndPoint(IPAddress.Loopback, 1883));
|
||||
|
||||
clientConnection.StartAsync().GetAwaiter().GetResult();
|
||||
_client = clientConnection.Transport;
|
||||
|
||||
var serverConnection = new SocketConnection(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 server = new TcpListener(IPAddress.Any, 1883);
|
||||
|
||||
server.Start(1);
|
||||
|
||||
var task = Task.Run(() => server.AcceptSocket());
|
||||
|
||||
var clientConnection = new SocketConnection(new IPEndPoint(IPAddress.Loopback, 1883));
|
||||
|
||||
clientConnection.StartAsync().GetAwaiter().GetResult();
|
||||
_client = clientConnection.Transport;
|
||||
|
||||
var serverConnection = new SocketConnection(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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,276 +2,32 @@
|
||||
// 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
|
||||
namespace MQTTnet.Benchmarks;
|
||||
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[RPlotExporter]
|
||||
[MemoryDiagnoser]
|
||||
public class TopicFilterComparerBenchmark : BaseBenchmark
|
||||
{
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[RPlotExporter]
|
||||
[MemoryDiagnoser]
|
||||
public class TopicFilterComparerBenchmark : BaseBenchmark
|
||||
readonly string _longTopic =
|
||||
"AAAAAAAAAAAAAssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssshhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC";
|
||||
|
||||
[Benchmark]
|
||||
public void MqttTopicFilterComparer_10000_LoopMethod()
|
||||
{
|
||||
static readonly char[] TopicLevelSeparator = { '/' };
|
||||
|
||||
readonly string _longTopic =
|
||||
"AAAAAAAAAAAAAssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssshhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC";
|
||||
|
||||
[Benchmark]
|
||||
public void MqttTopicFilterComparer_10000_LoopMethod()
|
||||
for (var i = 0; i < 100000; i++)
|
||||
{
|
||||
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;
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,84 +1,83 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace MQTTnet.Benchmarks
|
||||
namespace MQTTnet.Benchmarks;
|
||||
|
||||
public class TopicGenerator
|
||||
{
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
topicsByPublisher = new Dictionary<string, List<string>>();
|
||||
singleWildcardTopicsByPublisher = new Dictionary<string, List<string>>();
|
||||
multiWildcardTopicsByPublisher = new Dictionary<string, List<string>>();
|
||||
topicsPerLevel = 1;
|
||||
}
|
||||
|
||||
// Find some reasonable distribution across three topic levels
|
||||
var topicsPerLevel = (int)Math.Pow(numTopicsPerPublisher, 1.0 / 3.0);
|
||||
if (topicsPerLevel <= 0)
|
||||
var numLevel1Topics = topicsPerLevel;
|
||||
var numLevel2Topics = topicsPerLevel;
|
||||
|
||||
var maxNumLevel3Topics = 1 + (int)((double)numTopicsPerPublisher / numLevel1Topics / numLevel2Topics);
|
||||
if (maxNumLevel3Topics <= 0)
|
||||
{
|
||||
maxNumLevel3Topics = 1;
|
||||
}
|
||||
|
||||
for (var p = 0; p < numPublishers; ++p)
|
||||
{
|
||||
var publisherTopicCount = 0;
|
||||
var publisherName = "pub" + p;
|
||||
for (var l1 = 0; l1 < numLevel1Topics; ++l1)
|
||||
{
|
||||
topicsPerLevel = 1;
|
||||
}
|
||||
|
||||
var numLevel1Topics = topicsPerLevel;
|
||||
var numLevel2Topics = topicsPerLevel;
|
||||
|
||||
var maxNumLevel3Topics = 1 + (int)((double)numTopicsPerPublisher / numLevel1Topics / numLevel2Topics);
|
||||
if (maxNumLevel3Topics <= 0)
|
||||
{
|
||||
maxNumLevel3Topics = 1;
|
||||
}
|
||||
|
||||
for (var p = 0; p < numPublishers; ++p)
|
||||
{
|
||||
var publisherTopicCount = 0;
|
||||
var publisherName = "pub" + p;
|
||||
for (var l1 = 0; l1 < numLevel1Topics; ++l1)
|
||||
for (var l2 = 0; l2 < numLevel2Topics; ++l2)
|
||||
{
|
||||
for (var l2 = 0; l2 < numLevel2Topics; ++l2)
|
||||
for (var l3 = 0; l3 < maxNumLevel3Topics; ++l3)
|
||||
{
|
||||
for (var l3 = 0; l3 < maxNumLevel3Topics; ++l3)
|
||||
if (publisherTopicCount >= numTopicsPerPublisher)
|
||||
{
|
||||
if (publisherTopicCount >= numTopicsPerPublisher)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
var topic = $"{publisherName}/building{l1 + 1}/level{l2 + 1}/sensor{l3 + 1}";
|
||||
AddPublisherTopic(publisherName, topic, topicsByPublisher);
|
||||
|
||||
if (l2 == 0)
|
||||
{
|
||||
var singleWildcardTopic = $"{publisherName}/building{l1 + 1}/+/sensor{l3 + 1}";
|
||||
AddPublisherTopic(publisherName, singleWildcardTopic, singleWildcardTopicsByPublisher);
|
||||
}
|
||||
|
||||
if (l1 == 0 && l3 == 0)
|
||||
{
|
||||
var multiWildcardTopic = $"{publisherName}/+/level{l2 + 1}/+";
|
||||
AddPublisherTopic(publisherName, multiWildcardTopic, multiWildcardTopicsByPublisher);
|
||||
}
|
||||
|
||||
++publisherTopicCount;
|
||||
break;
|
||||
}
|
||||
|
||||
var topic = $"{publisherName}/building{l1 + 1}/level{l2 + 1}/sensor{l3 + 1}";
|
||||
AddPublisherTopic(publisherName, topic, topicsByPublisher);
|
||||
|
||||
if (l2 == 0)
|
||||
{
|
||||
var singleWildcardTopic = $"{publisherName}/building{l1 + 1}/+/sensor{l3 + 1}";
|
||||
AddPublisherTopic(publisherName, singleWildcardTopic, singleWildcardTopicsByPublisher);
|
||||
}
|
||||
|
||||
if (l1 == 0 && l3 == 0)
|
||||
{
|
||||
var multiWildcardTopic = $"{publisherName}/+/level{l2 + 1}/+";
|
||||
AddPublisherTopic(publisherName, multiWildcardTopic, multiWildcardTopicsByPublisher);
|
||||
}
|
||||
|
||||
++publisherTopicCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void AddPublisherTopic(string publisherName, string topic, Dictionary<string, List<string>> topicsByPublisher)
|
||||
static void AddPublisherTopic(string publisherName, string topic, Dictionary<string, List<string>> topicsByPublisher)
|
||||
{
|
||||
List<string> topicList;
|
||||
if (!topicsByPublisher.TryGetValue(publisherName, out topicList))
|
||||
{
|
||||
List<string> topicList;
|
||||
if (!topicsByPublisher.TryGetValue(publisherName, out topicList))
|
||||
{
|
||||
topicList = new List<string>();
|
||||
topicsByPublisher.Add(publisherName, topicList);
|
||||
}
|
||||
|
||||
topicList.Add(topic);
|
||||
topicList = new List<string>();
|
||||
topicsByPublisher.Add(publisherName, topicList);
|
||||
}
|
||||
|
||||
topicList.Add(topic);
|
||||
}
|
||||
}
|
||||
@@ -14,15 +14,15 @@ public class UnsubscribeBenchmark : BaseBenchmark
|
||||
{
|
||||
const int NumPublishers = 1;
|
||||
const int NumTopicsPerPublisher = 10000;
|
||||
|
||||
IMqttClient _mqttClient;
|
||||
MqttServer _mqttServer;
|
||||
|
||||
List<string> _topics;
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
TopicGenerator.Generate(NumPublishers, NumTopicsPerPublisher, out var topicsByPublisher, out var singleWildcardTopicsByPublisher, out var multiWildcardTopicsByPublisher);
|
||||
TopicGenerator.Generate(NumPublishers, NumTopicsPerPublisher, out var topicsByPublisher, out _, out _);
|
||||
_topics = topicsByPublisher.Values.First();
|
||||
|
||||
var serverOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
|
||||
|
||||
@@ -4,30 +4,26 @@
|
||||
|
||||
using System;
|
||||
|
||||
namespace MQTTnet.Extensions.Rpc
|
||||
namespace MQTTnet.Extensions.Rpc;
|
||||
|
||||
public sealed class DefaultMqttRpcClientTopicGenerationStrategy : IMqttRpcClientTopicGenerationStrategy
|
||||
{
|
||||
public sealed class DefaultMqttRpcClientTopicGenerationStrategy : IMqttRpcClientTopicGenerationStrategy
|
||||
public MqttRpcTopicPair CreateRpcTopics(TopicGenerationContext context)
|
||||
{
|
||||
public MqttRpcTopicPair CreateRpcTopics(TopicGenerationContext context)
|
||||
ArgumentNullException.ThrowIfNull(context);
|
||||
|
||||
if (context.MethodName.Contains('/') || context.MethodName.Contains('+') || context.MethodName.Contains('#'))
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (context.MethodName.Contains("/") || context.MethodName.Contains("+") || context.MethodName.Contains("#"))
|
||||
{
|
||||
throw new ArgumentException("The method name cannot contain /, + or #.");
|
||||
}
|
||||
|
||||
var requestTopic = $"MQTTnet.RPC/{Guid.NewGuid():N}/{context.MethodName}";
|
||||
var responseTopic = requestTopic + "/response";
|
||||
|
||||
return new MqttRpcTopicPair
|
||||
{
|
||||
RequestTopic = requestTopic,
|
||||
ResponseTopic = responseTopic
|
||||
};
|
||||
throw new ArgumentException("The method name cannot contain /, + or #.");
|
||||
}
|
||||
|
||||
var requestTopic = $"MQTTnet.RPC/{Guid.NewGuid():N}/{context.MethodName}";
|
||||
var responseTopic = requestTopic + "/response";
|
||||
|
||||
return new MqttRpcTopicPair
|
||||
{
|
||||
RequestTopic = requestTopic,
|
||||
ResponseTopic = responseTopic
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,12 +8,11 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
namespace MQTTnet.Extensions.Rpc
|
||||
namespace MQTTnet.Extensions.Rpc;
|
||||
|
||||
public interface IMqttRpcClient : IDisposable
|
||||
{
|
||||
public interface IMqttRpcClient : IDisposable
|
||||
{
|
||||
Task<byte[]> ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary<string,object> parameters = null);
|
||||
|
||||
Task<byte[]> ExecuteAsync(string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary<string, object> parameters = null, CancellationToken cancellationToken = default);
|
||||
}
|
||||
Task<byte[]> ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary<string,object> parameters = null);
|
||||
|
||||
Task<byte[]> ExecuteAsync(string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary<string, object> parameters = null, CancellationToken cancellationToken = default);
|
||||
}
|
||||
@@ -2,10 +2,9 @@
|
||||
// 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
|
||||
namespace MQTTnet.Extensions.Rpc;
|
||||
|
||||
public interface IMqttRpcClientTopicGenerationStrategy
|
||||
{
|
||||
public interface IMqttRpcClientTopicGenerationStrategy
|
||||
{
|
||||
MqttRpcTopicPair CreateRpcTopics(TopicGenerationContext context);
|
||||
}
|
||||
}
|
||||
MqttRpcTopicPair CreateRpcTopics(TopicGenerationContext context);
|
||||
}
|
||||
@@ -1,54 +1,19 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<AssemblyName>MQTTnet.Extensions.Rpc</AssemblyName>
|
||||
<RootNamespace>MQTTnet.Extensions.Rpc</RootNamespace>
|
||||
<IsAotCompatible>true</IsAotCompatible>
|
||||
|
||||
<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>Copyright (c) .NET Foundation and Contributors</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>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<PackageReleaseNotes>For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).</PackageReleaseNotes>
|
||||
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<NoWarn>1591;NETSDK1138;NU1803;NU1901;NU1902</NoWarn>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<NuGetAuditMode>all</NuGetAuditMode>
|
||||
<NuGetAudit>true</NuGetAudit>
|
||||
<NuGetAuditLevel>low</NuGetAuditLevel>
|
||||
<AnalysisLevel>latest-Recommended</AnalysisLevel>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="..\..\Images\nuget.png">
|
||||
<Pack>True</Pack>
|
||||
<PackagePath>\</PackagePath>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MQTTnet\MQTTnet.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
@@ -4,33 +4,25 @@
|
||||
|
||||
using System;
|
||||
|
||||
namespace MQTTnet.Extensions.Rpc
|
||||
namespace MQTTnet.Extensions.Rpc;
|
||||
|
||||
public static class MqttFactoryExtensions
|
||||
{
|
||||
public static class MqttFactoryExtensions
|
||||
public static IMqttRpcClient CreateMqttRpcClient(this MqttClientFactory clientFactory, IMqttClient mqttClient)
|
||||
{
|
||||
public static IMqttRpcClient CreateMqttRpcClient(this MqttClientFactory clientFactory, IMqttClient mqttClient)
|
||||
{
|
||||
return clientFactory.CreateMqttRpcClient(
|
||||
mqttClient,
|
||||
new MqttRpcClientOptions
|
||||
{
|
||||
TopicGenerationStrategy = new DefaultMqttRpcClientTopicGenerationStrategy()
|
||||
});
|
||||
}
|
||||
|
||||
public static IMqttRpcClient CreateMqttRpcClient(this MqttClientFactory _, IMqttClient mqttClient, MqttRpcClientOptions rpcClientOptions)
|
||||
{
|
||||
if (mqttClient == null)
|
||||
return clientFactory.CreateMqttRpcClient(
|
||||
mqttClient,
|
||||
new MqttRpcClientOptions
|
||||
{
|
||||
throw new ArgumentNullException(nameof(mqttClient));
|
||||
}
|
||||
TopicGenerationStrategy = new DefaultMqttRpcClientTopicGenerationStrategy()
|
||||
});
|
||||
}
|
||||
|
||||
if (rpcClientOptions == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(rpcClientOptions));
|
||||
}
|
||||
public static IMqttRpcClient CreateMqttRpcClient(this MqttClientFactory _, IMqttClient mqttClient, MqttRpcClientOptions rpcClientOptions)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(mqttClient);
|
||||
ArgumentNullException.ThrowIfNull(rpcClientOptions);
|
||||
|
||||
return new MqttRpcClient(mqttClient, rpcClientOptions);
|
||||
}
|
||||
return new MqttRpcClient(mqttClient, rpcClientOptions);
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@ using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Exceptions;
|
||||
@@ -14,131 +13,125 @@ using MQTTnet.Formatter;
|
||||
using MQTTnet.Internal;
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
namespace MQTTnet.Extensions.Rpc
|
||||
namespace MQTTnet.Extensions.Rpc;
|
||||
|
||||
public sealed class MqttRpcClient : IMqttRpcClient
|
||||
{
|
||||
public sealed class MqttRpcClient : IMqttRpcClient
|
||||
readonly IMqttClient _mqttClient;
|
||||
readonly MqttRpcClientOptions _options;
|
||||
|
||||
readonly ConcurrentDictionary<string, AsyncTaskCompletionSource<byte[]>> _waitingCalls = new ConcurrentDictionary<string, AsyncTaskCompletionSource<byte[]>>();
|
||||
|
||||
public MqttRpcClient(IMqttClient mqttClient, MqttRpcClientOptions options)
|
||||
{
|
||||
readonly IMqttClient _mqttClient;
|
||||
readonly MqttRpcClientOptions _options;
|
||||
_mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
|
||||
readonly ConcurrentDictionary<string, AsyncTaskCompletionSource<byte[]>> _waitingCalls = new ConcurrentDictionary<string, AsyncTaskCompletionSource<byte[]>>();
|
||||
_mqttClient.ApplicationMessageReceivedAsync += HandleApplicationMessageReceivedAsync;
|
||||
}
|
||||
|
||||
public MqttRpcClient(IMqttClient mqttClient, MqttRpcClientOptions options)
|
||||
public void Dispose()
|
||||
{
|
||||
_mqttClient.ApplicationMessageReceivedAsync -= HandleApplicationMessageReceivedAsync;
|
||||
|
||||
foreach (var tcs in _waitingCalls)
|
||||
{
|
||||
_mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
|
||||
_mqttClient.ApplicationMessageReceivedAsync += HandleApplicationMessageReceivedAsync;
|
||||
tcs.Value.TrySetCanceled();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
_waitingCalls.Clear();
|
||||
}
|
||||
|
||||
public async Task<byte[]> ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary<string, object> parameters = null)
|
||||
{
|
||||
using var timeoutToken = new CancellationTokenSource(timeout);
|
||||
try
|
||||
{
|
||||
_mqttClient.ApplicationMessageReceivedAsync -= HandleApplicationMessageReceivedAsync;
|
||||
|
||||
foreach (var tcs in _waitingCalls)
|
||||
{
|
||||
tcs.Value.TrySetCanceled();
|
||||
}
|
||||
|
||||
_waitingCalls.Clear();
|
||||
return await ExecuteAsync(methodName, payload, qualityOfServiceLevel, parameters, timeoutToken.Token).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task<byte[]> ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary<string, object> parameters = null)
|
||||
catch (OperationCanceledException exception)
|
||||
{
|
||||
using (var timeoutToken = new CancellationTokenSource(timeout))
|
||||
if (timeoutToken.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await ExecuteAsync(methodName, payload, qualityOfServiceLevel, parameters, timeoutToken.Token).ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException exception)
|
||||
{
|
||||
if (timeoutToken.IsCancellationRequested)
|
||||
{
|
||||
throw new MqttCommunicationTimedOutException(exception);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<byte[]> ExecuteAsync(string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary<string, object> parameters = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (methodName == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(methodName));
|
||||
throw new MqttCommunicationTimedOutException(exception);
|
||||
}
|
||||
|
||||
var context = new TopicGenerationContext(_mqttClient, _options, methodName, parameters, qualityOfServiceLevel);
|
||||
var topicNames = _options.TopicGenerationStrategy.CreateRpcTopics(context);
|
||||
|
||||
var requestTopic = topicNames.RequestTopic;
|
||||
var responseTopic = topicNames.ResponseTopic;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(requestTopic))
|
||||
{
|
||||
throw new MqttProtocolViolationException("RPC request topic is empty.");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(responseTopic))
|
||||
{
|
||||
throw new MqttProtocolViolationException("RPC response topic is empty.");
|
||||
}
|
||||
|
||||
var requestMessageBuilder = new MqttApplicationMessageBuilder().WithTopic(requestTopic).WithPayload(payload).WithQualityOfServiceLevel(qualityOfServiceLevel);
|
||||
|
||||
if (_mqttClient.Options.ProtocolVersion == MqttProtocolVersion.V500)
|
||||
{
|
||||
requestMessageBuilder.WithResponseTopic(responseTopic);
|
||||
}
|
||||
|
||||
var requestMessage = requestMessageBuilder.Build();
|
||||
|
||||
try
|
||||
{
|
||||
var awaitable = new AsyncTaskCompletionSource<byte[]>();
|
||||
|
||||
if (!_waitingCalls.TryAdd(responseTopic, awaitable))
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
var subscribeOptions = new MqttClientSubscribeOptionsBuilder().WithTopicFilter(responseTopic, qualityOfServiceLevel).Build();
|
||||
|
||||
await _mqttClient.SubscribeAsync(subscribeOptions, cancellationToken).ConfigureAwait(false);
|
||||
await _mqttClient.PublishAsync(requestMessage, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
using (cancellationToken.Register(
|
||||
() =>
|
||||
{
|
||||
awaitable.TrySetCanceled();
|
||||
}))
|
||||
{
|
||||
return await awaitable.Task.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_waitingCalls.TryRemove(responseTopic, out _);
|
||||
await _mqttClient.UnsubscribeAsync(responseTopic, CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
Task HandleApplicationMessageReceivedAsync(MqttApplicationMessageReceivedEventArgs eventArgs)
|
||||
{
|
||||
if (!_waitingCalls.TryRemove(eventArgs.ApplicationMessage.Topic, out var awaitable))
|
||||
{
|
||||
return CompletedTask.Instance;
|
||||
}
|
||||
|
||||
var payloadBuffer = eventArgs.ApplicationMessage.Payload.ToArray();
|
||||
awaitable.TrySetResult(payloadBuffer);
|
||||
|
||||
// Set this message to handled to that other code can avoid execution etc.
|
||||
eventArgs.IsHandled = true;
|
||||
|
||||
return CompletedTask.Instance;
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<byte[]> ExecuteAsync(string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary<string, object> parameters = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(methodName);
|
||||
|
||||
var context = new TopicGenerationContext(_mqttClient, _options, methodName, parameters, qualityOfServiceLevel);
|
||||
var topicNames = _options.TopicGenerationStrategy.CreateRpcTopics(context);
|
||||
|
||||
var requestTopic = topicNames.RequestTopic;
|
||||
var responseTopic = topicNames.ResponseTopic;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(requestTopic))
|
||||
{
|
||||
throw new MqttProtocolViolationException("RPC request topic is empty.");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(responseTopic))
|
||||
{
|
||||
throw new MqttProtocolViolationException("RPC response topic is empty.");
|
||||
}
|
||||
|
||||
var requestMessageBuilder = new MqttApplicationMessageBuilder().WithTopic(requestTopic).WithPayload(payload).WithQualityOfServiceLevel(qualityOfServiceLevel);
|
||||
|
||||
if (_mqttClient.Options.ProtocolVersion == MqttProtocolVersion.V500)
|
||||
{
|
||||
requestMessageBuilder.WithResponseTopic(responseTopic);
|
||||
}
|
||||
|
||||
var requestMessage = requestMessageBuilder.Build();
|
||||
|
||||
try
|
||||
{
|
||||
var awaitable = new AsyncTaskCompletionSource<byte[]>();
|
||||
|
||||
if (!_waitingCalls.TryAdd(responseTopic, awaitable))
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
var subscribeOptions = new MqttClientSubscribeOptionsBuilder().WithTopicFilter(responseTopic, qualityOfServiceLevel).Build();
|
||||
|
||||
await _mqttClient.SubscribeAsync(subscribeOptions, cancellationToken).ConfigureAwait(false);
|
||||
await _mqttClient.PublishAsync(requestMessage, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
using (cancellationToken.Register(
|
||||
() =>
|
||||
{
|
||||
awaitable.TrySetCanceled();
|
||||
}))
|
||||
{
|
||||
return await awaitable.Task.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_waitingCalls.TryRemove(responseTopic, out _);
|
||||
await _mqttClient.UnsubscribeAsync(responseTopic, CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
Task HandleApplicationMessageReceivedAsync(MqttApplicationMessageReceivedEventArgs eventArgs)
|
||||
{
|
||||
if (!_waitingCalls.TryRemove(eventArgs.ApplicationMessage.Topic, out var awaitable))
|
||||
{
|
||||
return CompletedTask.Instance;
|
||||
}
|
||||
|
||||
var payloadBuffer = eventArgs.ApplicationMessage.Payload.ToArray();
|
||||
awaitable.TrySetResult(payloadBuffer);
|
||||
|
||||
// Set this message to handled to that other code can avoid execution etc.
|
||||
eventArgs.IsHandled = true;
|
||||
|
||||
return CompletedTask.Instance;
|
||||
}
|
||||
}
|
||||
@@ -8,17 +8,16 @@ using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
namespace MQTTnet.Extensions.Rpc
|
||||
namespace MQTTnet.Extensions.Rpc;
|
||||
|
||||
public static class MqttRpcClientExtensions
|
||||
{
|
||||
public static class MqttRpcClientExtensions
|
||||
public static Task<byte[]> ExecuteAsync(this IMqttRpcClient client, TimeSpan timeout, string methodName, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary<string,object> parameters = null)
|
||||
{
|
||||
public static Task<byte[]> ExecuteAsync(this IMqttRpcClient client, TimeSpan timeout, string methodName, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary<string,object> parameters = null)
|
||||
{
|
||||
if (client == null) throw new ArgumentNullException(nameof(client));
|
||||
ArgumentNullException.ThrowIfNull(client);
|
||||
|
||||
var buffer = Encoding.UTF8.GetBytes(payload ?? string.Empty);
|
||||
var buffer = Encoding.UTF8.GetBytes(payload ?? string.Empty);
|
||||
|
||||
return client.ExecuteAsync(timeout, methodName, buffer, qualityOfServiceLevel, parameters);
|
||||
}
|
||||
return client.ExecuteAsync(timeout, methodName, buffer, qualityOfServiceLevel, parameters);
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,9 @@
|
||||
// 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
|
||||
namespace MQTTnet.Extensions.Rpc;
|
||||
|
||||
public sealed class MqttRpcClientOptions
|
||||
{
|
||||
public sealed class MqttRpcClientOptions
|
||||
{
|
||||
public IMqttRpcClientTopicGenerationStrategy TopicGenerationStrategy { get; set; } = new DefaultMqttRpcClientTopicGenerationStrategy();
|
||||
}
|
||||
}
|
||||
public IMqttRpcClientTopicGenerationStrategy TopicGenerationStrategy { get; set; } = new DefaultMqttRpcClientTopicGenerationStrategy();
|
||||
}
|
||||
@@ -4,25 +4,24 @@
|
||||
|
||||
using System;
|
||||
|
||||
namespace MQTTnet.Extensions.Rpc
|
||||
namespace MQTTnet.Extensions.Rpc;
|
||||
|
||||
public sealed class MqttRpcClientOptionsBuilder
|
||||
{
|
||||
public sealed class MqttRpcClientOptionsBuilder
|
||||
IMqttRpcClientTopicGenerationStrategy _topicGenerationStrategy = new DefaultMqttRpcClientTopicGenerationStrategy();
|
||||
|
||||
public MqttRpcClientOptions Build()
|
||||
{
|
||||
IMqttRpcClientTopicGenerationStrategy _topicGenerationStrategy = new DefaultMqttRpcClientTopicGenerationStrategy();
|
||||
|
||||
public MqttRpcClientOptions Build()
|
||||
return new MqttRpcClientOptions
|
||||
{
|
||||
return new MqttRpcClientOptions
|
||||
{
|
||||
TopicGenerationStrategy = _topicGenerationStrategy
|
||||
};
|
||||
}
|
||||
TopicGenerationStrategy = _topicGenerationStrategy
|
||||
};
|
||||
}
|
||||
|
||||
public MqttRpcClientOptionsBuilder WithTopicGenerationStrategy(IMqttRpcClientTopicGenerationStrategy value)
|
||||
{
|
||||
_topicGenerationStrategy = value ?? throw new ArgumentNullException(nameof(value));
|
||||
public MqttRpcClientOptionsBuilder WithTopicGenerationStrategy(IMqttRpcClientTopicGenerationStrategy value)
|
||||
{
|
||||
_topicGenerationStrategy = value ?? throw new ArgumentNullException(nameof(value));
|
||||
|
||||
return this;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,11 @@
|
||||
// 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
|
||||
{
|
||||
public string RequestTopic { get; set; }
|
||||
namespace MQTTnet.Extensions.Rpc;
|
||||
|
||||
public string ResponseTopic { get; set; }
|
||||
}
|
||||
}
|
||||
public sealed class MqttRpcTopicPair
|
||||
{
|
||||
public string RequestTopic { get; set; }
|
||||
|
||||
public string ResponseTopic { get; set; }
|
||||
}
|
||||
@@ -6,37 +6,36 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
namespace MQTTnet.Extensions.Rpc
|
||||
namespace MQTTnet.Extensions.Rpc;
|
||||
|
||||
public sealed class TopicGenerationContext
|
||||
{
|
||||
public sealed class TopicGenerationContext
|
||||
public TopicGenerationContext(IMqttClient mqttClient, MqttRpcClientOptions options, string methodName,
|
||||
IDictionary<string, object> parameters, MqttQualityOfServiceLevel qualityOfServiceLevel)
|
||||
{
|
||||
public TopicGenerationContext(IMqttClient mqttClient, MqttRpcClientOptions options, string methodName,
|
||||
IDictionary<string, object> parameters, MqttQualityOfServiceLevel qualityOfServiceLevel)
|
||||
{
|
||||
MethodName = methodName ?? throw new ArgumentNullException(nameof(methodName));
|
||||
Parameters = parameters;
|
||||
QualityOfServiceLevel = qualityOfServiceLevel;
|
||||
MqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
|
||||
Options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
public string MethodName { get; }
|
||||
|
||||
public IDictionary<string, object> Parameters { get; }
|
||||
|
||||
public IMqttClient MqttClient { get; }
|
||||
|
||||
public MqttRpcClientOptions Options { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the quality of service level.
|
||||
/// The Quality of Service (QoS) level is an agreement between the sender of a message and the receiver of a message
|
||||
/// that defines the guarantee of delivery for a specific message.
|
||||
/// There are 3 QoS levels in MQTT:
|
||||
/// - At most once (0): Message gets delivered no time, once or multiple times.
|
||||
/// - At least once (1): Message gets delivered at least once (one time or more often).
|
||||
/// - Exactly once (2): Message gets delivered exactly once (It's ensured that the message only comes once).
|
||||
/// </summary>
|
||||
public MqttQualityOfServiceLevel QualityOfServiceLevel { get; }
|
||||
MethodName = methodName ?? throw new ArgumentNullException(nameof(methodName));
|
||||
Parameters = parameters;
|
||||
QualityOfServiceLevel = qualityOfServiceLevel;
|
||||
MqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
|
||||
Options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
public string MethodName { get; }
|
||||
|
||||
public IDictionary<string, object> Parameters { get; }
|
||||
|
||||
public IMqttClient MqttClient { get; }
|
||||
|
||||
public MqttRpcClientOptions Options { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the quality of service level.
|
||||
/// The Quality of Service (QoS) level is an agreement between the sender of a message and the receiver of a message
|
||||
/// that defines the guarantee of delivery for a specific message.
|
||||
/// There are 3 QoS levels in MQTT:
|
||||
/// - At most once (0): Message gets delivered no time, once or multiple times.
|
||||
/// - At least once (1): Message gets delivered at least once (one time or more often).
|
||||
/// - Exactly once (2): Message gets delivered exactly once (It's ensured that the message only comes once).
|
||||
/// </summary>
|
||||
public MqttQualityOfServiceLevel QualityOfServiceLevel { get; }
|
||||
}
|
||||
@@ -1,53 +1,25 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<AssemblyName>MQTTnet.Extensions.TopicTemplate</AssemblyName>
|
||||
<RootNamespace>MQTTnet.Extensions.TopicTemplate</RootNamespace>
|
||||
<IsAotCompatible>true</IsAotCompatible>
|
||||
|
||||
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
||||
<Company>The contributors of MQTTnet</Company>
|
||||
<Product>MQTTnet</Product>
|
||||
<Description>
|
||||
Provides mqtt topic templating logic to support dispatch,
|
||||
routing and similar functionality based on the well known moustache syntax (AsyncAPI compatible).
|
||||
</Description>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
<Authors>The contributors of MQTTnet</Authors>
|
||||
<PackageId>MQTTnet.Extensions.TopicTemplate</PackageId>
|
||||
<SignAssembly>false</SignAssembly>
|
||||
<DelaySign>false</DelaySign>
|
||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||
<IncludeSymbols>true</IncludeSymbols>
|
||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||
<Copyright>Copyright (c) .NET Foundation and Contributors</Copyright>
|
||||
<PackageProjectUrl>https://github.com/dotnet/MQTTnet</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/dotnet/MQTTnet.git</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<PackageTags>MQTT Message Queue Telemetry MQTTClient Messaging Routing AsyncAPI Template</PackageTags>
|
||||
<NeutralLanguage>en-US</NeutralLanguage>
|
||||
<PackageIcon>nuget.png</PackageIcon>
|
||||
<EmbedUntrackedSources>true</EmbedUntrackedSources>
|
||||
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<PackageReleaseNotes>For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).</PackageReleaseNotes>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<NoWarn>1591;NETSDK1138</NoWarn>
|
||||
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="README.md">
|
||||
<Pack>True</Pack>
|
||||
<PackagePath>\</PackagePath>
|
||||
</None>
|
||||
<None Include="..\..\Images\nuget.png">
|
||||
<Pack>True</Pack>
|
||||
<PackagePath>\</PackagePath>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
|
||||
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
|
||||
<PackageReference Include="System.ValueTuple" Version="4.6.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -41,10 +41,7 @@ public sealed class MqttTopicTemplate : IEquatable<MqttTopicTemplate>
|
||||
/// </exception>
|
||||
public MqttTopicTemplate(string topicTemplate)
|
||||
{
|
||||
if (topicTemplate == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(topicTemplate));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(topicTemplate);
|
||||
|
||||
MqttTopicValidator.ThrowIfInvalidSubscribe(topicTemplate);
|
||||
|
||||
@@ -87,13 +84,13 @@ public sealed class MqttTopicTemplate : IEquatable<MqttTopicTemplate>
|
||||
{
|
||||
var filter = TopicFilter;
|
||||
// append slash if neccessary
|
||||
if (filter.Length > 0 && !filter.EndsWith(MqttTopicFilterComparer.LevelSeparator.ToString()) && !filter.EndsWith(MqttTopicFilterComparer.MultiLevelWildcard.ToString()))
|
||||
if (filter.Length > 0 && !filter.EndsWith(MqttTopicFilterComparer.LevelSeparator) && !filter.EndsWith(MqttTopicFilterComparer.MultiLevelWildcard))
|
||||
{
|
||||
filter += MqttTopicFilterComparer.LevelSeparator;
|
||||
}
|
||||
|
||||
// append hash if neccessary
|
||||
if (!filter.EndsWith(MqttTopicFilterComparer.MultiLevelWildcard.ToString()))
|
||||
if (!filter.EndsWith(MqttTopicFilterComparer.MultiLevelWildcard))
|
||||
{
|
||||
filter += MqttTopicFilterComparer.MultiLevelWildcard;
|
||||
}
|
||||
@@ -164,12 +161,12 @@ public sealed class MqttTopicTemplate : IEquatable<MqttTopicTemplate>
|
||||
return new MqttTopicTemplate(MqttTopicFilterComparer.MultiLevelWildcard.ToString());
|
||||
}
|
||||
|
||||
if (root.Contains(MqttTopicFilterComparer.LevelSeparator) && !root.EndsWith(MqttTopicFilterComparer.LevelSeparator.ToString()) && !root.EndsWith("}"))
|
||||
if (root.Contains(MqttTopicFilterComparer.LevelSeparator) && !root.EndsWith(MqttTopicFilterComparer.LevelSeparator.ToString(), StringComparison.InvariantCulture) && !root.EndsWith('}'))
|
||||
{
|
||||
root = root.Substring(0, root.LastIndexOf(MqttTopicFilterComparer.LevelSeparator) + 1);
|
||||
}
|
||||
|
||||
if (root.EndsWith(MqttTopicFilterComparer.LevelSeparator.ToString()))
|
||||
if (root.EndsWith(MqttTopicFilterComparer.LevelSeparator.ToString(), StringComparison.InvariantCulture))
|
||||
{
|
||||
root += MqttTopicFilterComparer.SingleLevelWildcard;
|
||||
}
|
||||
@@ -233,7 +230,7 @@ public sealed class MqttTopicTemplate : IEquatable<MqttTopicTemplate>
|
||||
throw new ArgumentException("the topic has to match this template", nameof(topic));
|
||||
}
|
||||
|
||||
return parseParameterValuesInternal(topic);
|
||||
return ParseParameterValuesInternal(topic);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -333,7 +330,7 @@ public sealed class MqttTopicTemplate : IEquatable<MqttTopicTemplate>
|
||||
return parameters.Aggregate(this, (t, p) => t.TrySetParameter(p.parameter, p.value));
|
||||
}
|
||||
|
||||
IEnumerable<(string parameter, int index, string value)> parseParameterValuesInternal(string topic)
|
||||
IEnumerable<(string parameter, int index, string value)> ParseParameterValuesInternal(string topic)
|
||||
{
|
||||
// because we have a match, we know the segment array is at least the template's length
|
||||
var segments = topic.Split(MqttTopicFilterComparer.LevelSeparator);
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
using System.Management.Automation;
|
||||
|
||||
namespace MQTTnet.PowerShell.Cmdlets;
|
||||
|
||||
[Cmdlet(VerbsCommunications.Connect, "MqttSession")]
|
||||
[OutputType(typeof(MqttClientConnectResult))]
|
||||
public class ConnectMqttSessionCmdlet : PSCmdlet
|
||||
{
|
||||
[Parameter]
|
||||
public SwitchParameter CleanSession { get; set; } = true;
|
||||
|
||||
[Parameter]
|
||||
public string? ClientId { get; set; } = Guid.NewGuid().ToString();
|
||||
|
||||
[Parameter(Mandatory = true, ParameterSetName = "Tcp")]
|
||||
[Parameter(Mandatory = true, ParameterSetName = "TcpWithTls")]
|
||||
public new string? Host { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string? Password { get; set; }
|
||||
|
||||
[Parameter(ParameterSetName = "Tcp")]
|
||||
[Parameter(ParameterSetName = "TcpWithTls")]
|
||||
public int Port { get; set; } = 1883;
|
||||
|
||||
[Parameter(Mandatory = true, ValueFromPipeline = true)]
|
||||
public required PsMqttSession Session { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string? Username { get; set; }
|
||||
|
||||
[Parameter(ParameterSetName = "TcpWithTls")]
|
||||
public SwitchParameter UseTls { get; set; }
|
||||
|
||||
[Parameter(ParameterSetName = "TcpWithTls")]
|
||||
public SwitchParameter AllowUntrustedCertificates { get; set; }
|
||||
|
||||
[Parameter(Mandatory = true, ParameterSetName = "WebSocket")]
|
||||
public string? WebSocketUri { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public int KeepAlivePeriod { get; set; } = 15;
|
||||
|
||||
[Parameter]
|
||||
public int Timeout { get; set; } = 10;
|
||||
|
||||
[Parameter]
|
||||
public string? WillTopic { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string? WillPayload { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public int WillQoS { get; set; } = 0;
|
||||
|
||||
[Parameter]
|
||||
public SwitchParameter WillRetain { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
var clientOptionsBuilder = new MqttClientOptionsBuilder();
|
||||
|
||||
// Configure transport
|
||||
if (ParameterSetName == "WebSocket")
|
||||
{
|
||||
clientOptionsBuilder.WithWebSocketServer(o => o.WithUri(WebSocketUri!));
|
||||
}
|
||||
else
|
||||
{
|
||||
clientOptionsBuilder.WithTcpServer(Host!, Port);
|
||||
|
||||
if (UseTls)
|
||||
{
|
||||
clientOptionsBuilder.WithTlsOptions(o =>
|
||||
{
|
||||
if (AllowUntrustedCertificates)
|
||||
{
|
||||
o.WithCertificateValidationHandler(_ => true);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Configure authentication
|
||||
if (Username != null)
|
||||
{
|
||||
clientOptionsBuilder.WithCredentials(Username, Password);
|
||||
}
|
||||
|
||||
// Configure session
|
||||
clientOptionsBuilder.WithCleanSession(CleanSession);
|
||||
|
||||
if (!string.IsNullOrEmpty(ClientId))
|
||||
{
|
||||
clientOptionsBuilder.WithClientId(ClientId);
|
||||
}
|
||||
|
||||
// Configure keep alive
|
||||
clientOptionsBuilder.WithKeepAlivePeriod(TimeSpan.FromSeconds(KeepAlivePeriod));
|
||||
clientOptionsBuilder.WithTimeout(TimeSpan.FromSeconds(Timeout));
|
||||
|
||||
// Configure Will message
|
||||
if (!string.IsNullOrEmpty(WillTopic))
|
||||
{
|
||||
clientOptionsBuilder.WithWillTopic(WillTopic);
|
||||
|
||||
if (!string.IsNullOrEmpty(WillPayload))
|
||||
{
|
||||
clientOptionsBuilder.WithWillPayload(System.Text.Encoding.UTF8.GetBytes(WillPayload));
|
||||
}
|
||||
|
||||
clientOptionsBuilder.WithWillQualityOfServiceLevel((Protocol.MqttQualityOfServiceLevel)WillQoS);
|
||||
clientOptionsBuilder.WithWillRetain(WillRetain);
|
||||
}
|
||||
|
||||
var response = Session.GetClient().ConnectAsync(clientOptionsBuilder.Build()).GetAwaiter().GetResult();
|
||||
|
||||
WriteObject(response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using System.Management.Automation;
|
||||
|
||||
namespace MQTTnet.PowerShell.Cmdlets;
|
||||
|
||||
[Cmdlet(VerbsCommunications.Disconnect, "MqttSession")]
|
||||
public class DisconnectMqttSessionCmdlet : PSCmdlet
|
||||
{
|
||||
[Parameter]
|
||||
public MqttClientDisconnectOptionsReason Reason { get; set; } = MqttClientDisconnectOptionsReason.NormalDisconnection;
|
||||
|
||||
[Parameter]
|
||||
public string? ReasonString { get; set; }
|
||||
|
||||
[Parameter(Mandatory = true, ValueFromPipeline = true)]
|
||||
public required PsMqttSession Session { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public uint SessionExpiryInterval { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
if (Session.GetClient().IsConnected)
|
||||
{
|
||||
var options = new MqttClientDisconnectOptionsBuilder().WithSessionExpiryInterval(SessionExpiryInterval).WithReason(Reason).WithReasonString(ReasonString).Build();
|
||||
|
||||
Session.GetClient().DisconnectAsync(options).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
WriteObject("Disconnected.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// 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.Management.Automation;
|
||||
|
||||
namespace MQTTnet.PowerShell.Cmdlets;
|
||||
|
||||
[Cmdlet(VerbsCommon.Get, "MqttSessionStatus")]
|
||||
[OutputType(typeof(MqttClientConnectionStatus))]
|
||||
public class GetMqttSessionStatusCmdlet : PSCmdlet
|
||||
{
|
||||
[Parameter(Mandatory = true, ValueFromPipeline = true)]
|
||||
public required PsMqttSession Session { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
var client = Session.GetClient();
|
||||
var status = new
|
||||
{
|
||||
IsConnected = client.IsConnected,
|
||||
Options = client.Options
|
||||
};
|
||||
|
||||
WriteObject(status);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using System.Management.Automation;
|
||||
|
||||
namespace MQTTnet.PowerShell.Cmdlets;
|
||||
|
||||
[Cmdlet(VerbsCommon.New, "MqttSession")]
|
||||
[OutputType(typeof(PsMqttSession))]
|
||||
public class NewMqttSessionCmdlet : PSCmdlet
|
||||
{
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
WriteObject(new PsMqttSession());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
using System.Collections;
|
||||
using System.Management.Automation;
|
||||
using System.Text;
|
||||
using MQTTnet.Protocol;
|
||||
using MQTTnet.Packets;
|
||||
|
||||
namespace MQTTnet.PowerShell.Cmdlets;
|
||||
|
||||
[Cmdlet(VerbsData.Publish, "MqttMessage")]
|
||||
public class PublishMqttMessageCmdlet : PSCmdlet
|
||||
{
|
||||
[Parameter(Mandatory = true)]
|
||||
public string? Payload { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public int QoS { get; set; } = 0;
|
||||
|
||||
[Parameter]
|
||||
public SwitchParameter Retain { get; set; }
|
||||
|
||||
[Parameter(Mandatory = true, ValueFromPipeline = true)]
|
||||
public required PsMqttSession Session { get; set; }
|
||||
|
||||
[Parameter(Mandatory = true)]
|
||||
public required string Topic { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string? ContentType { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string? ResponseTopic { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public ushort TopicAlias { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public uint MessageExpiryInterval { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Hashtable? UserProperties { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
var msgBuilder = new MqttApplicationMessageBuilder()
|
||||
.WithTopic(Topic)
|
||||
.WithPayload(Encoding.UTF8.GetBytes(Payload ?? string.Empty))
|
||||
.WithQualityOfServiceLevel((MqttQualityOfServiceLevel)QoS)
|
||||
.WithRetainFlag(Retain)
|
||||
.WithContentType(ContentType)
|
||||
.WithResponseTopic(ResponseTopic)
|
||||
.WithTopicAlias(TopicAlias)
|
||||
.WithMessageExpiryInterval(MessageExpiryInterval);
|
||||
|
||||
// Add user properties if provided
|
||||
if (UserProperties != null && UserProperties.Count > 0)
|
||||
{
|
||||
foreach (var key in UserProperties.Keys)
|
||||
{
|
||||
var value = UserProperties[key]?.ToString() ?? string.Empty;
|
||||
msgBuilder.WithUserProperty(key.ToString(), value);
|
||||
}
|
||||
}
|
||||
|
||||
var msg = msgBuilder.Build();
|
||||
var response = Session.GetClient().PublishAsync(msg).GetAwaiter().GetResult();
|
||||
|
||||
WriteObject(response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
// 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.Management.Automation;
|
||||
|
||||
namespace MQTTnet.PowerShell.Cmdlets;
|
||||
|
||||
[Cmdlet(VerbsCommunications.Receive, "MqttMessage")]
|
||||
[OutputType(typeof(PsMqttMessage))]
|
||||
public class ReceiveMqttMessageCmdlet : PSCmdlet
|
||||
{
|
||||
[Parameter(Mandatory = true, ValueFromPipeline = true)]
|
||||
public required PsMqttSession Session { get; set; }
|
||||
|
||||
[Parameter]
|
||||
[ValidateRange(0, int.MaxValue)]
|
||||
public int TimeoutSeconds { get; set; } = 0;
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
var tcs = new TaskCompletionSource<PsMqttMessage>();
|
||||
EventHandler<PsMqttMessage>? handler = null;
|
||||
CancellationTokenSource? cts = null;
|
||||
|
||||
try
|
||||
{
|
||||
handler = (s, e) =>
|
||||
{
|
||||
if (!tcs.Task.IsCompleted)
|
||||
{
|
||||
tcs.TrySetResult(e);
|
||||
}
|
||||
};
|
||||
|
||||
Session.MessageReceived += handler;
|
||||
|
||||
// Optionaler Timeout
|
||||
if (TimeoutSeconds > 0)
|
||||
{
|
||||
cts = new CancellationTokenSource();
|
||||
cts.CancelAfter(TimeSpan.FromSeconds(TimeoutSeconds));
|
||||
cts.Token.Register(() =>
|
||||
{
|
||||
if (!tcs.Task.IsCompleted)
|
||||
{
|
||||
tcs.TrySetCanceled();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
WriteVerbose("Waiting for next MQTT message...");
|
||||
|
||||
try
|
||||
{
|
||||
var message = tcs.Task.GetAwaiter().GetResult();
|
||||
WriteObject(message);
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
WriteWarning($"Timeout after {TimeoutSeconds} seconds — no message received.");
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (handler != null)
|
||||
{
|
||||
Session.MessageReceived -= handler;
|
||||
}
|
||||
|
||||
cts?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
using System.Management.Automation;
|
||||
using System.Management.Automation.Runspaces;
|
||||
|
||||
namespace MQTTnet.PowerShell.Cmdlets;
|
||||
|
||||
[Cmdlet(VerbsLifecycle.Register, "MqttMessageHandler")]
|
||||
public class RegisterMqttMessageHandlerCmdlet : PSCmdlet
|
||||
{
|
||||
[Parameter(Mandatory = true, ValueFromPipeline = true)]
|
||||
public required PsMqttSession Session { get; set; }
|
||||
|
||||
[Parameter(Mandatory = true)]
|
||||
public ScriptBlock? Action { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
// Capture the ScriptBlock and current default runspace (available during cmdlet execution)
|
||||
var scriptBlock = Action ?? throw new ArgumentNullException(nameof(Action));
|
||||
var capturedRunspace = Runspace.DefaultRunspace ?? throw new InvalidOperationException("No default runspace available during cmdlet execution.");
|
||||
var capturedHost = Host;
|
||||
|
||||
EventHandler<PsMqttMessage> handler = (sender, e) =>
|
||||
{
|
||||
// Run asynchronously to avoid blocking the event thread
|
||||
Task.Run(() =>
|
||||
{
|
||||
// Temporarily set the default runspace for this thread
|
||||
var originalRunspace = Runspace.DefaultRunspace;
|
||||
Runspace.DefaultRunspace = capturedRunspace;
|
||||
|
||||
try
|
||||
{
|
||||
// Invoke the ScriptBlock directly with arguments (returns Collection<PSObject>)
|
||||
var results = scriptBlock.Invoke(e.Topic, e.Payload, e); // Pass topic, payload, and full message
|
||||
|
||||
// Handle output manually (e.g., write to host/console)
|
||||
foreach (var result in results)
|
||||
{
|
||||
capturedHost.UI.WriteLine(result?.ToString() ?? string.Empty);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Handle errors by writing to host error stream
|
||||
capturedHost.UI.WriteErrorLine($"MQTT handler error: {ex.Message}");
|
||||
// Optionally: capturedHost.UI.WriteVerboseLine(ex.StackTrace); for more details
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Restore the original default runspace for this thread
|
||||
Runspace.DefaultRunspace = originalRunspace;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
Session.MessageReceived += handler;
|
||||
WriteObject($"Handler registered for MQTT messages on session.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using System.Management.Automation;
|
||||
|
||||
namespace MQTTnet.PowerShell.Cmdlets;
|
||||
|
||||
[Cmdlet(VerbsCommon.Remove, "MqttSession")]
|
||||
public class RemoveMqttSessionCmdlet : PSCmdlet
|
||||
{
|
||||
[Parameter(Mandatory = true, ValueFromPipeline = true)]
|
||||
public required PsMqttSession Session { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
Session.Dispose();
|
||||
WriteObject("Session removed.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
// 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.Management.Automation;
|
||||
|
||||
namespace MQTTnet.PowerShell.Cmdlets;
|
||||
|
||||
[Cmdlet(VerbsCommon.Show, "MqttExamples")]
|
||||
public class ShowMqttExamplesCmdlet : PSCmdlet
|
||||
{
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
var examples = @"
|
||||
MQTTnet PowerShell Module - Quick Examples
|
||||
==========================================
|
||||
|
||||
1. Basic Connection and Publish:
|
||||
$session = New-MqttSession
|
||||
Connect-MqttSession -Session $session -Host 'broker.hivemq.com'
|
||||
Publish-MqttMessage -Session $session -Topic 'test/topic' -Payload 'Hello'
|
||||
Disconnect-MqttSession -Session $session
|
||||
Remove-MqttSession -Session $session
|
||||
|
||||
2. Subscribe and Receive Messages:
|
||||
$session = New-MqttSession
|
||||
Connect-MqttSession -Session $session -Host 'broker.hivemq.com'
|
||||
Subscribe-MqttTopic -Session $session -Topic 'test/demo'
|
||||
$msg = Receive-MqttMessage -Session $session -TimeoutSeconds 30
|
||||
Write-Host ""Received: $($msg.Payload)""
|
||||
|
||||
3. WebSocket Connection:
|
||||
$session = New-MqttSession
|
||||
Connect-MqttSession -Session $session -WebSocketUri 'ws://broker.hivemq.com:8000/mqtt'
|
||||
Publish-MqttMessage -Session $session -Topic 'test/ws' -Payload 'Hello WS'
|
||||
|
||||
4. TLS/SSL Connection:
|
||||
$session = New-MqttSession
|
||||
Connect-MqttSession -Session $session -Host 'broker.hivemq.com' -Port 8883 `
|
||||
-UseTls -AllowUntrustedCertificates
|
||||
|
||||
5. Async Message Handler:
|
||||
$session = New-MqttSession
|
||||
Connect-MqttSession -Session $session -Host 'broker.hivemq.com'
|
||||
Subscribe-MqttTopic -Session $session -Topic 'test/#'
|
||||
Register-MqttMessageHandler -Session $session -Action {
|
||||
param($topic, $payload, $message)
|
||||
Write-Host ""Message on $topic : $payload""
|
||||
}
|
||||
|
||||
6. Last Will and Testament:
|
||||
$session = New-MqttSession
|
||||
Connect-MqttSession -Session $session -Host 'broker.hivemq.com' `
|
||||
-WillTopic 'status/client' -WillPayload 'Offline' -WillQoS 1 -WillRetain
|
||||
|
||||
7. Publish with User Properties:
|
||||
$props = @{ 'source' = 'PowerShell'; 'version' = '1.0' }
|
||||
Publish-MqttMessage -Session $session -Topic 'test/props' `
|
||||
-Payload 'Data' -UserProperties $props
|
||||
|
||||
8. High QoS Publishing:
|
||||
Publish-MqttMessage -Session $session -Topic 'critical/data' `
|
||||
-Payload 'Important' -QoS 2
|
||||
|
||||
9. Test Connection:
|
||||
$isAlive = Test-MqttConnection -Session $session
|
||||
Write-Host ""Connection alive: $isAlive""
|
||||
|
||||
10. Get Session Status:
|
||||
$status = Get-MqttSessionStatus -Session $session
|
||||
Write-Host ""Connected: $($status.IsConnected)""
|
||||
|
||||
For more details, see: Source/MQTTnet.PowerShell/README.md
|
||||
";
|
||||
|
||||
WriteObject(examples);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
// 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.Management.Automation;
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
namespace MQTTnet.PowerShell.Cmdlets;
|
||||
|
||||
[Cmdlet("Subscribe", "MqttTopic")]
|
||||
[OutputType(typeof(MqttClientSubscribeResult))]
|
||||
public class SubscribeMqttTopicCmdlet : PSCmdlet
|
||||
{
|
||||
[Parameter]
|
||||
public bool NoLocal { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public int QoS { get; set; } = 0;
|
||||
|
||||
[Parameter]
|
||||
public bool RetainAsPublished { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public MqttRetainHandling RetainHandling { get; set; } = MqttRetainHandling.SendAtSubscribe;
|
||||
|
||||
[Parameter(Mandatory = true, ValueFromPipeline = true)]
|
||||
public required PsMqttSession Session { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public uint SubscriptionIdentifier { get; set; }
|
||||
|
||||
[Parameter(Mandatory = true)]
|
||||
public required string Topic { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
var topicFilter = new MqttTopicFilterBuilder().WithTopic(Topic)
|
||||
.WithQualityOfServiceLevel((MqttQualityOfServiceLevel)QoS)
|
||||
.WithNoLocal(NoLocal)
|
||||
.WithRetainAsPublished(RetainAsPublished)
|
||||
.WithRetainHandling(RetainHandling)
|
||||
.Build();
|
||||
|
||||
var options = new MqttClientSubscribeOptionsBuilder().WithTopicFilter(topicFilter);
|
||||
|
||||
if (SubscriptionIdentifier > 0)
|
||||
{
|
||||
options.WithSubscriptionIdentifier(SubscriptionIdentifier);
|
||||
}
|
||||
|
||||
var response = Session.GetClient().SubscribeAsync(options.Build()).GetAwaiter().GetResult();
|
||||
|
||||
WriteObject(response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
// 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.Management.Automation;
|
||||
|
||||
namespace MQTTnet.PowerShell.Cmdlets;
|
||||
|
||||
[Cmdlet(VerbsDiagnostic.Test, "MqttConnection")]
|
||||
[OutputType(typeof(bool))]
|
||||
public class TestMqttConnectionCmdlet : PSCmdlet
|
||||
{
|
||||
[Parameter(Mandatory = true, ValueFromPipeline = true)]
|
||||
public required PsMqttSession Session { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public int TimeoutSeconds { get; set; } = 5;
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
try
|
||||
{
|
||||
var client = Session.GetClient();
|
||||
|
||||
if (!client.IsConnected)
|
||||
{
|
||||
WriteObject(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(TimeoutSeconds));
|
||||
client.PingAsync(cts.Token).GetAwaiter().GetResult();
|
||||
|
||||
WriteObject(true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
WriteObject(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// 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.Management.Automation;
|
||||
|
||||
namespace MQTTnet.PowerShell.Cmdlets;
|
||||
|
||||
[Cmdlet(VerbsLifecycle.Unregister, "MqttMessageHandler")]
|
||||
public class UnregisterMqttMessageHandlerCmdlet : PSCmdlet
|
||||
{
|
||||
[Parameter(Mandatory = true, ValueFromPipeline = true)]
|
||||
public required PsMqttSession Session { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
// Clear all event handlers
|
||||
Session.ClearMessageHandlers();
|
||||
WriteObject($"All message handlers cleared for MQTT session.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using System.Management.Automation;
|
||||
|
||||
namespace MQTTnet.PowerShell.Cmdlets;
|
||||
|
||||
[Cmdlet("Unsubscribe", "MqttTopic")]
|
||||
[OutputType(typeof(MqttClientUnsubscribeResult))]
|
||||
public class UnsubscribeMqttTopicCmdlet : PSCmdlet
|
||||
{
|
||||
[Parameter(Mandatory = true, ValueFromPipeline = true)]
|
||||
public required PsMqttSession Session { get; set; }
|
||||
|
||||
[Parameter(Mandatory = true)]
|
||||
public required string Topic { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
var options = new MqttClientUnsubscribeOptionsBuilder().WithTopicFilter(Topic).Build();
|
||||
|
||||
var response = Session.GetClient().UnsubscribeAsync(options).GetAwaiter().GetResult();
|
||||
|
||||
WriteObject(response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
# PowerShell Module Extension - Implementation Summary
|
||||
|
||||
## Overview
|
||||
This implementation extends the PowerShell support for the MQTTnet library as requested in issue #2215, implementing the proposal described in the ticket comments and exposing additional features of the library as PowerShell cmdlets.
|
||||
|
||||
## Changes Implemented
|
||||
|
||||
### 1. Enhanced RegisterMqttMessageHandler (Primary Requirement from #2215)
|
||||
**File**: `Source/MQTTnet.PowerShell/Cmdlets/RegisterMqttMessageHandlerCmdlet.cs`
|
||||
|
||||
Implemented the proposal from the issue comment by @ArakniD:
|
||||
- Proper ScriptBlock execution in the correct runspace
|
||||
- Asynchronous message handling using Task.Run to avoid blocking
|
||||
- Passes three parameters to the handler: topic (string), payload (string), and full message object
|
||||
- Proper error handling with try-catch and error output to host
|
||||
- Captures the runspace during cmdlet execution and restores it during handler execution
|
||||
|
||||
**Usage Example:**
|
||||
```powershell
|
||||
Register-MqttMessageHandler -Session $session -Action {
|
||||
param($topic, $payload, $message)
|
||||
Write-Host "Received on $topic: $payload"
|
||||
# Can access $message.QoS, $message.Retain, etc.
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Enhanced Connection Options
|
||||
**File**: `Source/MQTTnet.PowerShell/Cmdlets/ConnectMqttSessionCmdlet.cs`
|
||||
|
||||
Extended Connect-MqttSession with:
|
||||
- **WebSocket Support**: New `-WebSocketUri` parameter for WebSocket connections
|
||||
- **TLS/SSL Support**: New `-UseTls` and `-AllowUntrustedCertificates` parameters
|
||||
- **Last Will and Testament**: Parameters for `-WillTopic`, `-WillPayload`, `-WillQoS`, `-WillRetain`
|
||||
- **Connection Tuning**: Parameters for `-KeepAlivePeriod` and `-Timeout`
|
||||
- **Parameter Sets**: Organized into TCP, TcpWithTls, and WebSocket parameter sets
|
||||
|
||||
**Usage Examples:**
|
||||
```powershell
|
||||
# TCP with TLS
|
||||
Connect-MqttSession -Session $session -Host "broker.com" -Port 8883 -UseTls
|
||||
|
||||
# WebSocket
|
||||
Connect-MqttSession -Session $session -WebSocketUri "ws://broker.com:8000/mqtt"
|
||||
|
||||
# With Last Will
|
||||
Connect-MqttSession -Session $session -Host "broker.com" `
|
||||
-WillTopic "status" -WillPayload "Offline" -WillRetain
|
||||
```
|
||||
|
||||
### 3. Enhanced Publishing
|
||||
**File**: `Source/MQTTnet.PowerShell/Cmdlets/PublishMqttMessageCmdlet.cs`
|
||||
|
||||
Added support for:
|
||||
- **User Properties**: New `-UserProperties` parameter accepting a Hashtable
|
||||
- MQTT 5 features already supported: ContentType, ResponseTopic, TopicAlias, MessageExpiryInterval
|
||||
|
||||
**Usage Example:**
|
||||
```powershell
|
||||
$props = @{ "source" = "PowerShell"; "version" = "1.0" }
|
||||
Publish-MqttMessage -Session $session -Topic "data" -Payload "test" -UserProperties $props
|
||||
```
|
||||
|
||||
### 4. New Utility Cmdlets
|
||||
|
||||
#### Get-MqttSessionStatus
|
||||
**File**: `Source/MQTTnet.PowerShell/Cmdlets/GetMqttSessionStatusCmdlet.cs`
|
||||
|
||||
Returns connection status information.
|
||||
|
||||
```powershell
|
||||
$status = Get-MqttSessionStatus -Session $session
|
||||
Write-Host "Connected: $($status.IsConnected)"
|
||||
```
|
||||
|
||||
#### Test-MqttConnection
|
||||
**File**: `Source/MQTTnet.PowerShell/Cmdlets/TestMqttConnectionCmdlet.cs`
|
||||
|
||||
Pings the broker to verify connectivity.
|
||||
|
||||
```powershell
|
||||
$isAlive = Test-MqttConnection -Session $session -TimeoutSeconds 5
|
||||
```
|
||||
|
||||
#### Unregister-MqttMessageHandler
|
||||
**File**: `Source/MQTTnet.PowerShell/Cmdlets/UnregisterMqttMessageHandlerCmdlet.cs`
|
||||
|
||||
Clears all registered message handlers.
|
||||
|
||||
```powershell
|
||||
Unregister-MqttMessageHandler -Session $session
|
||||
```
|
||||
|
||||
#### Show-MqttExamples
|
||||
**File**: `Source/MQTTnet.PowerShell/Cmdlets/ShowMqttExamplesCmdlet.cs`
|
||||
|
||||
Displays quick reference examples for common MQTT tasks.
|
||||
|
||||
```powershell
|
||||
Show-MqttExamples
|
||||
```
|
||||
|
||||
### 5. Enhanced PsMqttSession Class
|
||||
**File**: `Source/MQTTnet.PowerShell/PsMqttSession.cs`
|
||||
|
||||
Added:
|
||||
- `ClearMessageHandlers()` method to remove all event handlers
|
||||
- Improved event handling infrastructure
|
||||
|
||||
### 6. Documentation
|
||||
**Files**:
|
||||
- `Source/MQTTnet.PowerShell/README.md` (new)
|
||||
- `Source/MQTTnet.PowerShell/Test.ps1` (updated)
|
||||
- `Source/MQTTnet.PowerShell/SmokeTest.ps1` (new)
|
||||
|
||||
Created comprehensive documentation including:
|
||||
- Quick start guide
|
||||
- Detailed cmdlet documentation
|
||||
- Parameter descriptions
|
||||
- Usage examples for all features
|
||||
- Advanced scenarios (request-response, error handling)
|
||||
- Tips and best practices
|
||||
- Smoke tests for validation
|
||||
|
||||
## Complete Cmdlet List (13 Total)
|
||||
|
||||
1. **New-MqttSession** - Create MQTT session
|
||||
2. **Connect-MqttSession** - Connect with TCP/WebSocket/TLS support
|
||||
3. **Disconnect-MqttSession** - Clean disconnect
|
||||
4. **Remove-MqttSession** - Cleanup resources
|
||||
5. **Get-MqttSessionStatus** - Check connection status
|
||||
6. **Test-MqttConnection** - Ping broker
|
||||
7. **Publish-MqttMessage** - Publish with advanced features
|
||||
8. **Subscribe-MqttTopic** - Subscribe to topics
|
||||
9. **Unsubscribe-MqttTopic** - Unsubscribe from topics
|
||||
10. **Receive-MqttMessage** - Synchronous message receive
|
||||
11. **Register-MqttMessageHandler** - Async message handling ⭐ (Primary requirement)
|
||||
12. **Unregister-MqttMessageHandler** - Clear handlers
|
||||
13. **Show-MqttExamples** - Quick reference examples
|
||||
|
||||
## Testing
|
||||
|
||||
### Build Status
|
||||
✅ PowerShell module builds successfully
|
||||
✅ No compilation errors or warnings
|
||||
✅ Code passes CodeQL security analysis (0 alerts)
|
||||
|
||||
### Smoke Tests
|
||||
✅ All 13 cmdlets load correctly
|
||||
✅ Session creation and disposal works
|
||||
✅ Show-MqttExamples returns help text
|
||||
✅ No runtime errors during basic operations
|
||||
|
||||
### Test Files
|
||||
- `SmokeTest.ps1`: Quick validation of module loading and basic operations
|
||||
- `Test.ps1`: Comprehensive examples demonstrating all features
|
||||
|
||||
## Features Not Implemented (Out of Scope)
|
||||
|
||||
The following were considered but determined to be out of scope for this task:
|
||||
- MQTT broker/server cmdlets (only client features were requested)
|
||||
- Managed MQTT client wrapper (existing client is sufficient)
|
||||
- RPC extension support (specialized use case)
|
||||
- File-based certificate handling cmdlets (can be done with existing .NET capabilities)
|
||||
|
||||
## Compatibility
|
||||
|
||||
- Requires .NET 8.0 or later
|
||||
- Requires PowerShell 7.0 or later
|
||||
- Works on Windows, Linux, and macOS
|
||||
- Compatible with MQTT 3.1.1 and MQTT 5.0 protocols
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
None. This is a new feature addition that doesn't modify existing code outside the PowerShell module.
|
||||
|
||||
## Migration Guide
|
||||
|
||||
For users who were using the initial implementation from PR #2215:
|
||||
- The RegisterMqttMessageHandler now actually works! Update your scripts to use the handler.
|
||||
- Connect-MqttSession now requires explicit parameter sets (-Host for TCP, -WebSocketUri for WebSocket)
|
||||
- No other breaking changes
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
Potential future improvements that could be added:
|
||||
1. Pipeline support for batch publishing
|
||||
2. Managed client wrapper for automatic reconnection
|
||||
3. Certificate-based authentication helpers
|
||||
4. MQTT broker cmdlets for running local brokers
|
||||
5. Performance counters and statistics cmdlets
|
||||
6. Async/await support for better PowerShell integration
|
||||
|
||||
## References
|
||||
|
||||
- Issue #2215: https://github.com/dotnet/MQTTnet/pull/2215
|
||||
- Proposal comment by @ArakniD: https://github.com/dotnet/MQTTnet/pull/2215#issuecomment-3594492551
|
||||
- MQTTnet Documentation: https://github.com/dotnet/MQTTnet
|
||||
@@ -0,0 +1,19 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<AssemblyName>MQTTnet.PowerShell</AssemblyName>
|
||||
<LangVersion>default</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="PowerShellStandard.Library" Version="5.1.1">
|
||||
<PrivateAssets>All</PrivateAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MQTTnet\MQTTnet.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +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 System.Management.Automation;
|
||||
using MQTTnet.Packets;
|
||||
|
||||
namespace MQTTnet.PowerShell;
|
||||
|
||||
public class PsMqttPublishResult
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public class PsMqttConnectResult
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public class PsMqttMessage
|
||||
{
|
||||
public required string? ContentType { get; init; }
|
||||
public required string Payload { get; init; }
|
||||
public int QoS { get; init; }
|
||||
public required byte[] RawPayload { get; init; }
|
||||
public string? ResponseTopic { get; init; }
|
||||
public bool Retain { get; init; }
|
||||
public required string Topic { get; init; }
|
||||
public required List<MqttUserProperty>? UserProperties { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
// 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;
|
||||
using System.Text;
|
||||
|
||||
namespace MQTTnet.PowerShell;
|
||||
|
||||
public sealed class PsMqttSession : IDisposable
|
||||
{
|
||||
readonly IMqttClient _client;
|
||||
|
||||
public PsMqttSession()
|
||||
{
|
||||
var factory = new MqttClientFactory();
|
||||
_client = factory.CreateMqttClient();
|
||||
|
||||
_client.ApplicationMessageReceivedAsync += e =>
|
||||
{
|
||||
MessageReceived?.Invoke(
|
||||
this,
|
||||
new PsMqttMessage
|
||||
{
|
||||
Topic = e.ApplicationMessage.Topic,
|
||||
Payload = Encoding.UTF8.GetString(e.ApplicationMessage.Payload),
|
||||
RawPayload = e.ApplicationMessage.Payload.ToArray(),
|
||||
UserProperties = e.ApplicationMessage.UserProperties,
|
||||
QoS = (int)e.ApplicationMessage.QualityOfServiceLevel,
|
||||
ContentType = e.ApplicationMessage.ContentType,
|
||||
Retain = e.ApplicationMessage.Retain,
|
||||
ResponseTopic = e.ApplicationMessage.ResponseTopic
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
}
|
||||
|
||||
public event EventHandler<PsMqttMessage>? MessageReceived;
|
||||
|
||||
public void ClearMessageHandlers()
|
||||
{
|
||||
// Remove all event subscribers
|
||||
if (MessageReceived != null)
|
||||
{
|
||||
foreach (var handler in MessageReceived.GetInvocationList())
|
||||
{
|
||||
MessageReceived -= (EventHandler<PsMqttMessage>)handler;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_client.Dispose();
|
||||
}
|
||||
|
||||
public IMqttClient GetClient()
|
||||
{
|
||||
return _client;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,396 @@
|
||||
# MQTTnet PowerShell Module
|
||||
|
||||
A PowerShell module for MQTTnet that provides cmdlets for MQTT communication in PowerShell.
|
||||
|
||||
## Features
|
||||
|
||||
- **Full MQTT Protocol Support**: Connect, subscribe, publish, and manage MQTT connections
|
||||
- **TCP and WebSocket Transports**: Support for both TCP and WebSocket connections
|
||||
- **TLS/SSL Support**: Secure connections with TLS encryption
|
||||
- **Async Message Handling**: Register PowerShell ScriptBlocks as message handlers
|
||||
- **Advanced MQTT Features**:
|
||||
- Quality of Service (QoS) levels 0, 1, and 2
|
||||
- Retained messages
|
||||
- Last Will and Testament (LWT)
|
||||
- User properties
|
||||
- Message expiry intervals
|
||||
- Response topics
|
||||
|
||||
## Installation
|
||||
|
||||
1. Build the MQTTnet.PowerShell project:
|
||||
```powershell
|
||||
dotnet build Source/MQTTnet.PowerShell/MQTTnet.PowerShell.csproj
|
||||
```
|
||||
|
||||
2. Import the module:
|
||||
```powershell
|
||||
Import-Module ./Source/MQTTnet.PowerShell/bin/Debug/net8.0/MQTTnet.PowerShell.dll
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Basic Connection and Publish
|
||||
|
||||
```powershell
|
||||
# Create a new MQTT session
|
||||
$session = New-MqttSession
|
||||
|
||||
# Connect to a broker
|
||||
Connect-MqttSession -Session $session -Host "broker.hivemq.com" -Port 1883
|
||||
|
||||
# Publish a message
|
||||
Publish-MqttMessage -Session $session -Topic "test/topic" -Payload "Hello MQTT!"
|
||||
|
||||
# Disconnect and cleanup
|
||||
Disconnect-MqttSession -Session $session
|
||||
Remove-MqttSession -Session $session
|
||||
```
|
||||
|
||||
### Subscribe and Receive Messages
|
||||
|
||||
```powershell
|
||||
$session = New-MqttSession
|
||||
Connect-MqttSession -Session $session -Host "broker.hivemq.com"
|
||||
|
||||
# Subscribe to a topic
|
||||
Subscribe-MqttTopic -Session $session -Topic "test/demo"
|
||||
|
||||
# Wait for a message (with timeout)
|
||||
$message = Receive-MqttMessage -Session $session -TimeoutSeconds 30
|
||||
Write-Host "Received: $($message.Payload)"
|
||||
|
||||
Disconnect-MqttSession -Session $session
|
||||
Remove-MqttSession -Session $session
|
||||
```
|
||||
|
||||
### Async Message Handler
|
||||
|
||||
```powershell
|
||||
$session = New-MqttSession
|
||||
Connect-MqttSession -Session $session -Host "broker.hivemq.com"
|
||||
|
||||
Subscribe-MqttTopic -Session $session -Topic "test/#"
|
||||
|
||||
# Register a message handler
|
||||
Register-MqttMessageHandler -Session $session -Action {
|
||||
param($topic, $payload, $message)
|
||||
Write-Host "Message on $topic : $payload"
|
||||
Write-Host "QoS: $($message.QoS), Retain: $($message.Retain)"
|
||||
}
|
||||
|
||||
# Messages will be handled asynchronously
|
||||
Start-Sleep -Seconds 60
|
||||
|
||||
Disconnect-MqttSession -Session $session
|
||||
Remove-MqttSession -Session $session
|
||||
```
|
||||
|
||||
## Available Cmdlets
|
||||
|
||||
### Help and Examples
|
||||
|
||||
#### Show-MqttExamples
|
||||
Displays quick examples for common MQTT tasks.
|
||||
|
||||
```powershell
|
||||
Show-MqttExamples
|
||||
```
|
||||
|
||||
### Session Management
|
||||
|
||||
#### New-MqttSession
|
||||
Creates a new MQTT session.
|
||||
|
||||
```powershell
|
||||
$session = New-MqttSession
|
||||
```
|
||||
|
||||
#### Connect-MqttSession
|
||||
Connects to an MQTT broker with various options.
|
||||
|
||||
**TCP Connection:**
|
||||
```powershell
|
||||
Connect-MqttSession -Session $session -Host "broker.hivemq.com" -Port 1883
|
||||
```
|
||||
|
||||
**TCP with TLS:**
|
||||
```powershell
|
||||
Connect-MqttSession -Session $session -Host "broker.hivemq.com" -Port 8883 `
|
||||
-UseTls -AllowUntrustedCertificates
|
||||
```
|
||||
|
||||
**WebSocket Connection:**
|
||||
```powershell
|
||||
Connect-MqttSession -Session $session -WebSocketUri "ws://broker.hivemq.com:8000/mqtt"
|
||||
```
|
||||
|
||||
**With Authentication:**
|
||||
```powershell
|
||||
Connect-MqttSession -Session $session -Host "broker.hivemq.com" `
|
||||
-Username "user" -Password "pass"
|
||||
```
|
||||
|
||||
**With Last Will and Testament:**
|
||||
```powershell
|
||||
Connect-MqttSession -Session $session -Host "broker.hivemq.com" `
|
||||
-WillTopic "status/client" -WillPayload "Offline" -WillQoS 1 -WillRetain
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `Session`: The MQTT session (mandatory)
|
||||
- `Host`: Broker hostname (for TCP)
|
||||
- `Port`: Broker port (default: 1883)
|
||||
- `WebSocketUri`: WebSocket URI (for WebSocket connections)
|
||||
- `Username`: Username for authentication
|
||||
- `Password`: Password for authentication
|
||||
- `ClientId`: Client identifier (auto-generated if not specified)
|
||||
- `CleanSession`: Use clean session (default: true)
|
||||
- `UseTls`: Enable TLS encryption
|
||||
- `AllowUntrustedCertificates`: Allow untrusted certificates
|
||||
- `KeepAlivePeriod`: Keep-alive period in seconds (default: 15)
|
||||
- `Timeout`: Connection timeout in seconds (default: 10)
|
||||
- `WillTopic`: Last Will topic
|
||||
- `WillPayload`: Last Will payload
|
||||
- `WillQoS`: Last Will QoS level (0-2)
|
||||
- `WillRetain`: Retain Last Will message
|
||||
|
||||
#### Get-MqttSessionStatus
|
||||
Gets the current status of the MQTT session.
|
||||
|
||||
```powershell
|
||||
$status = Get-MqttSessionStatus -Session $session
|
||||
Write-Host "Connected: $($status.IsConnected)"
|
||||
```
|
||||
|
||||
#### Disconnect-MqttSession
|
||||
Disconnects from the MQTT broker.
|
||||
|
||||
```powershell
|
||||
Disconnect-MqttSession -Session $session
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `Session`: The MQTT session (mandatory)
|
||||
- `Reason`: Disconnect reason code
|
||||
- `ReasonString`: Human-readable reason
|
||||
- `SessionExpiryInterval`: Session expiry interval
|
||||
|
||||
#### Remove-MqttSession
|
||||
Disposes the MQTT session and releases resources.
|
||||
|
||||
```powershell
|
||||
Remove-MqttSession -Session $session
|
||||
```
|
||||
|
||||
#### Test-MqttConnection
|
||||
Tests if the MQTT connection is alive by sending a PING packet.
|
||||
|
||||
```powershell
|
||||
$isAlive = Test-MqttConnection -Session $session -TimeoutSeconds 5
|
||||
Write-Host "Connection is alive: $isAlive"
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `Session`: The MQTT session (mandatory)
|
||||
- `TimeoutSeconds`: Timeout for the ping operation (default: 5)
|
||||
|
||||
### Publishing
|
||||
|
||||
#### Publish-MqttMessage
|
||||
Publishes a message to a topic.
|
||||
|
||||
**Basic:**
|
||||
```powershell
|
||||
Publish-MqttMessage -Session $session -Topic "test/topic" -Payload "Hello"
|
||||
```
|
||||
|
||||
**With QoS and Retain:**
|
||||
```powershell
|
||||
Publish-MqttMessage -Session $session -Topic "test/topic" -Payload "Hello" `
|
||||
-QoS 1 -Retain
|
||||
```
|
||||
|
||||
**With User Properties:**
|
||||
```powershell
|
||||
$props = @{
|
||||
"source" = "PowerShell"
|
||||
"version" = "1.0"
|
||||
}
|
||||
Publish-MqttMessage -Session $session -Topic "test/topic" -Payload "Hello" `
|
||||
-UserProperties $props
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `Session`: The MQTT session (mandatory)
|
||||
- `Topic`: Topic to publish to (mandatory)
|
||||
- `Payload`: Message payload (mandatory)
|
||||
- `QoS`: Quality of Service level (0-2, default: 0)
|
||||
- `Retain`: Retain flag
|
||||
- `ContentType`: Content type of the payload
|
||||
- `ResponseTopic`: Response topic for request/response pattern
|
||||
- `UserProperties`: Hashtable of user properties
|
||||
- `TopicAlias`: Topic alias
|
||||
- `MessageExpiryInterval`: Message expiry interval in seconds
|
||||
|
||||
### Subscribing
|
||||
|
||||
#### Subscribe-MqttTopic
|
||||
Subscribes to a topic.
|
||||
|
||||
```powershell
|
||||
Subscribe-MqttTopic -Session $session -Topic "test/demo" -QoS 1
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `Session`: The MQTT session (mandatory)
|
||||
- `Topic`: Topic filter to subscribe to (mandatory)
|
||||
- `QoS`: Quality of Service level (0-2, default: 0)
|
||||
- `NoLocal`: No local flag
|
||||
- `RetainAsPublished`: Retain as published flag
|
||||
- `RetainHandling`: Retain handling option
|
||||
- `SubscriptionIdentifier`: Subscription identifier
|
||||
|
||||
#### Unsubscribe-MqttTopic
|
||||
Unsubscribes from a topic.
|
||||
|
||||
```powershell
|
||||
Unsubscribe-MqttTopic -Session $session -Topic "test/demo"
|
||||
```
|
||||
|
||||
### Message Handling
|
||||
|
||||
#### Receive-MqttMessage
|
||||
Waits for and receives a single message.
|
||||
|
||||
```powershell
|
||||
# Wait indefinitely
|
||||
$message = Receive-MqttMessage -Session $session
|
||||
|
||||
# Wait with timeout
|
||||
$message = Receive-MqttMessage -Session $session -TimeoutSeconds 30
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `Session`: The MQTT session (mandatory)
|
||||
- `TimeoutSeconds`: Timeout in seconds (0 = no timeout)
|
||||
|
||||
#### Register-MqttMessageHandler
|
||||
Registers an asynchronous message handler using a PowerShell ScriptBlock.
|
||||
|
||||
```powershell
|
||||
Register-MqttMessageHandler -Session $session -Action {
|
||||
param($topic, $payload, $message)
|
||||
|
||||
Write-Host "Topic: $topic"
|
||||
Write-Host "Payload: $payload"
|
||||
Write-Host "QoS: $($message.QoS)"
|
||||
|
||||
# Access full message properties
|
||||
if ($message.UserProperties) {
|
||||
foreach ($prop in $message.UserProperties) {
|
||||
Write-Host "Property: $($prop.Name) = $($prop.Value)"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The ScriptBlock receives three parameters:
|
||||
1. `$topic`: The message topic (string)
|
||||
2. `$payload`: The message payload as UTF-8 string
|
||||
3. `$message`: The full message object with properties:
|
||||
- `Topic`: Message topic
|
||||
- `Payload`: Payload as string
|
||||
- `RawPayload`: Payload as byte array
|
||||
- `QoS`: Quality of Service level
|
||||
- `Retain`: Retain flag
|
||||
- `ContentType`: Content type
|
||||
- `ResponseTopic`: Response topic
|
||||
- `UserProperties`: List of user properties
|
||||
|
||||
**Parameters:**
|
||||
- `Session`: The MQTT session (mandatory)
|
||||
- `Action`: ScriptBlock to execute for each message (mandatory)
|
||||
|
||||
#### Unregister-MqttMessageHandler
|
||||
Removes all registered message handlers.
|
||||
|
||||
```powershell
|
||||
Unregister-MqttMessageHandler -Session $session
|
||||
```
|
||||
|
||||
## Advanced Examples
|
||||
|
||||
### Request-Response Pattern
|
||||
|
||||
```powershell
|
||||
$session = New-MqttSession
|
||||
Connect-MqttSession -Session $session -Host "broker.hivemq.com"
|
||||
|
||||
# Responder
|
||||
Subscribe-MqttTopic -Session $session -Topic "request/topic"
|
||||
Register-MqttMessageHandler -Session $session -Action {
|
||||
param($topic, $payload, $message)
|
||||
if ($message.ResponseTopic) {
|
||||
$response = "Response to: $payload"
|
||||
Publish-MqttMessage -Session $session -Topic $message.ResponseTopic `
|
||||
-Payload $response
|
||||
}
|
||||
}
|
||||
|
||||
# Requestor (in another session)
|
||||
$responseSession = New-MqttSession
|
||||
Connect-MqttSession -Session $responseSession -Host "broker.hivemq.com"
|
||||
Subscribe-MqttTopic -Session $responseSession -Topic "response/topic"
|
||||
Publish-MqttMessage -Session $responseSession -Topic "request/topic" `
|
||||
-Payload "Request data" -ResponseTopic "response/topic"
|
||||
|
||||
# Wait for response
|
||||
$response = Receive-MqttMessage -Session $responseSession -TimeoutSeconds 10
|
||||
```
|
||||
|
||||
### High QoS Publishing
|
||||
|
||||
```powershell
|
||||
# Publish with QoS 2 (exactly once delivery)
|
||||
$result = Publish-MqttMessage -Session $session -Topic "critical/data" `
|
||||
-Payload "Important data" -QoS 2
|
||||
|
||||
Write-Host "Publish result: $($result.ReasonCode)"
|
||||
```
|
||||
|
||||
### Error Handling in Message Handler
|
||||
|
||||
```powershell
|
||||
Register-MqttMessageHandler -Session $session -Action {
|
||||
param($topic, $payload, $message)
|
||||
try {
|
||||
# Process message
|
||||
$data = ConvertFrom-Json $payload
|
||||
# Do something with data
|
||||
}
|
||||
catch {
|
||||
Write-Host "Error processing message: $_" -ForegroundColor Red
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Tips and Best Practices
|
||||
|
||||
1. **Always disconnect cleanly**: Use `Disconnect-MqttSession` before `Remove-MqttSession`
|
||||
2. **Use appropriate QoS levels**: QoS 0 for best performance, QoS 1 for reliability, QoS 2 for critical data
|
||||
3. **Handle errors in message handlers**: Wrap handler code in try-catch blocks
|
||||
4. **Use meaningful client IDs**: Set custom client IDs for debugging
|
||||
5. **Secure connections**: Use TLS for production environments
|
||||
6. **Topic design**: Use hierarchical topic structures like `device/sensor/temperature`
|
||||
7. **Clean session**: Use `-CleanSession:$false` to maintain subscriptions across reconnects
|
||||
|
||||
## Requirements
|
||||
|
||||
- .NET 8.0 or later
|
||||
- PowerShell 7.0 or later
|
||||
- MQTTnet library
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the MIT License - see the LICENSE file in the repository root for details.
|
||||
@@ -0,0 +1,25 @@
|
||||
# Quick smoke test for MQTTnet PowerShell Module
|
||||
Import-Module ./bin/Debug/net8.0/MQTTnet.PowerShell.dll -WarningAction SilentlyContinue
|
||||
|
||||
Write-Host "Testing module load..." -ForegroundColor Yellow
|
||||
$commands = Get-Command -Module MQTTnet.PowerShell
|
||||
Write-Host "Found $($commands.Count) cmdlets:" -ForegroundColor Green
|
||||
$commands | ForEach-Object { Write-Host " - $($_.Name)" -ForegroundColor Cyan }
|
||||
|
||||
Write-Host "`nTesting session creation..." -ForegroundColor Yellow
|
||||
$session = New-MqttSession
|
||||
Write-Host "Session created successfully!" -ForegroundColor Green
|
||||
|
||||
Write-Host "`nTesting Show-MqttExamples..." -ForegroundColor Yellow
|
||||
$examples = Show-MqttExamples
|
||||
if ($examples) {
|
||||
Write-Host "Examples retrieved successfully!" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "Failed to retrieve examples!" -ForegroundColor Red
|
||||
}
|
||||
|
||||
Write-Host "`nTesting session cleanup..." -ForegroundColor Yellow
|
||||
Remove-MqttSession -Session $session
|
||||
Write-Host "Session removed successfully!" -ForegroundColor Green
|
||||
|
||||
Write-Host "`n✓ All smoke tests passed!" -ForegroundColor Green
|
||||
@@ -0,0 +1,117 @@
|
||||
# MQTTnet PowerShell Module Test Script
|
||||
# This script demonstrates the usage of the MQTTnet PowerShell module
|
||||
|
||||
Import-Module ./bin/Debug/net8.0/MQTTnet.PowerShell.dll
|
||||
|
||||
Write-Host "=== MQTTnet PowerShell Module Test ===" -ForegroundColor Cyan
|
||||
|
||||
# Example 1: Basic TCP connection
|
||||
Write-Host "`n1. Testing basic TCP connection..." -ForegroundColor Yellow
|
||||
$session = New-MqttSession
|
||||
|
||||
try {
|
||||
Connect-MqttSession -Session $session -Host "broker.hivemq.com" -Port 1883
|
||||
Write-Host "Connected successfully!" -ForegroundColor Green
|
||||
|
||||
# Get session status
|
||||
Write-Host "`n2. Getting session status..." -ForegroundColor Yellow
|
||||
$status = Get-MqttSessionStatus -Session $session
|
||||
Write-Host "IsConnected: $($status.IsConnected)" -ForegroundColor Green
|
||||
|
||||
# Example 2: Publish a simple message
|
||||
Write-Host "`n3. Publishing a simple message..." -ForegroundColor Yellow
|
||||
Publish-MqttMessage -Session $session -Topic "test/powershell" -Payload "Hello from PowerShell!"
|
||||
Write-Host "Message published!" -ForegroundColor Green
|
||||
|
||||
# Example 3: Publish with user properties
|
||||
Write-Host "`n4. Publishing with user properties..." -ForegroundColor Yellow
|
||||
$properties = @{
|
||||
"source" = "PowerShell"
|
||||
"version" = "1.0"
|
||||
}
|
||||
Publish-MqttMessage -Session $session -Topic "test/powershell/advanced" -Payload "Advanced message" -UserProperties $properties -QoS 1
|
||||
Write-Host "Message with properties published!" -ForegroundColor Green
|
||||
|
||||
# Example 4: Subscribe and receive messages
|
||||
Write-Host "`n5. Subscribing to topics..." -ForegroundColor Yellow
|
||||
Subscribe-MqttTopic -Session $session -Topic "test/demo"
|
||||
Write-Host "Subscribed to test/demo" -ForegroundColor Green
|
||||
|
||||
# Example 5: Register a message handler
|
||||
Write-Host "`n6. Registering message handler..." -ForegroundColor Yellow
|
||||
Register-MqttMessageHandler -Session $session -Action {
|
||||
param($topic, $payload, $message)
|
||||
Write-Host "Received message on topic: $topic" -ForegroundColor Cyan
|
||||
Write-Host "Payload: $payload" -ForegroundColor White
|
||||
Write-Host "QoS: $($message.QoS)" -ForegroundColor White
|
||||
}
|
||||
Write-Host "Handler registered!" -ForegroundColor Green
|
||||
|
||||
# Publish a test message that the handler will receive
|
||||
Write-Host "`n7. Publishing test message for handler..." -ForegroundColor Yellow
|
||||
Publish-MqttMessage -Session $session -Topic "test/demo" -Payload "Test message for handler"
|
||||
Start-Sleep -Seconds 2
|
||||
|
||||
# Example 6: Unsubscribe
|
||||
Write-Host "`n8. Unsubscribing from topic..." -ForegroundColor Yellow
|
||||
Unsubscribe-MqttTopic -Session $session -Topic "test/demo"
|
||||
Write-Host "Unsubscribed!" -ForegroundColor Green
|
||||
|
||||
# Example 7: Clean disconnect
|
||||
Write-Host "`n9. Disconnecting..." -ForegroundColor Yellow
|
||||
Disconnect-MqttSession -Session $session
|
||||
Write-Host "Disconnected!" -ForegroundColor Green
|
||||
}
|
||||
catch {
|
||||
Write-Host "Error: $_" -ForegroundColor Red
|
||||
}
|
||||
finally {
|
||||
# Cleanup
|
||||
Remove-MqttSession -Session $session
|
||||
Write-Host "`nSession cleaned up!" -ForegroundColor Green
|
||||
}
|
||||
|
||||
Write-Host "`n=== WebSocket Connection Example ===" -ForegroundColor Cyan
|
||||
|
||||
# Example 8: WebSocket connection
|
||||
Write-Host "`nTesting WebSocket connection..." -ForegroundColor Yellow
|
||||
$wsSession = New-MqttSession
|
||||
|
||||
try {
|
||||
Connect-MqttSession -Session $wsSession -WebSocketUri "ws://broker.hivemq.com:8000/mqtt"
|
||||
Write-Host "Connected via WebSocket!" -ForegroundColor Green
|
||||
|
||||
Publish-MqttMessage -Session $wsSession -Topic "test/websocket" -Payload "Hello via WebSocket!"
|
||||
Write-Host "Message published via WebSocket!" -ForegroundColor Green
|
||||
|
||||
Disconnect-MqttSession -Session $wsSession
|
||||
}
|
||||
catch {
|
||||
Write-Host "WebSocket Error: $_" -ForegroundColor Red
|
||||
}
|
||||
finally {
|
||||
Remove-MqttSession -Session $wsSession
|
||||
}
|
||||
|
||||
Write-Host "`n=== Connection with Will Message Example ===" -ForegroundColor Cyan
|
||||
|
||||
# Example 9: Connection with Last Will Testament
|
||||
Write-Host "`nTesting connection with Will message..." -ForegroundColor Yellow
|
||||
$willSession = New-MqttSession
|
||||
|
||||
try {
|
||||
Connect-MqttSession -Session $willSession -Host "broker.hivemq.com" `
|
||||
-WillTopic "test/will" -WillPayload "Client disconnected unexpectedly" -WillQoS 1 -WillRetain
|
||||
Write-Host "Connected with Will message configured!" -ForegroundColor Green
|
||||
|
||||
Disconnect-MqttSession -Session $willSession
|
||||
}
|
||||
catch {
|
||||
Write-Host "Will Error: $_" -ForegroundColor Red
|
||||
}
|
||||
finally {
|
||||
Remove-MqttSession -Session $willSession
|
||||
}
|
||||
|
||||
Write-Host "`n=== All tests completed ===" -ForegroundColor Cyan
|
||||
|
||||
@@ -5,28 +5,27 @@
|
||||
using MQTTnet.Packets;
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
namespace MQTTnet.Server
|
||||
namespace MQTTnet.Server;
|
||||
|
||||
public sealed class MqttServerClientDisconnectOptions
|
||||
{
|
||||
public sealed class MqttServerClientDisconnectOptions
|
||||
{
|
||||
public MqttDisconnectReasonCode ReasonCode { get; set; } = MqttDisconnectReasonCode.NormalDisconnection;
|
||||
public MqttDisconnectReasonCode ReasonCode { get; set; } = MqttDisconnectReasonCode.NormalDisconnection;
|
||||
|
||||
/// <summary>
|
||||
/// The reason string is sent to every client via a DISCONNECT packet.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public string ReasonString { get; set; }
|
||||
/// <summary>
|
||||
/// The reason string is sent to every client via a DISCONNECT packet.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public string ReasonString { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The server reference is sent to every client via a DISCONNECT packet.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public string ServerReference { get; set; }
|
||||
/// <summary>
|
||||
/// The server reference is sent to every client via a DISCONNECT packet.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public string ServerReference { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// These user properties are sent to every client via a DISCONNECT packet.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public List<MqttUserProperty> UserProperties { get; set; }
|
||||
}
|
||||
/// <summary>
|
||||
/// These user properties are sent to every client via a DISCONNECT packet.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public List<MqttUserProperty> UserProperties { get; set; }
|
||||
}
|
||||
@@ -5,50 +5,49 @@
|
||||
using MQTTnet.Packets;
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
namespace MQTTnet.Server
|
||||
namespace MQTTnet.Server;
|
||||
|
||||
public sealed class MqttServerClientDisconnectOptionsBuilder
|
||||
{
|
||||
public sealed class MqttServerClientDisconnectOptionsBuilder
|
||||
readonly MqttServerClientDisconnectOptions _options = new MqttServerClientDisconnectOptions();
|
||||
|
||||
public MqttServerClientDisconnectOptions Build()
|
||||
{
|
||||
readonly MqttServerClientDisconnectOptions _options = new MqttServerClientDisconnectOptions();
|
||||
return _options;
|
||||
}
|
||||
|
||||
public MqttServerClientDisconnectOptions Build()
|
||||
public MqttServerClientDisconnectOptionsBuilder WithReasonCode(MqttDisconnectReasonCode value)
|
||||
{
|
||||
_options.ReasonCode = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttServerClientDisconnectOptionsBuilder WithReasonString(string value)
|
||||
{
|
||||
_options.ReasonString = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttServerClientDisconnectOptionsBuilder WithServerReference(string value)
|
||||
{
|
||||
_options.ServerReference = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttServerClientDisconnectOptionsBuilder WithUserProperties(List<MqttUserProperty> value)
|
||||
{
|
||||
_options.UserProperties = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttServerClientDisconnectOptionsBuilder WithUserProperty(string name, string value)
|
||||
{
|
||||
if (_options.UserProperties == null)
|
||||
{
|
||||
return _options;
|
||||
_options.UserProperties = new List<MqttUserProperty>();
|
||||
}
|
||||
|
||||
public MqttServerClientDisconnectOptionsBuilder WithReasonCode(MqttDisconnectReasonCode value)
|
||||
{
|
||||
_options.ReasonCode = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttServerClientDisconnectOptionsBuilder WithReasonString(string value)
|
||||
{
|
||||
_options.ReasonString = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttServerClientDisconnectOptionsBuilder WithServerReference(string value)
|
||||
{
|
||||
_options.ServerReference = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttServerClientDisconnectOptionsBuilder WithUserProperties(List<MqttUserProperty> value)
|
||||
{
|
||||
_options.UserProperties = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttServerClientDisconnectOptionsBuilder WithUserProperty(string name, string value)
|
||||
{
|
||||
if (_options.UserProperties == null)
|
||||
{
|
||||
_options.UserProperties = new List<MqttUserProperty>();
|
||||
}
|
||||
|
||||
_options.UserProperties.Add(new MqttUserProperty(name, value));
|
||||
return this;
|
||||
}
|
||||
_options.UserProperties.Add(new MqttUserProperty(name, value));
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -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 MQTTnet.Packets;
|
||||
|
||||
namespace MQTTnet.Server.EnhancedAuthentication;
|
||||
|
||||
public sealed class ExchangeEnhancedAuthenticationOptions
|
||||
{
|
||||
public byte[] AuthenticationData { get; set; }
|
||||
|
||||
public string ReasonString { get; set; }
|
||||
|
||||
public List<MqttUserProperty> UserProperties { get; set; }
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
// 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.Text;
|
||||
using MQTTnet.Packets;
|
||||
|
||||
namespace MQTTnet.Server.EnhancedAuthentication;
|
||||
|
||||
public sealed class ExchangeEnhancedAuthenticationOptionsFactory
|
||||
{
|
||||
readonly ExchangeEnhancedAuthenticationOptions _options = new();
|
||||
|
||||
public ExchangeEnhancedAuthenticationOptions Build()
|
||||
{
|
||||
return _options;
|
||||
}
|
||||
|
||||
public ExchangeEnhancedAuthenticationOptionsFactory WithAuthenticationData(byte[] authenticationData)
|
||||
{
|
||||
_options.AuthenticationData = authenticationData;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public ExchangeEnhancedAuthenticationOptionsFactory WithAuthenticationData(string authenticationData)
|
||||
{
|
||||
if (authenticationData == null)
|
||||
{
|
||||
_options.AuthenticationData = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
_options.AuthenticationData = Encoding.UTF8.GetBytes(authenticationData);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public ExchangeEnhancedAuthenticationOptionsFactory WithReasonString(string reasonString)
|
||||
{
|
||||
_options.ReasonString = reasonString;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public ExchangeEnhancedAuthenticationOptionsFactory WithUserProperties(List<MqttUserProperty> userProperties)
|
||||
{
|
||||
_options.UserProperties = userProperties;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public ExchangeEnhancedAuthenticationOptionsFactory WithUserProperty(string name, string value)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(name);
|
||||
|
||||
if (_options.UserProperties == null)
|
||||
{
|
||||
_options.UserProperties = new List<MqttUserProperty>();
|
||||
}
|
||||
|
||||
_options.UserProperties.Add(new MqttUserProperty(name, value));
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using MQTTnet.Packets;
|
||||
|
||||
namespace MQTTnet.Server.EnhancedAuthentication;
|
||||
|
||||
public sealed class ExchangeEnhancedAuthenticationResult
|
||||
{
|
||||
public string ReasonString { get; set; }
|
||||
|
||||
public List<MqttUserProperty> UserProperties { get; set; }
|
||||
|
||||
public byte[] AuthenticationData { get; set; }
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
using MQTTnet.Packets;
|
||||
|
||||
namespace MQTTnet.Server.EnhancedAuthentication;
|
||||
|
||||
public static class ExchangeEnhancedAuthenticationResultFactory
|
||||
{
|
||||
public static ExchangeEnhancedAuthenticationResult Create(MqttAuthPacket authPacket)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(authPacket);
|
||||
|
||||
return new ExchangeEnhancedAuthenticationResult
|
||||
{
|
||||
AuthenticationData = authPacket.AuthenticationData,
|
||||
|
||||
ReasonString = authPacket.ReasonString,
|
||||
UserProperties = authPacket.UserProperties
|
||||
};
|
||||
}
|
||||
|
||||
public static ExchangeEnhancedAuthenticationResult Create(MqttDisconnectPacket disconnectPacket)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(disconnectPacket);
|
||||
|
||||
return new ExchangeEnhancedAuthenticationResult
|
||||
{
|
||||
AuthenticationData = null,
|
||||
ReasonString = disconnectPacket.ReasonString,
|
||||
UserProperties = disconnectPacket.UserProperties
|
||||
|
||||
// SessionExpiryInterval makes no sense because the connection is not yet made!
|
||||
// ServerReferences makes no sense when the client initiated a DISCONNECT!
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -2,26 +2,23 @@
|
||||
// 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.Server;
|
||||
|
||||
namespace MQTTnet.Server
|
||||
public sealed class ApplicationMessageEnqueuedEventArgs : EventArgs
|
||||
{
|
||||
public sealed class ApplicationMessageEnqueuedEventArgs : EventArgs
|
||||
public ApplicationMessageEnqueuedEventArgs(string senderClientId, string receiverClientId, MqttApplicationMessage applicationMessage, bool isDropped)
|
||||
{
|
||||
public ApplicationMessageEnqueuedEventArgs(string senderClientId, string receiverClientId, MqttApplicationMessage applicationMessage, bool isDropped)
|
||||
{
|
||||
SenderClientId = senderClientId ?? throw new ArgumentNullException( nameof(senderClientId));
|
||||
ReceiverClientId = receiverClientId ?? throw new ArgumentNullException(nameof(receiverClientId));
|
||||
ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
|
||||
IsDropped = isDropped;
|
||||
}
|
||||
|
||||
public string SenderClientId { get; }
|
||||
|
||||
public string ReceiverClientId { get; }
|
||||
|
||||
public bool IsDropped { get; }
|
||||
|
||||
public MqttApplicationMessage ApplicationMessage { get; }
|
||||
SenderClientId = senderClientId ?? throw new ArgumentNullException( nameof(senderClientId));
|
||||
ReceiverClientId = receiverClientId ?? throw new ArgumentNullException(nameof(receiverClientId));
|
||||
ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
|
||||
IsDropped = isDropped;
|
||||
}
|
||||
|
||||
public string SenderClientId { get; }
|
||||
|
||||
public string ReceiverClientId { get; }
|
||||
|
||||
public bool IsDropped { get; }
|
||||
|
||||
public MqttApplicationMessage ApplicationMessage { get; }
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user