Compare commits

..
Author SHA1 Message Date
copilot-swe-agent[bot]andchkr1011 06842366b8 Add implementation summary documentation
Co-authored-by: chkr1011 <6939810+chkr1011@users.noreply.github.com>
2026-02-07 10:55:09 +00:00
copilot-swe-agent[bot]andchkr1011 9cf9a714cb Fix WebSocket URI format and remove unnecessary null-forgiving operator
Co-authored-by: chkr1011 <6939810+chkr1011@users.noreply.github.com>
2026-02-07 10:52:10 +00:00
copilot-swe-agent[bot]andchkr1011 16f8765562 Add Test-MqttConnection and Show-MqttExamples cmdlets, update documentation
Co-authored-by: chkr1011 <6939810+chkr1011@users.noreply.github.com>
2026-02-07 10:50:42 +00:00
copilot-swe-agent[bot]andchkr1011 07b72e75ed Add comprehensive documentation and smoke tests for PowerShell module
Co-authored-by: chkr1011 <6939810+chkr1011@users.noreply.github.com>
2026-02-07 10:49:16 +00:00
copilot-swe-agent[bot]andchkr1011 419d7622d8 Implement enhanced PowerShell cmdlets with async handler, WebSocket, TLS, and more features
Co-authored-by: chkr1011 <6939810+chkr1011@users.noreply.github.com>
2026-02-07 10:47:51 +00:00
copilot-swe-agent[bot]andchkr1011 71140d12c7 Remove binary files from git and clean up csproj
Co-authored-by: chkr1011 <6939810+chkr1011@users.noreply.github.com>
2026-02-07 10:45:31 +00:00
copilot-swe-agent[bot]andchkr1011 a2957180fa Initial plan for PowerShell module extension
Co-authored-by: chkr1011 <6939810+chkr1011@users.noreply.github.com>
2026-02-07 10:45:13 +00:00
copilot-swe-agent[bot] ffb455b5b4 Initial plan 2026-02-07 10:43:27 +00:00
christian 076039abc7 Add binary files to repo for testing purposes 2025-10-24 17:35:12 +02:00
christian 61cbaa06a8 Expose more properties 2025-10-20 21:41:20 +02:00
christian 1114fa041d Add new PowerShell project 2025-10-20 20:52:31 +02:00
Christian 535a4e010c Add validations of variable byte integers (#2214)
* Add validations and add docs

* Update release notes

* Fix build
2025-10-20 14:33:50 +02:00
老九andchristian 37c1eecce5 Add Directory.Build.props. (#2120)
* Add Directory.Build.props.

* Adjust Analysis related properties and add conditional enablement for ContinuousIntegrationBuild.

* Adopt new changes in master

---------

Co-authored-by: christian <6939810+chkr1011@users.noreply.github.com>
2025-10-20 13:32:31 +02:00
Christian e791d9aba4 Expose local endpoint in channel adapter (#2213)
* Expose local endpoint

* Update release notes
2025-10-19 23:06:36 +02:00
Christian 5ebc5fcb7e Call enqueue interceptor for retained messages (fix) (#2212)
* Call the event handler also for retained messages

* Update release notes

* Add Unit Test
2025-10-19 21:21:47 +02:00
Christian 51979fa5a4 Call enqueue interceptor for retained messages (#2211)
* Call the event handler also for retained messages

* Update release notes
2025-10-19 19:14:33 +02:00
Christian 7ac7f256ef Update nugets (#2210)
* Upate nugets, fix breaking changes

* Fix CI build
2025-10-17 21:29:46 +02:00
e7ccd897b0 Use License file from code repo (#2197)
* Use License file from code repo

* Configure all relevant projects

* Update release notes

* Restore release notes

---------

Co-authored-by: Jens Nordenbro <jens.nordenbro@beijerelectronics.com>
Co-authored-by: christian <6939810+chkr1011@users.noreply.github.com>
2025-10-17 19:28:45 +02:00
Christian d5d72ac37d Enable support for AOT compilation (#2209)
* Update project files

* Update release notes
2025-10-17 19:06:28 +02:00
Christian a5ef10fddb Restore strong name (#2208)
* Restore strong name certificate

* Migrate strong name certificate loading to linux.

* Fix wrong path

* Restore version, update release notes
2025-10-17 19:05:20 +02:00
zhaowgitandChristian c97bd82334 Issues #2091 Improve the performance of RetainedMessages without subs… (#2093)
* Issues #2091 Improve the performance of RetainedMessages without subscribing to RetainedMessageChangedEvent.

* Fix build errors

* Update release notes

---------

Co-authored-by: Christian <6939810+chkr1011@users.noreply.github.com>
2025-10-17 19:04:11 +02:00
christian 75ebf116cf Merge remote-tracking branch 'origin/master' 2025-09-14 10:47:46 +02:00
christian 2f7f55e3f5 Update version 2025-09-14 10:47:32 +02:00
Christian e43e7f8e36 Use latest language features (#2176)
* Make use of language features line AsyncDispose, init etc.

* Fix build errors

* Improve Unit Tests

* Improve Unit Tests

* Improve Unit Tests

* Update samples

* Remove obsolete benchmarks

* Code cleanup

* Improve unit tests

* Improve Unit Tests

* Fix Unit Tests

* Improve Unit Tests

* Improve Unit Tests

* Restore unit tests

* Exclude load tests from unit tests
2025-08-30 10:52:59 +02:00
timyhac 247988e633 Remove "MQTTv5 only" in documentation comments for MqttClientConnectResult.IsSessionPresent and MqttClientConnectResult.ResultCode (#2166)
* Remove "MQTTv5" from documentation comments for `IsSessionPresent`

 As far as I can tell, this is available for MQTT v3.1.1

References:
* [OASIS Standard](https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718035)
* [MQTTnet source code](https://github.com/dotnet/MQTTnet/blob/93a9d8d1dbe8dde2b7d79a37c873fae13b4f4797/Source/MQTTnet/Connecting/MqttClientConnectResultFactory.cs#L75)

* Remove "MQTTv5 only" for `ResultCode`
2025-03-29 19:11:53 +01:00
Youssef Victor 93a9d8d1db Move to MTP (#2163) 2025-03-02 11:59:33 +01:00
Christian 24c418cf43 Update README.md 2025-01-07 18:57:58 +01:00
Lucas Tandchristian 948c7c2b69 Corrected Cancellation Source for KeepAlive Messages (#2129)
* Corrected Cancellation Source for KeepAlive Messages

* Use half of the keep alive value for the ping timeout

* Update release notes

* Update ReleaseNotes.md

* Update ReleaseNotes.md

* Update ReleaseNotes.md

---------

Co-authored-by: christian <6939810+chkr1011@users.noreply.github.com>
2025-01-02 12:17:05 +01:00
christian 1cdc343e6d Update release notes 2025-01-02 12:11:57 +01:00
27771f0146 Improve session message injection (#2117)
* Improve session message injection methods

* Add more tests for session injection

* Change return type from MqttPublishPacket to MqttPacketWithIdentifier

* Use dedicated type for MattApplicationMessage injection result

* Code review minor fixes

* Apply project code style

* Update release notes

---------

Co-authored-by: Anton Smolkov <anton.smolkov@idp.zyfra.com>
Co-authored-by: christian <6939810+chkr1011@users.noreply.github.com>
2025-01-02 11:51:46 +01:00
christian e7de2b913a Increase version 2025-01-02 11:34:14 +01:00
christian 3dd3279656 Fix missing release notes in nuget package 2025-01-02 11:34:08 +01:00
christian c5934bf18b Reset release notes 2025-01-02 11:22:19 +01:00
380 changed files with 23477 additions and 22859 deletions
+10 -3
View File
@@ -3,7 +3,7 @@ name: CI
on: [push, pull_request] on: [push, pull_request]
env: env:
VERSION: "5.0.0.${{github.run_number}}" VERSION: "5.1.0.${{github.run_number}}"
PACKAGE_SUFFIX: "" PACKAGE_SUFFIX: ""
jobs: jobs:
@@ -21,8 +21,15 @@ jobs:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 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 - 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 - name: Upload nuget packages
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
@@ -46,7 +53,7 @@ jobs:
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Execute tests - 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: sign:
needs: build needs: build
+58
View File
@@ -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
View File
@@ -7,11 +7,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet", "Source\MQTTnet\M
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{B3F60ECB-45BA-4C66-8903-8BB89CA67998}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{B3F60ECB-45BA-4C66-8903-8BB89CA67998}"
ProjectSection(SolutionItems) = preProject ProjectSection(SolutionItems) = preProject
.github\workflows\ci.yml = .github\workflows\ci.yml
CODE-OF-CONDUCT.md = CODE-OF-CONDUCT.md CODE-OF-CONDUCT.md = CODE-OF-CONDUCT.md
Directory.Build.props = Directory.Build.props
LICENSE = LICENSE LICENSE = LICENSE
README.md = README.md README.md = README.md
Source\ReleaseNotes.md = Source\ReleaseNotes.md Source\ReleaseNotes.md = Source\ReleaseNotes.md
.github\workflows\ci.yml = .github\workflows\ci.yml
EndProjectSection EndProjectSection
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.AspNetCore", "Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj", "{F10C4060-F7EE-4A83-919F-FF723E72F94A}" 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 EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MQTTnet.Server", "Source\MQTTnet.Server\MQTTnet.Server.csproj", "{C876EFFD-C5AD-4E26-BD9F-4C48252C02BC}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MQTTnet.Server", "Source\MQTTnet.Server\MQTTnet.Server.csproj", "{C876EFFD-C5AD-4E26-BD9F-4C48252C02BC}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MQTTnet.PowerShell", "Source\MQTTnet.PowerShell\MQTTnet.PowerShell.csproj", "{37B129C5-0231-4EB5-AF13-77959068911C}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU 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}.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.ActiveCfg = Release|Any CPU
{C876EFFD-C5AD-4E26-BD9F-4C48252C02BC}.Release|Any CPU.Build.0 = 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 EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@@ -85,6 +92,4 @@ Global
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {07536672-5CBC-4BE3-ACE0-708A431A7894} SolutionGuid = {07536672-5CBC-4BE3-ACE0-708A431A7894}
EndGlobalSection EndGlobalSection
GlobalSection(NestedProjects) = preSolution
EndGlobalSection
EndGlobal EndGlobal
+4
View File
@@ -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"> <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_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_FOREACH/@EntryValue">Required</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_IFELSE/@EntryValue">Required</s:String> <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_IFELSE/@EntryValue">Required</s:String>
+2 -2
View File
@@ -3,9 +3,9 @@
<br/> <br/>
</p> </p>
[![NuGet Badge](https://buildstats.info/nuget/MQTTnet)](https://www.nuget.org/packages/MQTTnet) [![NuGet Badge](https://img.shields.io/nuget/dt/MQTTnet)](https://www.nuget.org/packages/MQTTnet)
[![CI](https://github.com/dotnet/MQTTnet/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/dotnet/MQTTnet/actions/workflows/ci.yml) [![CI](https://github.com/dotnet/MQTTnet/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/dotnet/MQTTnet/actions/workflows/ci.yml)
[![MyGet](https://img.shields.io/myget/mqttnet/v/mqttnet?color=orange&label=MyGet-Preview)](https://www.myget.org/feed/mqttnet/package/nuget/MQTTnet) [![MyGet](https://img.shields.io/myget/mqttnet/v/mqttnet?color=orange&label=preview)](https://www.myget.org/feed/mqttnet/package/nuget/MQTTnet)
![Size](https://img.shields.io/github/repo-size/dotnet/MQTTnet.svg) ![Size](https://img.shields.io/github/repo-size/dotnet/MQTTnet.svg)
[![Join the chat at https://gitter.im/MQTTnet/community](https://badges.gitter.im/MQTTnet/community.svg)](https://gitter.im/MQTTnet/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Join the chat at https://gitter.im/MQTTnet/community](https://badges.gitter.im/MQTTnet/community.svg)](https://gitter.im/MQTTnet/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://raw.githubusercontent.com/dotnet/MQTTnet/master/LICENSE) [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://raw.githubusercontent.com/dotnet/MQTTnet/master/LICENSE)
+2 -2
View File
@@ -413,7 +413,7 @@ m/XriWr/Cq4h/JfB7NTsezVslgkBaoU=
if (e.ClientWasConnected) if (e.ClientWasConnected)
{ {
// Use the current options as the new options. // Use the current options as the new options.
await mqttClient.ConnectAsync(mqttClient.Options); await mqttClient.ConnectAsync(mqttClientOptions);
} }
}; };
@@ -490,7 +490,7 @@ m/XriWr/Cq4h/JfB7NTsezVslgkBaoU=
} }
} }
class SampleClientKerberosAuthenticationHandler : IMqttEnhancedAuthenticationHandler sealed class SampleClientKerberosAuthenticationHandler : IMqttEnhancedAuthenticationHandler
{ {
public async Task HandleEnhancedAuthenticationAsync(MqttEnhancedAuthenticationEventArgs eventArgs) public async Task HandleEnhancedAuthenticationAsync(MqttEnhancedAuthenticationEventArgs eventArgs)
{ {
+34 -38
View File
@@ -23,25 +23,23 @@ public static class Client_Publish_Samples
var mqttFactory = new MqttClientFactory(); var mqttFactory = new MqttClientFactory();
using (var mqttClient = mqttFactory.CreateMqttClient()) using var mqttClient = mqttFactory.CreateMqttClient();
{ var mqttClientOptions = new MqttClientOptionsBuilder()
var mqttClientOptions = new MqttClientOptionsBuilder() .WithTcpServer("broker.hivemq.com")
.WithTcpServer("broker.hivemq.com") .Build();
.Build();
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None); await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
var applicationMessage = new MqttApplicationMessageBuilder() var applicationMessage = new MqttApplicationMessageBuilder()
.WithTopic("samples/temperature/living_room") .WithTopic("samples/temperature/living_room")
.WithPayload("19.5") .WithPayload("19.5")
.Build(); .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() public static async Task Publish_Multiple_Application_Messages()
@@ -54,38 +52,36 @@ public static class Client_Publish_Samples
var mqttFactory = new MqttClientFactory(); var mqttFactory = new MqttClientFactory();
using (var mqttClient = mqttFactory.CreateMqttClient()) using var mqttClient = mqttFactory.CreateMqttClient();
{ var mqttClientOptions = new MqttClientOptionsBuilder()
var mqttClientOptions = new MqttClientOptionsBuilder() .WithTcpServer("broker.hivemq.com")
.WithTcpServer("broker.hivemq.com") .Build();
.Build();
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None); await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
var applicationMessage = new MqttApplicationMessageBuilder() var applicationMessage = new MqttApplicationMessageBuilder()
.WithTopic("samples/temperature/living_room") .WithTopic("samples/temperature/living_room")
.WithPayload("19.5") .WithPayload("19.5")
.Build(); .Build();
await mqttClient.PublishAsync(applicationMessage, CancellationToken.None); await mqttClient.PublishAsync(applicationMessage, CancellationToken.None);
applicationMessage = new MqttApplicationMessageBuilder() applicationMessage = new MqttApplicationMessageBuilder()
.WithTopic("samples/temperature/living_room") .WithTopic("samples/temperature/living_room")
.WithPayload("20.0") .WithPayload("20.0")
.Build(); .Build();
await mqttClient.PublishAsync(applicationMessage, CancellationToken.None); await mqttClient.PublishAsync(applicationMessage, CancellationToken.None);
applicationMessage = new MqttApplicationMessageBuilder() applicationMessage = new MqttApplicationMessageBuilder()
.WithTopic("samples/temperature/living_room") .WithTopic("samples/temperature/living_room")
.WithPayload("21.0") .WithPayload("21.0")
.Build(); .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.");
}
} }
} }
+59 -67
View File
@@ -26,32 +26,30 @@ public static class Client_Subscribe_Samples
var mqttFactory = new MqttClientFactory(); 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 return Task.CompletedTask;
// 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; 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() public static async Task Send_Responses()
@@ -62,36 +60,34 @@ public static class Client_Subscribe_Samples
var mqttFactory = new MqttClientFactory(); 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. // Now respond to the broker with a reason code other than success.
args.ReasonCode = MqttApplicationMessageReceivedReasonCode.ImplementationSpecificError; args.ReasonCode = MqttApplicationMessageReceivedReasonCode.ImplementationSpecificError;
args.ResponseReasonString = "That did not work!"; args.ResponseReasonString = "That did not work!";
// User properties require MQTT v5! // User properties require MQTT v5!
args.ResponseUserProperties.Add(new MqttUserProperty("My", "Data")); args.ResponseUserProperties.Add(new MqttUserProperty("My", "Data"));
// Now the broker will resend the message again. // Now the broker will resend the message again.
return Task.CompletedTask; 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. // The response contains additional data sent by the server after subscribing.
response.DumpToConsole(); response.DumpToConsole();
}
} }
public static async Task Subscribe_Multiple_Topics() public static async Task Subscribe_Multiple_Topics()
@@ -102,27 +98,25 @@ public static class Client_Subscribe_Samples
var mqttFactory = new MqttClientFactory(); var mqttFactory = new MqttClientFactory();
using (var mqttClient = mqttFactory.CreateMqttClient()) using var mqttClient = mqttFactory.CreateMqttClient();
{ 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);
// Create the subscribe options including several topics with different options. // 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. // It is also possible to all of these topics using a dedicated call of _SubscribeAsync_ per topic.
var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder() var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder()
.WithTopicTemplate(sampleTemplate.WithParameter("id", "1")) .WithTopicTemplate(sampleTemplate.WithParameter("id", "1"))
.WithTopicTemplate(sampleTemplate.WithParameter("id", "2"), noLocal: true) .WithTopicTemplate(sampleTemplate.WithParameter("id", "2"), noLocal: true)
.WithTopicTemplate(sampleTemplate.WithParameter("id", "3"), retainHandling: MqttRetainHandling.SendAtSubscribe) .WithTopicTemplate(sampleTemplate.WithParameter("id", "3"), retainHandling: MqttRetainHandling.SendAtSubscribe)
.Build(); .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. // The response contains additional data sent by the server after subscribing.
response.DumpToConsole(); response.DumpToConsole();
}
} }
public static async Task Subscribe_Topic() public static async Task Subscribe_Topic()
@@ -133,24 +127,22 @@ public static class Client_Subscribe_Samples
var mqttFactory = new MqttClientFactory(); var mqttFactory = new MqttClientFactory();
using (var mqttClient = mqttFactory.CreateMqttClient()) using var mqttClient = mqttFactory.CreateMqttClient();
{ 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. // The response contains additional data sent by the server after subscribing.
response.DumpToConsole(); 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 * 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: * This sample shows how to achieve concurrent processing, with:
+15 -18
View File
@@ -6,6 +6,7 @@
// ReSharper disable UnusedMember.Global // ReSharper disable UnusedMember.Global
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming
using System.Globalization;
using System.Text; using System.Text;
using MQTTnet.Diagnostics.Logger; using MQTTnet.Diagnostics.Logger;
@@ -26,17 +27,15 @@ public static class Logger_Samples
.WithTcpServer("broker.hivemq.com") .WithTcpServer("broker.hivemq.com")
.Build(); .Build();
using (var mqttClient = mqttFactory.CreateMqttClient()) using var mqttClient = mqttFactory.CreateMqttClient();
{ 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() var mqttClientDisconnectOptions = mqttFactory.CreateClientDisconnectOptionsBuilder()
.Build(); .Build();
await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None); await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None);
}
} }
public static async Task Use_Event_Logger() 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. // The logger ID is optional but can be set do distinguish different logger instances.
var mqttEventLogger = new MqttNetEventLogger("MyCustomLogger"); var mqttEventLogger = new MqttNetEventLogger("MyCustomLogger");
mqttEventLogger.LogMessagePublished += (sender, args) => mqttEventLogger.LogMessagePublished += (_, args) =>
{ {
var output = new StringBuilder(); 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) if (args.LogMessage.Exception != null)
{ {
output.AppendLine(args.LogMessage.Exception.ToString()); output.AppendLine(args.LogMessage.Exception.ToString());
@@ -68,17 +67,15 @@ public static class Logger_Samples
.WithTcpServer("broker.hivemq.com") .WithTcpServer("broker.hivemq.com")
.Build(); .Build();
using (var mqttClient = mqttFactory.CreateMqttClient()) using var mqttClient = mqttFactory.CreateMqttClient();
{ 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() var mqttClientDisconnectOptions = mqttFactory.CreateClientDisconnectOptionsBuilder()
.Build(); .Build();
await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None); await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None);
}
} }
sealed class MyLogger : IMqttNetLogger sealed class MyLogger : IMqttNetLogger
@@ -20,23 +20,21 @@ public static class PackageInspection_Samples
var mqttFactory = new MqttClientFactory(); var mqttFactory = new MqttClientFactory();
using (var mqttClient = mqttFactory.CreateMqttClient()) using var mqttClient = mqttFactory.CreateMqttClient();
{ var mqttClientOptions = mqttFactory.CreateClientOptionsBuilder()
var mqttClientOptions = mqttFactory.CreateClientOptionsBuilder() .WithTcpServer("broker.hivemq.com")
.WithTcpServer("broker.hivemq.com") .Build();
.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() var mqttClientDisconnectOptions = mqttFactory.CreateClientDisconnectOptionsBuilder()
.Build(); .Build();
await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None); await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None);
}
} }
static Task OnInspectPacket(InspectMqttPacketEventArgs eventArgs) static Task OnInspectPacket(InspectMqttPacketEventArgs eventArgs)
+7 -5
View File
@@ -8,17 +8,19 @@ namespace MQTTnet.Samples.Helpers;
internal static class ObjectExtensions internal static class ObjectExtensions
{ {
static readonly JsonSerializerOptions SerializerOptions = new()
{
WriteIndented = true
};
public static TObject DumpToConsole<TObject>(this TObject @object) public static TObject DumpToConsole<TObject>(this TObject @object)
{ {
var output = "NULL"; var output = "NULL";
if (@object != null) if (@object != null)
{ {
output = JsonSerializer.Serialize(@object, new JsonSerializerOptions output = JsonSerializer.Serialize(@object, SerializerOptions);
{
WriteIndented = true
});
} }
Console.WriteLine($"[{@object?.GetType().Name}]:\r\n{output}"); Console.WriteLine($"[{@object?.GetType().Name}]:\r\n{output}");
return @object; return @object;
} }
+1 -10
View File
@@ -2,19 +2,10 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers> <NoWarn>$(NoWarn);CA1707</NoWarn>
<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>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
+4 -3
View File
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System.Globalization;
using System.Reflection; using System.Reflection;
Console.BackgroundColor = ConsoleColor.White; Console.BackgroundColor = ConsoleColor.White;
@@ -28,7 +29,7 @@ Console.Write("Please choose sample class (press Enter to continue): ");
Console.ResetColor(); Console.ResetColor();
var input = Console.ReadLine(); var input = Console.ReadLine();
var selectedIndex = int.Parse(input ?? "0"); var selectedIndex = int.Parse(input ?? "0", CultureInfo.InvariantCulture);
var selectedSampleClass = sampleClasses[selectedIndex]; var selectedSampleClass = sampleClasses[selectedIndex];
var sampleMethods = selectedSampleClass.GetMethods(BindingFlags.Static | BindingFlags.Public).OrderBy(m => m.Name).ToList(); 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(); Console.ResetColor();
input = Console.ReadLine(); input = Console.ReadLine();
selectedIndex = int.Parse(input ?? "0"); selectedIndex = int.Parse(input ?? "0", CultureInfo.InvariantCulture);
var selectedSampleMethod = sampleMethods[selectedIndex]; var selectedSampleMethod = sampleMethods[selectedIndex];
Console.WriteLine(); Console.WriteLine();
@@ -58,7 +59,7 @@ try
{ {
if (selectedSampleMethod.Invoke(null, null) is Task task) if (selectedSampleMethod.Invoke(null, null) is Task task)
{ {
await task; await task;
} }
} }
catch (Exception exception) catch (Exception exception)
+13 -15
View File
@@ -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 // The RPC client is an addon for the existing client. So we need a regular client
// which is wrapped later. // 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() // Access to a fully featured application message is not supported for RPC calls!
.WithTcpServer("broker.hivemq.com") // The method will throw an exception when the response was not received in time.
.Build(); await mqttRpcClient.ExecuteAsync(TimeSpan.FromSeconds(2), "ping", "", MqttQualityOfServiceLevel.AtMostOnce);
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.");
} }
Console.WriteLine("The RPC call was successful.");
} }
/* /*
+7 -4
View File
@@ -8,6 +8,7 @@
// ReSharper disable EmptyConstructor // ReSharper disable EmptyConstructor
// ReSharper disable MemberCanBeMadeStatic.Local // ReSharper disable MemberCanBeMadeStatic.Local
using System.Diagnostics.CodeAnalysis;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
@@ -52,14 +53,14 @@ public static class Server_ASP_NET_Samples
// Inject other services via constructor. // Inject other services via constructor.
} }
public Task OnClientConnected(ClientConnectedEventArgs eventArgs) public static Task OnClientConnected(ClientConnectedEventArgs eventArgs)
{ {
Console.WriteLine($"Client '{eventArgs.ClientId}' connected."); Console.WriteLine($"Client '{eventArgs.ClientId}' connected.");
return Task.CompletedTask; return Task.CompletedTask;
} }
public Task ValidateConnection(ValidatingConnectionEventArgs eventArgs) public static Task ValidateConnection(ValidatingConnectionEventArgs eventArgs)
{ {
Console.WriteLine($"Client '{eventArgs.ClientId}' wants to connect. Accepting!"); Console.WriteLine($"Client '{eventArgs.ClientId}' wants to connect. Accepting!");
return Task.CompletedTask; return Task.CompletedTask;
@@ -68,6 +69,7 @@ public static class Server_ASP_NET_Samples
sealed class Startup sealed class Startup
{ {
[SuppressMessage("Performance", "CA1822:Mark members as static")]
public void Configure(IApplicationBuilder app, IWebHostEnvironment environment, MqttController mqttController) public void Configure(IApplicationBuilder app, IWebHostEnvironment environment, MqttController mqttController)
{ {
app.UseRouting(); app.UseRouting();
@@ -88,11 +90,12 @@ public static class Server_ASP_NET_Samples
* Attach event handlers etc. if required. * Attach event handlers etc. if required.
*/ */
server.ValidatingConnectionAsync += mqttController.ValidateConnection; server.ValidatingConnectionAsync += MqttController.ValidateConnection;
server.ClientConnectedAsync += mqttController.OnClientConnected; server.ClientConnectedAsync += MqttController.OnClientConnected;
}); });
} }
[SuppressMessage("Performance", "CA1822:Mark members as static")]
public void ConfigureServices(IServiceCollection services) public void ConfigureServices(IServiceCollection services)
{ {
services.AddHostedMqttServer( services.AddHostedMqttServer(
+15 -17
View File
@@ -25,28 +25,26 @@ public static class Server_Diagnostics_Samples
var mqttServerFactory = new MqttServerFactory(); var mqttServerFactory = new MqttServerFactory();
var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build(); 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. Console.WriteLine($"Client '{e.ClientId}' acknowledged packet {e.PublishPacket.PacketIdentifier} with topic '{e.PublishPacket.Topic}'");
mqttServer.ClientAcknowledgedPublishPacketAsync += e =>
{
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. // It is also possible to read additional data from the client response. This requires casting the response packet.
var qos1AcknowledgePacket = e.AcknowledgePacket as MqttPubAckPacket; var qos1AcknowledgePacket = e.AcknowledgePacket as MqttPubAckPacket;
Console.WriteLine($"QoS 1 reason code: {qos1AcknowledgePacket?.ReasonCode}"); Console.WriteLine($"QoS 1 reason code: {qos1AcknowledgePacket?.ReasonCode}");
var qos2AcknowledgePacket = e.AcknowledgePacket as MqttPubCompPacket; var qos2AcknowledgePacket = e.AcknowledgePacket as MqttPubCompPacket;
Console.WriteLine($"QoS 2 reason code: {qos1AcknowledgePacket?.ReasonCode}"); Console.WriteLine($"QoS 2 reason code: {qos2AcknowledgePacket?.ReasonCode}");
return CompletedTask.Instance; return CompletedTask.Instance;
}; };
await mqttServer.StartAsync(); await mqttServer.StartAsync();
Console.WriteLine("Press Enter to exit."); Console.WriteLine("Press Enter to exit.");
Console.ReadLine(); Console.ReadLine();
await mqttServer.StopAsync(); await mqttServer.StopAsync();
}
} }
} }
+40 -13
View File
@@ -9,6 +9,7 @@
// ReSharper disable MemberCanBeMadeStatic.Local // ReSharper disable MemberCanBeMadeStatic.Local
using MQTTnet.Internal; using MQTTnet.Internal;
using MQTTnet.Protocol;
using MQTTnet.Server; using MQTTnet.Server;
namespace MQTTnet.Samples.Server; namespace MQTTnet.Samples.Server;
@@ -25,23 +26,49 @@ public static class Server_Intercepting_Samples
var mqttServerFactory = new MqttServerFactory(); var mqttServerFactory = new MqttServerFactory();
var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build(); 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. args.Response.ReasonCode = MqttSubscribeReasonCode.NotAuthorized;
// 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; return CompletedTask.Instance;
}; };
await mqttServer.StartAsync(); await mqttServer.StartAsync();
Console.WriteLine("Press Enter to exit."); Console.WriteLine("Press Enter to exit.");
Console.ReadLine(); Console.ReadLine();
await mqttServer.StopAsync(); 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! // Due to security reasons the "default" endpoint (which is unencrypted) is not enabled by default!
var mqttServerOptions = mqttServerFactory.CreateServerOptionsBuilder().WithDefaultEndpoint().Build(); 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. try
server.LoadingRetainedMessageAsync += async eventArgs =>
{ {
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; eventArgs.LoadedRetainedMessages = retainedMessages;
Console.WriteLine("Retained messages loaded."); Console.WriteLine("Retained messages loaded.");
} }
catch (FileNotFoundException) 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 =>
{ {
try // Ignore because nothing is stored yet.
{ Console.WriteLine("No retained messages stored yet.");
// 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 catch (Exception exception)
// 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 += _ =>
{ {
File.Delete(storePath); Console.WriteLine(exception);
return Task.CompletedTask; }
}; };
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."); var models = eventArgs.StoredRetainedMessages.Select(MqttRetainedMessageModel.Create);
Console.ReadLine();
} 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 sealed class MqttRetainedMessageModel
+53 -62
View File
@@ -6,6 +6,7 @@
// ReSharper disable UnusedMember.Global // ReSharper disable UnusedMember.Global
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming
using System.Globalization;
using MQTTnet.Diagnostics.Logger; using MQTTnet.Diagnostics.Logger;
using MQTTnet.Protocol; using MQTTnet.Protocol;
using MQTTnet.Server; using MQTTnet.Server;
@@ -22,17 +23,15 @@ public static class Server_Simple_Samples
* See _Run_Minimal_Server_ for more information. * See _Run_Minimal_Server_ for more information.
*/ */
using (var mqttServer = await StartMqttServer()) using var mqttServer = await StartMqttServer();
{ // Let the client connect.
// Let the client connect. await Task.Delay(TimeSpan.FromSeconds(5));
await Task.Delay(TimeSpan.FromSeconds(5));
// Now disconnect the client (if connected). // Now disconnect the client (if connected).
var affectedClient = (await mqttServer.GetClientsAsync()).FirstOrDefault(c => c.Id == "MyClient"); var affectedClient = (await mqttServer.GetClientsAsync()).FirstOrDefault(c => c.Id == "MyClient");
if (affectedClient != null) if (affectedClient != null)
{ {
await affectedClient.DisconnectAsync(); await affectedClient.DisconnectAsync();
}
} }
} }
@@ -44,18 +43,16 @@ public static class Server_Simple_Samples
* See _Run_Minimal_Server_ for more information. * See _Run_Minimal_Server_ for more information.
*/ */
using (var mqttServer = await StartMqttServer()) using var mqttServer = await StartMqttServer();
{ // Create a new message using the builder as usual.
// Create a new message using the builder as usual. var message = new MqttApplicationMessageBuilder().WithTopic("HelloWorld").WithPayload("Test").Build();
var message = new MqttApplicationMessageBuilder().WithTopic("HelloWorld").WithPayload("Test").Build();
// Now inject the new message at the broker. // Now inject the new message at the broker.
await mqttServer.InjectApplicationMessage( await mqttServer.InjectApplicationMessage(
new InjectedMqttApplicationMessage(message) new InjectedMqttApplicationMessage(message)
{ {
SenderClientId = "SenderClientId" SenderClientId = "SenderClientId"
}); });
}
} }
public static async Task Run_Minimal_Server() public static async Task Run_Minimal_Server()
@@ -77,16 +74,14 @@ public static class Server_Simple_Samples
// .WithDefaultEndpointPort(1234) // .WithDefaultEndpointPort(1234)
// .Build(); // .Build();
using (var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions)) using var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions);
{ await mqttServer.StartAsync();
await mqttServer.StartAsync();
Console.WriteLine("Press Enter to exit."); Console.WriteLine("Press Enter to exit.");
Console.ReadLine(); Console.ReadLine();
// Stop and dispose the MQTT server if it is no longer needed! // Stop and dispose the MQTT server if it is no longer needed!
await mqttServer.StopAsync(); await mqttServer.StopAsync();
}
} }
public static async Task Run_Server_With_Logging() public static async Task Run_Server_With_Logging()
@@ -103,16 +98,14 @@ public static class Server_Simple_Samples
var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build(); var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
using (var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions)) using var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions);
{ await mqttServer.StartAsync();
await mqttServer.StartAsync();
Console.WriteLine("Press Enter to exit."); Console.WriteLine("Press Enter to exit.");
Console.ReadLine(); Console.ReadLine();
// Stop and dispose the MQTT server if it is no longer needed! // Stop and dispose the MQTT server if it is no longer needed!
await mqttServer.StopAsync(); await mqttServer.StopAsync();
}
} }
public static async Task Validating_Connections() public static async Task Validating_Connections()
@@ -127,37 +120,35 @@ public static class Server_Simple_Samples
var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build(); 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 if (e.ClientId != "ValidClientId")
// no change to connect without valid credentials.
mqttServer.ValidatingConnectionAsync += e =>
{ {
if (e.ClientId != "ValidClientId") e.ReasonCode = MqttConnectReasonCode.ClientIdentifierNotValid;
{ }
e.ReasonCode = MqttConnectReasonCode.ClientIdentifierNotValid;
}
if (e.UserName != "ValidUser") if (e.UserName != "ValidUser")
{ {
e.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword; e.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword;
} }
if (e.Password != "SecretPassword") if (e.Password != "SecretPassword")
{ {
e.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword; e.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword;
} }
return Task.CompletedTask; return Task.CompletedTask;
}; };
await mqttServer.StartAsync(); await mqttServer.StartAsync();
Console.WriteLine("Press Enter to exit."); Console.WriteLine("Press Enter to exit.");
Console.ReadLine(); Console.ReadLine();
await mqttServer.StopAsync(); await mqttServer.StopAsync();
}
} }
static async Task<MqttServer> StartMqttServer() static async Task<MqttServer> StartMqttServer()
@@ -171,7 +162,7 @@ public static class Server_Simple_Samples
return server; return server;
} }
class ConsoleLogger : IMqttNetLogger sealed class ConsoleLogger : IMqttNetLogger
{ {
readonly object _consoleSyncRoot = new(); readonly object _consoleSyncRoot = new();
@@ -201,7 +192,7 @@ public static class Server_Simple_Samples
if (parameters?.Length > 0) if (parameters?.Length > 0)
{ {
message = string.Format(message, parameters); message = string.Format(CultureInfo.InvariantCulture, message, parameters);
} }
lock (_consoleSyncRoot) lock (_consoleSyncRoot)
+18 -24
View File
@@ -31,16 +31,14 @@ public static class Server_TLS_Samples
var mqttServerOptions = new MqttServerOptionsBuilder().WithEncryptionCertificate(certificate).WithEncryptedEndpoint().Build(); var mqttServerOptions = new MqttServerOptionsBuilder().WithEncryptionCertificate(certificate).WithEncryptedEndpoint().Build();
using (var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions)) using var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions);
{ await mqttServer.StartAsync();
await mqttServer.StartAsync();
Console.WriteLine("Press Enter to exit."); Console.WriteLine("Press Enter to exit.");
Console.ReadLine(); Console.ReadLine();
// Stop and dispose the MQTT server if it is no longer needed! // Stop and dispose the MQTT server if it is no longer needed!
await mqttServer.StopAsync(); await mqttServer.StopAsync();
}
} }
static X509Certificate2 CreateSelfSignedCertificate(string oid) static X509Certificate2 CreateSelfSignedCertificate(string oid)
@@ -50,26 +48,22 @@ public static class Server_TLS_Samples
sanBuilder.AddIpAddress(IPAddress.IPv6Loopback); sanBuilder.AddIpAddress(IPAddress.IPv6Loopback);
sanBuilder.AddDnsName("localhost"); sanBuilder.AddDnsName("localhost");
using (var rsa = RSA.Create()) using var rsa = RSA.Create();
{ var certRequest = new CertificateRequest("CN=localhost", rsa, HashAlgorithmName.SHA512, RSASignaturePadding.Pkcs1);
var certRequest = new CertificateRequest("CN=localhost", rsa, HashAlgorithmName.SHA512, RSASignaturePadding.Pkcs1);
certRequest.CertificateExtensions.Add( certRequest.CertificateExtensions.Add(
new X509KeyUsageExtension(X509KeyUsageFlags.DataEncipherment | X509KeyUsageFlags.KeyEncipherment | X509KeyUsageFlags.DigitalSignature, false)); 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))) using var certificate = certRequest.CreateSelfSigned(DateTimeOffset.Now.AddMinutes(-10), DateTimeOffset.Now.AddMinutes(10));
{ var pfxCertificate = new X509Certificate2(
var pfxCertificate = new X509Certificate2( certificate.Export(X509ContentType.Pfx),
certificate.Export(X509ContentType.Pfx), (string)null!,
(string)null!, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable);
X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable);
return pfxCertificate; return pfxCertificate;
}
}
} }
} }
@@ -1,19 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk.Web"> <Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers> <NoWarn>$(NoWarn);CA1707</NoWarn>
<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>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
+15 -13
View File
@@ -4,20 +4,22 @@
using Microsoft.AspNetCore.Mvc.RazorPages; 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; _logger = logger;
public IndexModel(ILogger<IndexModel> logger)
{
_logger = logger;
}
public void OnGet()
{
}
} }
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>(); var adapter = app.ApplicationServices.GetRequiredService<MqttWebSocketServerAdapter>();
using (var webSocket = await context.WebSockets.AcceptWebSocketAsync(subProtocol).ConfigureAwait(false)) using var webSocket = await context.WebSockets.AcceptWebSocketAsync(subProtocol).ConfigureAwait(false);
{ await adapter.RunWebSocketConnectionAsync(webSocket, context);
await adapter.RunWebSocketConnectionAsync(webSocket, context);
}
}); });
return app; return app;
@@ -4,13 +4,12 @@
using Microsoft.AspNetCore.Connections; 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,19 +6,17 @@ using System;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Routing; 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 =>
{ {
ArgumentNullException.ThrowIfNull(endpoints); options.WebSockets.SubProtocolSelector = MqttSubProtocolSelector.SelectSubProtocol;
});
endpoints.MapConnectionHandler<MqttConnectionHandler>(pattern, options =>
{
options.WebSockets.SubProtocolSelector = MqttSubProtocolSelector.SelectSubProtocol;
});
}
} }
} }
@@ -1,60 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<AssemblyName>MQTTnet.AspNetCore</AssemblyName> <AssemblyName>MQTTnet.AspNetCore</AssemblyName>
<RootNamespace>MQTTnet.AspNetCore</RootNamespace> <RootNamespace>MQTTnet.AspNetCore</RootNamespace>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild> <IsAotCompatible>true</IsAotCompatible>
<Company>The contributors of MQTTnet</Company>
<Product>MQTTnet</Product> <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> <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> <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> <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> <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> <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> </PropertyGroup>
<ItemGroup> <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"/> <FrameworkReference Include="Microsoft.AspNetCore.App"/>
</ItemGroup> </ItemGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release'">
</PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\MQTTnet.Server\MQTTnet.Server.csproj"/> <ProjectReference Include="..\MQTTnet.Server\MQTTnet.Server.csproj"/>
<ProjectReference Include="..\MQTTnet\MQTTnet.csproj"/> <ProjectReference Include="..\MQTTnet\MQTTnet.csproj"/>
@@ -7,28 +7,27 @@ using MQTTnet.Adapter;
using MQTTnet.Diagnostics.Logger; using MQTTnet.Diagnostics.Logger;
using MQTTnet.Formatter; 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)); case MqttClientTcpOptions tcpOptions:
switch (options.ChannelOptions)
{ {
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)); var formatter = new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(4096, 65535));
return new MqttConnectionContext(formatter, tcpConnection); return new MqttConnectionContext(formatter, tcpConnection);
} }
default: default:
{ {
throw new NotSupportedException(); throw new NotSupportedException();
}
} }
} }
} }
} }
@@ -61,27 +61,6 @@ public sealed class MqttConnectionContext : IMqttChannelAdapter
} }
} }
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 bool IsSecureConnection public bool IsSecureConnection
{ {
get 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 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) public async Task ConnectAsync(CancellationToken cancellationToken)
{ {
if (_connection is SocketConnection tcp && !tcp.IsConnected) if (_connection is SocketConnection tcp && !tcp.IsConnected)
@@ -232,8 +253,8 @@ public sealed class MqttConnectionContext : IMqttChannelAdapter
var span = output.GetSpan(buffer.Length); var span = output.GetSpan(buffer.Length);
buffer.Packet.AsSpan().CopyTo(span); buffer.Packet.AsSpan().CopyTo(span);
int offset = buffer.Packet.Count; var offset = buffer.Packet.Count;
buffer.Payload.CopyTo(destination: span.Slice(offset)); buffer.Payload.CopyTo(span.Slice(offset));
output.Advance(buffer.Length); output.Advance(buffer.Length);
} }
} }
@@ -33,13 +33,11 @@ public sealed class MqttConnectionHandler : ConnectionHandler, IMqttServerAdapte
} }
var formatter = new MqttPacketFormatterAdapter(new MqttBufferWriter(_serverOptions.WriterBufferSize, _serverOptions.WriterBufferSizeMax)); 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; await clientHandler(adapter).ConfigureAwait(false);
if (clientHandler != null)
{
await clientHandler(adapter).ConfigureAwait(false);
}
} }
} }
@@ -29,6 +29,6 @@ public static class MqttSubProtocolSelector
ArgumentNullException.ThrowIfNull(requestedSubProtocolValues); ArgumentNullException.ThrowIfNull(requestedSubProtocolValues);
// Order the protocols to also match "mqtt", "mqttv-3.1", "mqttv-3.11" etc. // 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));
} }
} }
@@ -43,10 +43,8 @@ public sealed class MqttWebSocketServerAdapter : IMqttServerAdapter
var formatter = new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535)); var formatter = new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535));
var channel = new MqttWebSocketChannel(webSocket, remoteEndPoint, isSecureConnection, clientCertificate); var channel = new MqttWebSocketChannel(webSocket, remoteEndPoint, isSecureConnection, clientCertificate);
using (var channelAdapter = new MqttChannelAdapter(channel, formatter, _logger)) using var channelAdapter = new MqttChannelAdapter(channel, formatter, _logger);
{ await clientHandler(channelAdapter).ConfigureAwait(false);
await clientHandler(channelAdapter).ConfigureAwait(false);
}
} }
} }
finally finally
+7 -13
View File
@@ -12,34 +12,28 @@ using System.Threading.Tasks;
namespace MQTTnet.AspNetCore; 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; int _bytesTransferred;
Action _callback; Action _callback;
SocketError _error; SocketError _error;
public SocketAwaitable(PipeScheduler ioScheduler) public bool IsCompleted => ReferenceEquals(_callback, CallbackCompleted);
{
_ioScheduler = ioScheduler;
}
public bool IsCompleted => ReferenceEquals(_callback, _callbackCompleted);
public void Complete(int bytesTransferred, SocketError socketError) public void Complete(int bytesTransferred, SocketError socketError)
{ {
_error = socketError; _error = socketError;
_bytesTransferred = bytesTransferred; _bytesTransferred = bytesTransferred;
var continuation = Interlocked.Exchange(ref _callback, _callbackCompleted); var continuation = Interlocked.Exchange(ref _callback, CallbackCompleted);
if (continuation != null) 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() public int GetResult()
{ {
Debug.Assert(ReferenceEquals(_callback, _callbackCompleted)); Debug.Assert(ReferenceEquals(_callback, CallbackCompleted));
_callback = null; _callback = null;
@@ -64,7 +58,7 @@ public class SocketAwaitable : ICriticalNotifyCompletion
public void OnCompleted(Action continuation) 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); Task.Run(continuation);
} }
+14 -8
View File
@@ -18,11 +18,11 @@ namespace MQTTnet.AspNetCore;
public sealed class SocketConnection : ConnectionContext public sealed class SocketConnection : ConnectionContext
{ {
readonly EndPoint _endPoint; readonly EndPoint _endPoint;
volatile bool _aborted; volatile bool _aborted;
IDuplexPipe _application; IDuplexPipe _application;
SocketReceiver _receiver; SocketReceiver _receiver;
SocketSender _sender; SocketSender _sender;
Socket _socket; Socket _socket;
public SocketConnection(EndPoint endPoint) public SocketConnection(EndPoint endPoint)
@@ -40,10 +40,13 @@ public sealed class SocketConnection : ConnectionContext
} }
public override string ConnectionId { get; set; } public override string ConnectionId { get; set; }
public override IFeatureCollection Features { get; } public override IFeatureCollection Features { get; }
public bool IsConnected { get; private set; } public bool IsConnected { get; private set; }
public override IDictionary<object, object> Items { get; set; } public override IDictionary<object, object> Items { get; set; }
public override IDuplexPipe Transport { get; set; } public override IDuplexPipe Transport { get; set; }
public override ValueTask DisposeAsync() public override ValueTask DisposeAsync()
@@ -78,7 +81,7 @@ public sealed class SocketConnection : ConnectionContext
IsConnected = true; IsConnected = true;
} }
Exception ConnectionAborted() static MqttCommunicationException ConnectionAborted()
{ {
return new MqttCommunicationException("Connection Aborted"); return new MqttCommunicationException("Connection Aborted");
} }
@@ -95,8 +98,7 @@ public sealed class SocketConnection : ConnectionContext
{ {
error = new MqttCommunicationException(ex); error = new MqttCommunicationException(ex);
} }
catch (SocketException ex) when (ex.SocketErrorCode == SocketError.OperationAborted || ex.SocketErrorCode == SocketError.ConnectionAborted || catch (SocketException ex) when (ex.SocketErrorCode is SocketError.OperationAborted or SocketError.ConnectionAborted or SocketError.Interrupted or SocketError.InvalidArgument)
ex.SocketErrorCode == SocketError.Interrupted || ex.SocketErrorCode == SocketError.InvalidArgument)
{ {
if (!_aborted) if (!_aborted)
{ {
@@ -123,10 +125,10 @@ public sealed class SocketConnection : ConnectionContext
{ {
if (_aborted) 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(); var flushTask = _application.Output.FlushAsync();
FlushResult result;
if (!flushTask.IsCompleted) if (!flushTask.IsCompleted)
{ {
await flushTask; result = await flushTask;
}
else
{
result = flushTask.GetAwaiter().GetResult();
} }
var result = flushTask.GetAwaiter().GetResult();
if (result.IsCompleted) if (result.IsCompleted)
{ {
// Pipe consumer is shut down, do we stop writing // Pipe consumer is shut down, do we stop writing
+6 -1
View File
@@ -8,7 +8,7 @@ using System.Net.Sockets;
namespace MQTTnet.AspNetCore; namespace MQTTnet.AspNetCore;
public sealed class SocketReceiver public sealed class SocketReceiver : IDisposable
{ {
readonly SocketAwaitable _awaitable; readonly SocketAwaitable _awaitable;
readonly SocketAsyncEventArgs _eventArgs = new(); readonly SocketAsyncEventArgs _eventArgs = new();
@@ -33,4 +33,9 @@ public sealed class SocketReceiver
return _awaitable; return _awaitable;
} }
public void Dispose()
{
_eventArgs.Dispose();
}
} }
+6 -1
View File
@@ -12,7 +12,7 @@ using System.Runtime.InteropServices;
namespace MQTTnet.AspNetCore; namespace MQTTnet.AspNetCore;
public sealed class SocketSender public sealed class SocketSender : IDisposable
{ {
readonly SocketAwaitable _awaitable; readonly SocketAwaitable _awaitable;
readonly SocketAsyncEventArgs _eventArgs = new(); readonly SocketAsyncEventArgs _eventArgs = new();
@@ -28,6 +28,11 @@ public sealed class SocketSender
_eventArgs.Completed += (_, e) => ((SocketAwaitable)e.UserToken).Complete(e.BytesTransferred, e.SocketError); _eventArgs.Completed += (_, e) => ((SocketAwaitable)e.UserToken).Complete(e.BytesTransferred, e.SocketError);
} }
public void Dispose()
{
_eventArgs.Dispose();
}
public SocketAwaitable SendAsync(in ReadOnlySequence<byte> buffers) public SocketAwaitable SendAsync(in ReadOnlySequence<byte> buffers)
{ {
if (buffers.IsSingleSegment) if (buffers.IsSingleSegment)
@@ -0,0 +1,3 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
[assembly: DoNotParallelize]
+42 -45
View File
@@ -9,57 +9,54 @@ using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
using MQTTnet.Internal; using MQTTnet.Internal;
namespace MQTTnet.Benchmarks namespace MQTTnet.Benchmarks;
[SimpleJob(RuntimeMoniker.Net60)]
[MemoryDiagnoser]
public class AsyncLockBenchmark : BaseBenchmark
{ {
[SimpleJob(RuntimeMoniker.Net60)] [Benchmark]
[MemoryDiagnoser] public async Task Synchronize_100_Tasks()
public class AsyncLockBenchmark : BaseBenchmark
{ {
[Benchmark] const int tasksCount = 100;
public async Task Synchronize_100_Tasks()
var tasks = new Task[tasksCount];
var asyncLock = new AsyncLock();
var globalI = 0;
for (var i = 0; i < tasksCount; i++)
{ {
const int tasksCount = 100; tasks[i] = Task.Run(
async () =>
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++)
{ {
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))
{
} }
} }
} }
+3 -4
View File
@@ -2,9 +2,8 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // 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.Packets;
using MQTTnet.Tests.Mockups; using MQTTnet.Tests.Mockups;
namespace MQTTnet.Benchmarks namespace MQTTnet.Benchmarks;
[MemoryDiagnoser]
public sealed class ChannelAdapterBenchmark : BaseBenchmark, IDisposable
{ {
[MemoryDiagnoser] MqttChannelAdapter _channelAdapter;
public sealed class ChannelAdapterBenchmark : BaseBenchmark int _iterations;
MqttPublishPacket _packet;
MemoryStream _stream;
[Benchmark]
public void Receive_10000_Messages()
{ {
MqttChannelAdapter _channelAdapter; _stream.Position = 0;
int _iterations;
MqttPublishPacket _packet;
MemoryStream _stream;
[Benchmark] for (var i = 0; i < 10000; i++)
public void Receive_10000_Messages()
{ {
_stream.Position = 0; _channelAdapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
for (var i = 0; i < 10000; i++)
{
_channelAdapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
}
_stream.Position = 0;
} }
[Benchmark] _stream.Position = 0;
public void Send_10000_Messages() }
[Benchmark]
public void Send_10000_Messages()
{
_stream.Position = 0;
for (var i = 0; i < 10000; i++)
{ {
_stream.Position = 0; _channelAdapter.SendPacketAsync(_packet, CancellationToken.None).GetAwaiter().GetResult();
for (var i = 0; i < 10000; i++)
{
_channelAdapter.SendPacketAsync(_packet, CancellationToken.None).GetAwaiter().GetResult();
}
_stream.Position = 0;
} }
[GlobalSetup] _stream.Position = 0;
public void Setup() }
[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++) for (var i = 0; i < _iterations; i++)
{ {
_stream.Write(serializedPacket, 0, serializedPacket.Length); _stream.Write(serializedPacket, 0, serializedPacket.Length);
}
_stream.Position = 0;
var channel = new MemoryMqttChannel(_stream);
_channelAdapter = new MqttChannelAdapter(channel, serializer, new MqttNetEventLogger());
} }
static byte[] Join(params ArraySegment<byte>[] chunks) _stream.Position = 0;
{
var buffer = new MemoryStream();
foreach (var chunk in chunks)
{
buffer.Write(chunk.Array, chunk.Offset, chunk.Count);
}
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();
} }
} }
+75 -75
View File
@@ -2,90 +2,90 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System.Diagnostics;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
using MQTTnet.Diagnostics.Logger; using MQTTnet.Diagnostics.Logger;
namespace MQTTnet.Benchmarks namespace MQTTnet.Benchmarks;
[SimpleJob(RuntimeMoniker.Net60)]
[RPlotExporter]
[MemoryDiagnoser]
public class LoggerBenchmark : BaseBenchmark
{ {
[SimpleJob(RuntimeMoniker.Net60)] MqttNetNullLogger _nullLogger;
[RPlotExporter] MqttNetSourceLogger _sourceNullLogger;
[MemoryDiagnoser]
public class LoggerBenchmark : BaseBenchmark MqttNetEventLogger _eventLogger;
MqttNetSourceLogger _sourceEventLogger;
MqttNetEventLogger _eventLoggerNoListener;
MqttNetSourceLogger _sourceEventLoggerNoListener;
bool _useHandler;
[GlobalSetup]
public void Setup()
{ {
MqttNetNullLogger _nullLogger; _nullLogger = new MqttNetNullLogger();
MqttNetSourceLogger _sourceNullLogger; _sourceNullLogger = _nullLogger.WithSource("Source");
MqttNetEventLogger _eventLogger; _eventLogger = new MqttNetEventLogger();
MqttNetSourceLogger _sourceEventLogger; _eventLogger.LogMessagePublished += OnLogMessagePublished;
_sourceEventLogger = _eventLogger.WithSource("Source");
MqttNetEventLogger _eventLoggerNoListener; _eventLoggerNoListener = new MqttNetEventLogger();
MqttNetSourceLogger _sourceEventLoggerNoListener; _sourceEventLoggerNoListener = _eventLoggerNoListener.WithSource("Source");
}
bool _useHandler; void OnLogMessagePublished(object sender, MqttNetLogMessagePublishedEventArgs eventArgs)
{
[GlobalSetup] if (_useHandler)
public void Setup()
{ {
_nullLogger = new MqttNetNullLogger(); Debug.WriteLine(eventArgs.LogMessage.ToString());
_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");
}
} }
} }
}
[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"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<DebugType>Full</DebugType> <DebugType>Full</DebugType>
<TargetFramework>net8.0</TargetFramework>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<EnableNETAnalyzers>false</EnableNETAnalyzers> <NoWarn>$(NoWarn);CS8892;CA1707;CA1051;CA1822</NoWarn>
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
<NoWarn>1591;NETSDK1138;NU1803;NU1901;NU1902;CS8892</NoWarn>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors> <TreatWarningsAsErrors>false</TreatWarningsAsErrors>
<NuGetAuditMode>all</NuGetAuditMode>
<NuGetAudit>true</NuGetAudit>
<NuGetAuditLevel>low</NuGetAuditLevel>
<AnalysisLevel>latest-Recommended</AnalysisLevel>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.14.0" /> <PackageReference Include="BenchmarkDotNet" Version="0.15.4" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -3,38 +3,38 @@ using BenchmarkDotNet.Jobs;
using System; using System;
namespace MQTTnet.Benchmarks namespace MQTTnet.Benchmarks;
[SimpleJob(RuntimeMoniker.Net60)]
[RPlotExporter, RankColumn]
[MemoryDiagnoser]
public class MemoryCopyBenchmark
{ {
[SimpleJob(RuntimeMoniker.Net60)] const int MaxLength = 1024 * 8;
[RPlotExporter, RankColumn]
[MemoryDiagnoser] byte[] _source;
public class MemoryCopyBenchmark 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; _source = new byte[MaxLength];
private byte[] source; _target = new byte[MaxLength];
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);
}
} }
[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. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // 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 BenchmarkDotNet.Attributes;
using MQTTnet.Packets; using MQTTnet.Packets;
using MQTTnet.Server; using MQTTnet.Server;
@@ -14,7 +9,7 @@ using MQTTnet.Server;
namespace MQTTnet.Benchmarks; namespace MQTTnet.Benchmarks;
[MemoryDiagnoser] [MemoryDiagnoser]
public class MessageDeliveryBenchmark : BaseBenchmark public sealed class MessageDeliveryBenchmark : BaseBenchmark, IDisposable
{ {
List<string> _allSubscribedTopics; // Keep track of the subset of topics that are subscribed List<string> _allSubscribedTopics; // Keep track of the subset of topics that are subscribed
CancellationTokenSource _cancellationTokenSource; CancellationTokenSource _cancellationTokenSource;
@@ -23,22 +18,19 @@ public class MessageDeliveryBenchmark : BaseBenchmark
int _messagesExpectedCount; int _messagesExpectedCount;
int _messagesReceivedCount; int _messagesReceivedCount;
Dictionary<string, IMqttClient> _mqttPublisherClientsByPublisherName; Dictionary<string, IMqttClient> _mqttPublisherClientsByPublisherName;
MqttServer _mqttServer; MqttServer _mqttServer;
List<IMqttClient> _mqttSubscriberClients; 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; Dictionary<string, string> _publisherByTopic;
List<MqttApplicationMessage> _topicPublishMessages;
Dictionary<string, List<string>> _topicsByPublisher; 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] [GlobalCleanup]
public void Cleanup() public void Cleanup()
{ {
@@ -64,14 +56,11 @@ public class MessageDeliveryBenchmark : BaseBenchmark
_mqttServer = null; _mqttServer = null;
} }
/// <summary>
/// Publish messages and wait for messages sent to subscribers
/// </summary>
[Benchmark] [Benchmark]
public void DeliverMessages() public void DeliverMessages()
{ {
// There should be one message received per publish for each subscribed topic // 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 // Loop for a while and exchange messages
@@ -82,8 +71,6 @@ public class MessageDeliveryBenchmark : BaseBenchmark
// same payload for all messages // same payload for all messages
var payload = new byte[] { 1, 2, 3, 4 }; var payload = new byte[] { 1, 2, 3, 4 };
var tasks = new List<Task>();
// publish a message for each subscribed topic // publish a message for each subscribed topic
foreach (var topic in _allSubscribedTopics) foreach (var topic in _allSubscribedTopics)
{ {
@@ -101,35 +88,29 @@ public class MessageDeliveryBenchmark : BaseBenchmark
} }
catch catch
{ {
// Ignore all errors.
} }
_cancellationTokenSource.Dispose(); _cancellationTokenSource.Dispose();
if (_messagesReceivedCount < _messagesExpectedCount) 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] [GlobalSetup]
public void Setup() public void Setup()
{ {
_lockMsgCount = new object(); _lockMsgCount = new object();
Dictionary<string, List<string>> singleWildcardTopicsByPublisher; TopicGenerator.Generate(_numPublishers, _numTopicsPerPublisher, out _topicsByPublisher, out _, out _);
Dictionary<string, List<string>> multiWildcardTopicsByPublisher;
TopicGenerator.Generate(NumPublishers, NumTopicsPerPublisher, out _topicsByPublisher, out singleWildcardTopicsByPublisher, out multiWildcardTopicsByPublisher);
var topics = _topicsByPublisher.First().Value;
_topicPublishMessages = new List<MqttApplicationMessage>();
// Prepare messages, same for each publisher
foreach (var topic in topics)
{
var message = new MqttApplicationMessageBuilder().WithTopic(topic).WithPayload(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 }).Build();
_topicPublishMessages.Add(message);
}
// Create server // Create server
var serverFactory = new MqttServerFactory(); var serverFactory = new MqttServerFactory();
@@ -139,7 +120,7 @@ public class MessageDeliveryBenchmark : BaseBenchmark
_mqttServer.StartAsync().GetAwaiter().GetResult(); _mqttServer.StartAsync().GetAwaiter().GetResult();
// Create publisher clients // Create publisher clients
_mqttPublisherClientsByPublisherName = new Dictionary<string, IMqttClient>(); _mqttPublisherClientsByPublisherName = [];
foreach (var pt in _topicsByPublisher) foreach (var pt in _topicsByPublisher)
{ {
var publisherName = pt.Key; var publisherName = pt.Key;
@@ -151,13 +132,13 @@ public class MessageDeliveryBenchmark : BaseBenchmark
// Create subscriber clients // Create subscriber clients
_mqttSubscriberClients = new List<IMqttClient>(); _mqttSubscriberClients = new List<IMqttClient>();
for (var i = 0; i < NumSubscribers; i++) for (var i = 0; i < _numSubscribers; i++)
{ {
var mqttSubscriberClient = clientFactory.CreateMqttClient(); var mqttSubscriberClient = clientFactory.CreateMqttClient();
_mqttSubscriberClients.Add(mqttSubscriberClient); _mqttSubscriberClients.Add(mqttSubscriberClient);
var subscriberOptions = new MqttClientOptionsBuilder().WithTcpServer("localhost").WithClientId("subscriber" + i).Build(); 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 // count messages and signal cancellation when expected message count is reached
lock (_lockMsgCount) lock (_lockMsgCount)
@@ -171,12 +152,13 @@ public class MessageDeliveryBenchmark : BaseBenchmark
return Task.CompletedTask; return Task.CompletedTask;
}; };
mqttSubscriberClient.ConnectAsync(subscriberOptions).GetAwaiter().GetResult(); mqttSubscriberClient.ConnectAsync(subscriberOptions).GetAwaiter().GetResult();
} }
var allTopics = new List<string>(); var allTopics = new List<string>();
_publisherByTopic = new Dictionary<string, string>(); _publisherByTopic = [];
foreach (var t in _topicsByPublisher) foreach (var t in _topicsByPublisher)
{ {
foreach (var topic in t.Value) foreach (var topic in t.Value)
@@ -189,21 +171,18 @@ public class MessageDeliveryBenchmark : BaseBenchmark
// Subscribe to NumSubscribedTopics topics spread across all topics // Subscribe to NumSubscribedTopics topics spread across all topics
_allSubscribedTopics = new List<string>(); _allSubscribedTopics = new List<string>();
var totalNumTopics = NumPublishers * NumTopicsPerPublisher; var totalNumTopics = _numPublishers * _numTopicsPerPublisher;
var topicIndexStep = totalNumTopics / (NumSubscribedTopicsPerSubscriber * NumSubscribers); var topicIndexStep = totalNumTopics / (_numSubscribedTopicsPerSubscriber * _numSubscribers);
if (topicIndexStep * NumSubscribedTopicsPerSubscriber * NumSubscribers != totalNumTopics) if (topicIndexStep * _numSubscribedTopicsPerSubscriber * _numSubscribers != totalNumTopics)
{ {
throw new Exception( throw new InvalidOperationException(
string.Format( $"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}");
"The total number of topics must be divisible by the number of subscribed topics across all subscribers. Total number of topics: {0}, topic step: {1}",
totalNumTopics,
topicIndexStep));
} }
var topicIndex = 0; var topicIndex = 0;
foreach (var mqttSubscriber in _mqttSubscriberClients) 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]; var topic = allTopics[topicIndex];
_allSubscribedTopics.Add(topic); _allSubscribedTopics.Add(topic);
@@ -9,65 +9,64 @@ using Microsoft.AspNetCore.Hosting;
using MQTTnet.AspNetCore; using MQTTnet.AspNetCore;
using MQTTnet.Diagnostics.Logger; using MQTTnet.Diagnostics.Logger;
namespace MQTTnet.Benchmarks namespace MQTTnet.Benchmarks;
[SimpleJob(RuntimeMoniker.Net60)]
[MemoryDiagnoser]
public class MessageProcessingMqttConnectionContextBenchmark : BaseBenchmark
{ {
[SimpleJob(RuntimeMoniker.Net60)] IWebHost _host;
[MemoryDiagnoser] IMqttClient _mqttClient;
public class MessageProcessingMqttConnectionContextBenchmark : BaseBenchmark MqttApplicationMessage _message;
[GlobalSetup]
public void Setup()
{ {
IWebHost _host; _host = WebHost.CreateDefaultBuilder()
IMqttClient _mqttClient; .UseKestrel(o => o.ListenAnyIP(1883, l => l.UseMqtt()))
MqttApplicationMessage _message; .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() _mqttClient.PublishAsync(_message).GetAwaiter().GetResult();
.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();
}
} }
} }
} }
@@ -8,36 +8,35 @@ using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
using MQTTnet.Formatter; using MQTTnet.Formatter;
namespace MQTTnet.Benchmarks namespace MQTTnet.Benchmarks;
[SimpleJob(RuntimeMoniker.Net60)]
[MemoryDiagnoser]
public class MqttBufferReaderBenchmark
{ {
[SimpleJob(RuntimeMoniker.Net60)] byte[] _buffer;
[MemoryDiagnoser] int _bufferLength;
public class MqttBufferReaderBenchmark
[GlobalSetup]
public void GlobalSetup()
{ {
byte[] _buffer; var writer = new MqttBufferWriter(1024, 1024);
int _bufferLength; 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] _buffer = writer.GetBuffer();
public void GlobalSetup() _bufferLength = writer.Length;
{ }
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(); [Benchmark]
_bufferLength = writer.Length; public void Use_Span()
} {
var span = _buffer.AsSpan(0, _bufferLength);
Encoding.UTF8.GetString(span);
}
[Benchmark] [Benchmark]
public void Use_Span() public void Use_Encoding()
{ {
var span = _buffer.AsSpan(0, _bufferLength); Encoding.UTF8.GetString(_buffer, 0, _bufferLength);
Encoding.UTF8.GetString(span);
}
[Benchmark]
public void Use_Encoding()
{
Encoding.UTF8.GetString(_buffer, 0, _bufferLength);
}
} }
} }
@@ -8,89 +8,88 @@ using BenchmarkDotNet.Jobs;
using MQTTnet.Formatter; using MQTTnet.Formatter;
using MQTTnet.Tests.Mockups; using MQTTnet.Tests.Mockups;
namespace MQTTnet.Benchmarks namespace MQTTnet.Benchmarks;
[SimpleJob(RuntimeMoniker.Net60)]
[MemoryDiagnoser]
public class MqttPacketReaderWriterBenchmark : BaseBenchmark
{ {
[SimpleJob(RuntimeMoniker.Net60)] readonly byte[] _demoPayload = new byte[1024];
[MemoryDiagnoser]
public class MqttPacketReaderWriterBenchmark : BaseBenchmark byte[] _readPayload;
[GlobalCleanup]
public void GlobalCleanup()
{ {
readonly byte[] _demoPayload = new byte[1024]; }
byte[] _readPayload;
[GlobalCleanup] [GlobalSetup]
public void GlobalCleanup() 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] [Benchmark]
public void GlobalSetup() 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.WriteString("A relative short string.");
writer.WriteBinary(_demoPayload);
writer.WriteByte(0x01); writer.WriteByte(0x01);
writer.WriteByte(0x02); writer.WriteByte(0x02);
writer.WriteVariableByteInteger(5647382); writer.WriteVariableByteInteger(5647382);
writer.WriteString("A relative short string."); writer.WriteString("A relative short string.");
writer.WriteVariableByteInteger(8574489); writer.WriteVariableByteInteger(8574589);
writer.WriteBinary(_demoPayload); writer.WriteBinary(_demoPayload);
writer.WriteByte(2); writer.WriteByte(2);
writer.WriteByte(0x02); writer.WriteByte(0x02);
writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug"); writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug");
writer.WriteBinary(_demoPayload); writer.WriteBinary(_demoPayload);
_readPayload = new ArraySegment<byte>(writer.GetBuffer(), 0, writer.Length).ToArray(); writer.Reset(0);
}
[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);
}
} }
} }
} }
@@ -2,11 +2,8 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System;
using System.Buffers; using System.Buffers;
using System.Reflection; using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
using MQTTnet.Channel; using MQTTnet.Channel;
@@ -19,9 +16,9 @@ namespace MQTTnet.Benchmarks;
[SimpleJob(RuntimeMoniker.Net60)] [SimpleJob(RuntimeMoniker.Net60)]
[MemoryDiagnoser] [MemoryDiagnoser]
public class MqttTcpChannelBenchmark : BaseBenchmark public sealed class MqttTcpChannelBenchmark : BaseBenchmark, IDisposable
{ {
IMqttChannel _clientChannel; MqttTcpChannel _clientChannel;
MqttServer _mqttServer; MqttServer _mqttServer;
IMqttChannel _serverChannel; IMqttChannel _serverChannel;
@@ -85,4 +82,11 @@ public class MqttTcpChannelBenchmark : BaseBenchmark
await _serverChannel.WriteAsync(buffer, true, CancellationToken.None).ConfigureAwait(false); await _serverChannel.WriteAsync(buffer, true, CancellationToken.None).ConfigureAwait(false);
} }
} }
public void Dispose()
{
_clientChannel?.Dispose();
_mqttServer?.Dispose();
_serverChannel?.Dispose();
}
} }
+102 -105
View File
@@ -5,138 +5,135 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using BenchmarkDotNet.Loggers;
using BenchmarkDotNet.Running; 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; _benchmarks = CollectBenchmarks();
static List<Type> _benchmarks; HandleArguments(arguments);
public static void Main(string[] arguments) while (true)
{ {
_benchmarks = CollectBenchmarks(); RenderMenu();
HandleArguments(arguments);
while (true) var key = Console.ReadKey(true);
if (key.Key == ConsoleKey.DownArrow)
{ {
RenderMenu(); _selectedBenchmarkIndex++;
if (_selectedBenchmarkIndex > _benchmarks.Count - 1)
var key = Console.ReadKey(true);
if (key.Key == ConsoleKey.DownArrow)
{ {
_selectedBenchmarkIndex++; _selectedBenchmarkIndex = 0;
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;
} }
} }
else if (key.Key == ConsoleKey.UpArrow)
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)
{ {
if (typeof(BaseBenchmark).IsAssignableFrom(type)) _selectedBenchmarkIndex--;
if (_selectedBenchmarkIndex < 0)
{ {
if (type != typeof(BaseBenchmark)) _selectedBenchmarkIndex = _benchmarks.Count - 1;
{
benchmarks.Add(type);
}
} }
} }
else if (key.Key == ConsoleKey.Escape)
return benchmarks.OrderBy(b => b.Name).ToList();
}
static void HandleArguments(string[] arguments)
{
if (arguments.Length == 0)
{ {
return; Environment.Exit(0);
} }
else if (key.Key == ConsoleKey.Enter)
// Allow for preselection to avoid developer frustration.
if (int.TryParse(arguments[0], out var benchmarkIndex))
{ {
_selectedBenchmarkIndex = benchmarkIndex; break;
return;
}
_selectedBenchmarkIndex = _benchmarks.FindIndex(b => b.Name.Equals(arguments[0]));
if (_selectedBenchmarkIndex < 0)
{
_selectedBenchmarkIndex = 0;
}
if (_selectedBenchmarkIndex > _benchmarks.Count - 1)
{
_selectedBenchmarkIndex = _benchmarks.Count - 1;
} }
} }
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(); if (typeof(BaseBenchmark).IsAssignableFrom(type))
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 (type != typeof(BaseBenchmark))
if (_selectedBenchmarkIndex == index)
{ {
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.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
using MQTTnet.Adapter;
using MQTTnet.AspNetCore; using MQTTnet.AspNetCore;
using MQTTnet.Exceptions; using MQTTnet.Exceptions;
using MQTTnet.Formatter; using MQTTnet.Formatter;
using MQTTnet.Packets;
using System;
using System.Buffers; using System.Buffers;
using System.IO; using System.IO;
using System.IO.Pipelines; using System.IO.Pipelines;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace MQTTnet.Benchmarks namespace MQTTnet.Benchmarks;
{
[SimpleJob(RuntimeMoniker.Net60)]
[RPlotExporter, RankColumn]
[MemoryDiagnoser]
public class ReaderExtensionsBenchmark
{
MqttPacketFormatterAdapter mqttPacketFormatter;
MemoryStream stream;
[GlobalSetup] [SimpleJob(RuntimeMoniker.Net60)]
public void GlobalSetup() [RPlotExporter, RankColumn]
{ [MemoryDiagnoser]
mqttPacketFormatter = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535)); public sealed class ReaderExtensionsBenchmark : IDisposable, IAsyncDisposable
var mqttMessage = new MqttApplicationMessageBuilder() {
MqttPacketFormatterAdapter _mqttPacketFormatter;
MemoryStream _stream;
[GlobalSetup]
public void GlobalSetup()
{
_mqttPacketFormatter = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535));
var mqttMessage = new MqttApplicationMessageBuilder()
.WithTopic("topic") .WithTopic("topic")
.WithPayload(new byte[10 * 1024]) .WithPayload(new byte[10 * 1024])
.Build(); .Build();
var packet = MqttPublishPacketFactory.Create(mqttMessage); 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)
{
ArgumentNullException.ThrowIfNull(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 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.Mockups;
using MQTTnet.Tests.Server; using MQTTnet.Tests.Server;
namespace MQTTnet.Benchmarks namespace MQTTnet.Benchmarks;
[SimpleJob(RuntimeMoniker.Net60)]
[RPlotExporter, RankColumn]
[MemoryDiagnoser]
public class RoundtripProcessingBenchmark : BaseBenchmark
{ {
[SimpleJob(RuntimeMoniker.Net60)] [GlobalSetup]
[RPlotExporter, RankColumn] public void GlobalSetup()
[MemoryDiagnoser]
public class RoundtripProcessingBenchmark : BaseBenchmark
{ {
[GlobalSetup] TestEnvironment.EnableLogger = false;
public void GlobalSetup() }
{
TestEnvironment.EnableLogger = false; [GlobalCleanup]
} public void GlobalCleanup()
{
[GlobalCleanup] }
public void GlobalCleanup()
{ [Benchmark]
} public void Handle_100_000_Messages_In_Receiving_Client()
{
[Benchmark] //new Load_Tests().Handle_100_000_Messages_In_Receiving_Client().GetAwaiter().GetResult();
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.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
using MQTTnet.Formatter; 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)] MqttPacketBuffer _buffer;
[RPlotExporter, RankColumn] MemoryStream _stream;
[MemoryDiagnoser]
public class SendPacketAsyncBenchmark : BaseBenchmark [Benchmark]
public async ValueTask After()
{ {
MemoryStream stream; _stream.Position = 0;
MqttPacketBuffer buffer; var output = PipeWriter.Create(_stream);
[GlobalSetup] if (_buffer.Payload.Length == 0)
public void GlobalSetup()
{ {
stream = new MemoryStream(1024); await output.WriteAsync(_buffer.Packet).ConfigureAwait(false);
var packet = new ArraySegment<byte>(new byte[10]);
buffer = new MqttPacketBuffer(packet);
} }
else
[Benchmark(Baseline = true)]
public async ValueTask Before()
{ {
stream.Position = 0; WritePacketBuffer(output, _buffer);
var output = PipeWriter.Create(stream); await output.FlushAsync().ConfigureAwait(false);
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);
} }
} }
[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);
}
} }
@@ -17,103 +17,104 @@ using MQTTnet.Diagnostics.Logger;
using System.Buffers; using System.Buffers;
using System.Net; using System.Net;
namespace MQTTnet.Benchmarks namespace MQTTnet.Benchmarks;
[SimpleJob(RuntimeMoniker.Net60)]
[RPlotExporter]
[MemoryDiagnoser]
public class SerializerBenchmark : BaseBenchmark
{ {
[SimpleJob(RuntimeMoniker.Net60)] MqttPacket _packet;
[RPlotExporter] ArraySegment<byte> _serializedPacket;
[MemoryDiagnoser] MqttV3PacketFormatter _serializer;
public class SerializerBenchmark : BaseBenchmark MqttBufferWriter _bufferWriter;
[GlobalSetup]
public void GlobalSetup()
{ {
MqttPacket _packet; _packet = new MqttPublishPacket
ArraySegment<byte> _serializedPacket;
IMqttPacketFormatter _serializer;
MqttBufferWriter _bufferWriter;
[GlobalSetup]
public void GlobalSetup()
{ {
_packet = new MqttPublishPacket Topic = "A"
{ };
Topic = "A"
};
_bufferWriter = new MqttBufferWriter(4096, 65535); _bufferWriter = new MqttBufferWriter(4096, 65535);
_serializer = new MqttV3PacketFormatter(_bufferWriter, MqttProtocolVersion.V311); _serializer = new MqttV3PacketFormatter(_bufferWriter, MqttProtocolVersion.V311);
_serializedPacket = _serializer.Encode(_packet).Join(); _serializedPacket = _serializer.Encode(_packet).Join();
} }
[Benchmark] [Benchmark]
public void Serialize_10000_Messages() public void Serialize10000Messages()
{
for (var i = 0; i < 10000; i++)
{ {
for (var i = 0; i < 10000; i++) _serializer.Encode(_packet);
{ _bufferWriter.Cleanup();
_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 EndPoint RemoteEndPoint { get; set; }
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()
{
}
} }
} }
}
[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.Mockups;
using MQTTnet.Tests.Server; using MQTTnet.Tests.Server;
namespace MQTTnet.Benchmarks namespace MQTTnet.Benchmarks;
[SimpleJob(RuntimeMoniker.Net60)]
[RPlotExporter, RankColumn]
[MemoryDiagnoser]
public class ServerProcessingBenchmark : BaseBenchmark
{ {
[SimpleJob(RuntimeMoniker.Net60)] [GlobalSetup]
[RPlotExporter, RankColumn] public void GlobalSetup()
[MemoryDiagnoser]
public class ServerProcessingBenchmark : BaseBenchmark
{ {
[GlobalSetup] TestEnvironment.EnableLogger = false;
public void GlobalSetup() }
{
TestEnvironment.EnableLogger = false; [GlobalCleanup]
} public void GlobalCleanup()
{
[GlobalCleanup] }
public void GlobalCleanup()
{ [Benchmark]
} public void Handle_100_000_Messages_In_Server_MqttClient()
{
[Benchmark] //new Load_Tests().Handle_100_000_Messages_In_Server().GetAwaiter().GetResult();
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()
{
//[Benchmark] //new Load_Tests().Handle_100_000_Messages_In_Low_Level_Client().GetAwaiter().GetResult();
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] [GlobalSetup]
public void Setup() 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(); _topics = topicsByPublisher.Values.First();
var serverOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build(); var serverOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
+60 -61
View File
@@ -11,70 +11,69 @@ using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
using MQTTnet.AspNetCore; using MQTTnet.AspNetCore;
namespace MQTTnet.Benchmarks namespace MQTTnet.Benchmarks;
[SimpleJob(RuntimeMoniker.Net60)]
[MemoryDiagnoser]
public class TcpPipesBenchmark : BaseBenchmark
{ {
[SimpleJob(RuntimeMoniker.Net60)] IDuplexPipe _client;
[MemoryDiagnoser] IDuplexPipe _server;
public class TcpPipesBenchmark : BaseBenchmark
[GlobalSetup]
public void Setup()
{ {
IDuplexPipe _client; var server = new TcpListener(IPAddress.Any, 1883);
IDuplexPipe _server;
[GlobalSetup] server.Start(1);
public void Setup()
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); var readresult = await input.ReadAsync(CancellationToken.None).ConfigureAwait(false);
input.AdvanceTo(readresult.Buffer.End);
server.Start(1); read += readresult.Buffer.Length;
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);
}
} }
} }
}
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,269 +2,32 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
namespace MQTTnet.Benchmarks namespace MQTTnet.Benchmarks;
[SimpleJob(RuntimeMoniker.Net60)]
[RPlotExporter]
[MemoryDiagnoser]
public class TopicFilterComparerBenchmark : BaseBenchmark
{ {
[SimpleJob(RuntimeMoniker.Net60)] readonly string _longTopic =
[RPlotExporter] "AAAAAAAAAAAAAssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssshhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC";
[MemoryDiagnoser]
public class TopicFilterComparerBenchmark : BaseBenchmark [Benchmark]
public void MqttTopicFilterComparer_10000_LoopMethod()
{ {
static readonly char[] TopicLevelSeparator = { '/' }; for (var i = 0; i < 100000; i++)
readonly string _longTopic =
"AAAAAAAAAAAAAssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssshhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC";
[Benchmark]
public void MqttTopicFilterComparer_10000_LoopMethod()
{ {
for (var i = 0; i < 100000; i++) MqttTopicFilterComparer.Compare("sport/tennis/player1", "sport/#");
{ MqttTopicFilterComparer.Compare("sport/tennis/player1/ranking", "sport/#/ranking");
MqttTopicFilterComparer.Compare("sport/tennis/player1", "sport/#"); MqttTopicFilterComparer.Compare("sport/tennis/player1/score/wimbledon", "sport/+/player1/#");
MqttTopicFilterComparer.Compare("sport/tennis/player1/ranking", "sport/#/ranking"); MqttTopicFilterComparer.Compare("sport/tennis/player1", "sport/tennis/+");
MqttTopicFilterComparer.Compare("sport/tennis/player1/score/wimbledon", "sport/+/player1/#"); MqttTopicFilterComparer.Compare("/finance", "+/+");
MqttTopicFilterComparer.Compare("sport/tennis/player1", "sport/tennis/+"); MqttTopicFilterComparer.Compare("/finance", "/+");
MqttTopicFilterComparer.Compare("/finance", "+/+"); MqttTopicFilterComparer.Compare("/finance", "+");
MqttTopicFilterComparer.Compare("/finance", "/+"); MqttTopicFilterComparer.Compare(_longTopic, _longTopic);
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)
{
ArgumentNullException.ThrowIfNull(topic);
ArgumentNullException.ThrowIfNull(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;
}
} }
} }
} }
+62 -63
View File
@@ -1,84 +1,83 @@
using System; using System;
using System.Collections.Generic; 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( topicsByPublisher = new Dictionary<string, List<string>>();
int numPublishers, singleWildcardTopicsByPublisher = new Dictionary<string, List<string>>();
int numTopicsPerPublisher, multiWildcardTopicsByPublisher = new Dictionary<string, List<string>>();
out Dictionary<string, List<string>> topicsByPublisher,
out Dictionary<string, List<string>> singleWildcardTopicsByPublisher, // Find some reasonable distribution across three topic levels
out Dictionary<string, List<string>> multiWildcardTopicsByPublisher) var topicsPerLevel = (int)Math.Pow(numTopicsPerPublisher, 1.0 / 3.0);
if (topicsPerLevel <= 0)
{ {
topicsByPublisher = new Dictionary<string, List<string>>(); topicsPerLevel = 1;
singleWildcardTopicsByPublisher = new Dictionary<string, List<string>>(); }
multiWildcardTopicsByPublisher = new Dictionary<string, List<string>>();
// Find some reasonable distribution across three topic levels var numLevel1Topics = topicsPerLevel;
var topicsPerLevel = (int)Math.Pow(numTopicsPerPublisher, 1.0 / 3.0); var numLevel2Topics = topicsPerLevel;
if (topicsPerLevel <= 0)
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; for (var l2 = 0; l2 < numLevel2Topics; ++l2)
}
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 l3 = 0; l3 < maxNumLevel3Topics; ++l3)
{ {
for (var l3 = 0; l3 < maxNumLevel3Topics; ++l3) if (publisherTopicCount >= numTopicsPerPublisher)
{ {
if (publisherTopicCount >= numTopicsPerPublisher) break;
{
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;
} }
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; topicList = new List<string>();
if (!topicsByPublisher.TryGetValue(publisherName, out topicList)) topicsByPublisher.Add(publisherName, topicList);
{
topicList = new List<string>();
topicsByPublisher.Add(publisherName, topicList);
}
topicList.Add(topic);
} }
topicList.Add(topic);
} }
} }
@@ -14,15 +14,15 @@ public class UnsubscribeBenchmark : BaseBenchmark
{ {
const int NumPublishers = 1; const int NumPublishers = 1;
const int NumTopicsPerPublisher = 10000; const int NumTopicsPerPublisher = 10000;
IMqttClient _mqttClient; IMqttClient _mqttClient;
MqttServer _mqttServer; MqttServer _mqttServer;
List<string> _topics; List<string> _topics;
[GlobalSetup] [GlobalSetup]
public void Setup() 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(); _topics = topicsByPublisher.Values.First();
var serverOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build(); var serverOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
@@ -4,27 +4,26 @@
using System; 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('#'))
{ {
ArgumentNullException.ThrowIfNull(context); throw new ArgumentException("The method name cannot contain /, + or #.");
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
};
} }
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 System.Threading.Tasks;
using MQTTnet.Protocol; 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(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(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. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // 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"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<AssemblyName>MQTTnet.Extensions.Rpc</AssemblyName> <AssemblyName>MQTTnet.Extensions.Rpc</AssemblyName>
<RootNamespace>MQTTnet.Extensions.Rpc</RootNamespace> <RootNamespace>MQTTnet.Extensions.Rpc</RootNamespace>
<IsAotCompatible>true</IsAotCompatible>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild> <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> <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> <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> <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> <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> <PackageReleaseNotes>For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).</PackageReleaseNotes>
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <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> </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> <ItemGroup>
<ProjectReference Include="..\MQTTnet\MQTTnet.csproj"/> <ProjectReference Include="..\MQTTnet\MQTTnet.csproj"/>
</ItemGroup> </ItemGroup>
@@ -4,26 +4,25 @@
using System; 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,
return clientFactory.CreateMqttRpcClient( new MqttRpcClientOptions
mqttClient, {
new MqttRpcClientOptions TopicGenerationStrategy = new DefaultMqttRpcClientTopicGenerationStrategy()
{ });
TopicGenerationStrategy = new DefaultMqttRpcClientTopicGenerationStrategy() }
});
}
public static IMqttRpcClient CreateMqttRpcClient(this MqttClientFactory _, IMqttClient mqttClient, MqttRpcClientOptions rpcClientOptions) public static IMqttRpcClient CreateMqttRpcClient(this MqttClientFactory _, IMqttClient mqttClient, MqttRpcClientOptions rpcClientOptions)
{ {
ArgumentNullException.ThrowIfNull(mqttClient); ArgumentNullException.ThrowIfNull(mqttClient);
ArgumentNullException.ThrowIfNull(rpcClientOptions); ArgumentNullException.ThrowIfNull(rpcClientOptions);
return new MqttRpcClient(mqttClient, rpcClientOptions); return new MqttRpcClient(mqttClient, rpcClientOptions);
}
} }
} }
+106 -110
View File
@@ -6,7 +6,6 @@ using System;
using System.Buffers; using System.Buffers;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Exceptions; using MQTTnet.Exceptions;
@@ -14,128 +13,125 @@ using MQTTnet.Formatter;
using MQTTnet.Internal; using MQTTnet.Internal;
using MQTTnet.Protocol; 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; _mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
readonly MqttRpcClientOptions _options; _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)); tcs.Value.TrySetCanceled();
_options = options ?? throw new ArgumentNullException(nameof(options));
_mqttClient.ApplicationMessageReceivedAsync += HandleApplicationMessageReceivedAsync;
} }
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; return await ExecuteAsync(methodName, payload, qualityOfServiceLevel, parameters, timeoutToken.Token).ConfigureAwait(false);
foreach (var tcs in _waitingCalls)
{
tcs.Value.TrySetCanceled();
}
_waitingCalls.Clear();
} }
catch (OperationCanceledException exception)
public async Task<byte[]> ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary<string, object> parameters = null)
{ {
using (var timeoutToken = new CancellationTokenSource(timeout)) if (timeoutToken.IsCancellationRequested)
{ {
try throw new MqttCommunicationTimedOutException(exception);
{
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)
{
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;
{
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;
} }
} }
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 System.Threading.Tasks;
using MQTTnet.Protocol; 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) ArgumentNullException.ThrowIfNull(client);
{
if (client == null) throw new ArgumentNullException(nameof(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. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // 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; 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(); return new MqttRpcClientOptions
public MqttRpcClientOptions Build()
{ {
return new MqttRpcClientOptions TopicGenerationStrategy = _topicGenerationStrategy
{ };
TopicGenerationStrategy = _topicGenerationStrategy }
};
}
public MqttRpcClientOptionsBuilder WithTopicGenerationStrategy(IMqttRpcClientTopicGenerationStrategy value) public MqttRpcClientOptionsBuilder WithTopicGenerationStrategy(IMqttRpcClientTopicGenerationStrategy value)
{ {
_topicGenerationStrategy = value ?? throw new ArgumentNullException(nameof(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. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
namespace MQTTnet.Extensions.Rpc namespace MQTTnet.Extensions.Rpc;
{
public sealed class MqttRpcTopicPair
{
public string RequestTopic { get; set; }
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 System.Collections.Generic;
using MQTTnet.Protocol; 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, MethodName = methodName ?? throw new ArgumentNullException(nameof(methodName));
IDictionary<string, object> parameters, MqttQualityOfServiceLevel qualityOfServiceLevel) Parameters = parameters;
{ QualityOfServiceLevel = qualityOfServiceLevel;
MethodName = methodName ?? throw new ArgumentNullException(nameof(methodName)); MqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
Parameters = parameters; Options = options ?? throw new ArgumentNullException(nameof(options));
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; }
} }
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"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<AssemblyName>MQTTnet.Extensions.TopicTemplate</AssemblyName> <AssemblyName>MQTTnet.Extensions.TopicTemplate</AssemblyName>
<RootNamespace>MQTTnet.Extensions.TopicTemplate</RootNamespace> <RootNamespace>MQTTnet.Extensions.TopicTemplate</RootNamespace>
<IsAotCompatible>true</IsAotCompatible>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild> <GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<Company>The contributors of MQTTnet</Company>
<Product>MQTTnet</Product>
<Description> <Description>
Provides mqtt topic templating logic to support dispatch, Provides mqtt topic templating logic to support dispatch,
routing and similar functionality based on the well known moustache syntax (AsyncAPI compatible). routing and similar functionality based on the well known moustache syntax (AsyncAPI compatible).
</Description> </Description>
<PackageReadmeFile>README.md</PackageReadmeFile> <PackageReadmeFile>README.md</PackageReadmeFile>
<Authors>The contributors of MQTTnet</Authors>
<PackageId>MQTTnet.Extensions.TopicTemplate</PackageId> <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> <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> <PackageReleaseNotes>For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).</PackageReleaseNotes>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>1591;NETSDK1138</NoWarn>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<None Include="README.md"> <PackageReference Include="System.ValueTuple" Version="4.6.1" />
<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" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -84,13 +84,13 @@ public sealed class MqttTopicTemplate : IEquatable<MqttTopicTemplate>
{ {
var filter = TopicFilter; var filter = TopicFilter;
// append slash if neccessary // 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; filter += MqttTopicFilterComparer.LevelSeparator;
} }
// append hash if neccessary // append hash if neccessary
if (!filter.EndsWith(MqttTopicFilterComparer.MultiLevelWildcard.ToString())) if (!filter.EndsWith(MqttTopicFilterComparer.MultiLevelWildcard))
{ {
filter += MqttTopicFilterComparer.MultiLevelWildcard; filter += MqttTopicFilterComparer.MultiLevelWildcard;
} }
@@ -161,12 +161,12 @@ public sealed class MqttTopicTemplate : IEquatable<MqttTopicTemplate>
return new MqttTopicTemplate(MqttTopicFilterComparer.MultiLevelWildcard.ToString()); 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); 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; root += MqttTopicFilterComparer.SingleLevelWildcard;
} }
@@ -230,7 +230,7 @@ public sealed class MqttTopicTemplate : IEquatable<MqttTopicTemplate>
throw new ArgumentException("the topic has to match this template", nameof(topic)); throw new ArgumentException("the topic has to match this template", nameof(topic));
} }
return parseParameterValuesInternal(topic); return ParseParameterValuesInternal(topic);
} }
/// <summary> /// <summary>
@@ -330,7 +330,7 @@ public sealed class MqttTopicTemplate : IEquatable<MqttTopicTemplate>
return parameters.Aggregate(this, (t, p) => t.TrySetParameter(p.parameter, p.value)); 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 // because we have a match, we know the segment array is at least the template's length
var segments = topic.Split(MqttTopicFilterComparer.LevelSeparator); 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);
}
}
+197
View File
@@ -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;
}
}
+396
View File
@@ -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.
+25
View File
@@ -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
+117
View File
@@ -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.Packets;
using MQTTnet.Protocol; 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> /// <summary>
/// The reason string is sent to every client via a DISCONNECT packet. /// The reason string is sent to every client via a DISCONNECT packet.
/// <remarks>MQTT 5.0.0+ feature.</remarks> /// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary> /// </summary>
public string ReasonString { get; set; } public string ReasonString { get; set; }
/// <summary> /// <summary>
/// The server reference is sent to every client via a DISCONNECT packet. /// The server reference is sent to every client via a DISCONNECT packet.
/// <remarks>MQTT 5.0.0+ feature.</remarks> /// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary> /// </summary>
public string ServerReference { get; set; } public string ServerReference { get; set; }
/// <summary> /// <summary>
/// These user properties are sent to every client via a DISCONNECT packet. /// These user properties are sent to every client via a DISCONNECT packet.
/// <remarks>MQTT 5.0.0+ feature.</remarks> /// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary> /// </summary>
public List<MqttUserProperty> UserProperties { get; set; } public List<MqttUserProperty> UserProperties { get; set; }
}
} }
@@ -5,50 +5,49 @@
using MQTTnet.Packets; using MQTTnet.Packets;
using MQTTnet.Protocol; 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.UserProperties.Add(new MqttUserProperty(name, value));
{ return this;
_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;
}
} }
} }
@@ -53,10 +53,7 @@ public sealed class ExchangeEnhancedAuthenticationOptionsFactory
public ExchangeEnhancedAuthenticationOptionsFactory WithUserProperty(string name, string value) public ExchangeEnhancedAuthenticationOptionsFactory WithUserProperty(string name, string value)
{ {
if (name == null) ArgumentNullException.ThrowIfNull(name);
{
throw new ArgumentNullException(nameof(name));
}
if (_options.UserProperties == null) if (_options.UserProperties == null)
{ {
@@ -2,26 +2,23 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // 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));
SenderClientId = senderClientId ?? throw new ArgumentNullException( nameof(senderClientId)); ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
ReceiverClientId = receiverClientId ?? throw new ArgumentNullException(nameof(receiverClientId)); IsDropped = isDropped;
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; }
} }
public string SenderClientId { get; }
public string ReceiverClientId { get; }
public bool IsDropped { get; }
public MqttApplicationMessage ApplicationMessage { get; }
} }
@@ -2,26 +2,23 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System; namespace MQTTnet.Server;
namespace MQTTnet.Server public sealed class ApplicationMessageNotConsumedEventArgs : EventArgs
{ {
public sealed class ApplicationMessageNotConsumedEventArgs : EventArgs public ApplicationMessageNotConsumedEventArgs(MqttApplicationMessage applicationMessage, string senderId)
{ {
public ApplicationMessageNotConsumedEventArgs(MqttApplicationMessage applicationMessage, string senderId) ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
{ SenderId = senderId;
ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
SenderId = senderId;
}
/// <summary>
/// Gets the application message which was not consumed by any client.
/// </summary>
public MqttApplicationMessage ApplicationMessage { get; }
/// <summary>
/// Gets the ID of the client which has sent the affected application message.
/// </summary>
public string SenderId { get; }
} }
/// <summary>
/// Gets the application message which was not consumed by any client.
/// </summary>
public MqttApplicationMessage ApplicationMessage { get; }
/// <summary>
/// Gets the ID of the client which has sent the affected application message.
/// </summary>
public string SenderId { get; }
} }
@@ -2,51 +2,49 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System;
using System.Collections; using System.Collections;
using MQTTnet.Packets; using MQTTnet.Packets;
namespace MQTTnet.Server namespace MQTTnet.Server;
public sealed class ClientAcknowledgedPublishPacketEventArgs : EventArgs
{ {
public sealed class ClientAcknowledgedPublishPacketEventArgs : EventArgs public ClientAcknowledgedPublishPacketEventArgs(string clientId, string userName, IDictionary sessionItems, MqttPublishPacket publishPacket, MqttPacketWithIdentifier acknowledgePacket)
{ {
public ClientAcknowledgedPublishPacketEventArgs(string clientId, string userName, IDictionary sessionItems, MqttPublishPacket publishPacket, MqttPacketWithIdentifier acknowledgePacket) ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
{ UserName = userName;
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId)); SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
UserName = userName; PublishPacket = publishPacket ?? throw new ArgumentNullException(nameof(publishPacket));
SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems)); AcknowledgePacket = acknowledgePacket ?? throw new ArgumentNullException(nameof(acknowledgePacket));
PublishPacket = publishPacket ?? throw new ArgumentNullException(nameof(publishPacket));
AcknowledgePacket = acknowledgePacket ?? throw new ArgumentNullException(nameof(acknowledgePacket));
}
/// <summary>
/// Gets the packet which was used for acknowledge. This can be a PubAck or PubComp packet.
/// </summary>
public MqttPacketWithIdentifier AcknowledgePacket { get; }
/// <summary>
/// Gets the ID of the client which acknowledged a PUBLISH packet.
/// </summary>
public string ClientId { get; }
/// <summary>
/// Gets the user name of the client.
/// </summary>
public string UserName { get; }
/// <summary>
/// Gets whether the PUBLISH packet is fully acknowledged. This is the case for PUBACK (QoS 1) and PUBCOMP (QoS 2.
/// </summary>
public bool IsCompleted => AcknowledgePacket is MqttPubAckPacket || AcknowledgePacket is MqttPubCompPacket;
/// <summary>
/// Gets the PUBLISH packet which was acknowledged.
/// </summary>
public MqttPublishPacket PublishPacket { get; }
/// <summary>
/// Gets the session items which contain custom user data per session.
/// </summary>
public IDictionary SessionItems { get; }
} }
/// <summary>
/// Gets the packet which was used for acknowledge. This can be a PubAck or PubComp packet.
/// </summary>
public MqttPacketWithIdentifier AcknowledgePacket { get; }
/// <summary>
/// Gets the ID of the client which acknowledged a PUBLISH packet.
/// </summary>
public string ClientId { get; }
/// <summary>
/// Gets the user name of the client.
/// </summary>
public string UserName { get; }
/// <summary>
/// Gets whether the PUBLISH packet is fully acknowledged. This is the case for PUBACK (QoS 1) and PUBCOMP (QoS 2.
/// </summary>
public bool IsCompleted => AcknowledgePacket is MqttPubAckPacket || AcknowledgePacket is MqttPubCompPacket;
/// <summary>
/// Gets the PUBLISH packet which was acknowledged.
/// </summary>
public MqttPublishPacket PublishPacket { get; }
/// <summary>
/// Gets the session items which contain custom user data per session.
/// </summary>
public IDictionary SessionItems { get; }
} }
@@ -2,70 +2,67 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System;
using System.Collections; using System.Collections;
using System.Collections.Generic;
using System.Net; using System.Net;
using System.Text; using System.Text;
using MQTTnet.Formatter; using MQTTnet.Formatter;
using MQTTnet.Packets; using MQTTnet.Packets;
namespace MQTTnet.Server namespace MQTTnet.Server;
public sealed class ClientConnectedEventArgs : EventArgs
{ {
public sealed class ClientConnectedEventArgs : EventArgs readonly MqttConnectPacket _connectPacket;
public ClientConnectedEventArgs(MqttConnectPacket connectPacket, MqttProtocolVersion protocolVersion, EndPoint remoteEndPoint, IDictionary sessionItems)
{ {
readonly MqttConnectPacket _connectPacket; _connectPacket = connectPacket ?? throw new ArgumentNullException(nameof(connectPacket));
ProtocolVersion = protocolVersion;
public ClientConnectedEventArgs(MqttConnectPacket connectPacket, MqttProtocolVersion protocolVersion, EndPoint remoteEndPoint, IDictionary sessionItems) RemoteEndPoint = remoteEndPoint;
{ SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
_connectPacket = connectPacket ?? throw new ArgumentNullException(nameof(connectPacket));
ProtocolVersion = protocolVersion;
RemoteEndPoint = remoteEndPoint;
SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
}
public byte[] AuthenticationData => _connectPacket.AuthenticationData;
public string AuthenticationMethod => _connectPacket.AuthenticationMethod;
/// <summary>
/// Gets the client identifier of the connected client.
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
/// </summary>
public string ClientId => _connectPacket.ClientId;
/// <summary>
/// Gets the endpoint of the connected client.
/// </summary>
public EndPoint RemoteEndPoint { get; }
[Obsolete("Use RemoteEndPoint instead.")]
public string Endpoint => RemoteEndPoint?.ToString();
/// <summary>
/// Gets the protocol version which is used by the connected client.
/// </summary>
public MqttProtocolVersion ProtocolVersion { get; }
/// <summary>
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
/// </summary>
public IDictionary SessionItems { get; }
/// <summary>
/// Gets the user name of the connected client.
/// </summary>
public string UserName => _connectPacket.Username;
/// <summary>
/// Gets the password of the connected client.
/// </summary>
public string Password => Encoding.UTF8.GetString(_connectPacket.Password.AsSpan());
/// <summary>
/// Gets the user properties sent by the client.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public List<MqttUserProperty> UserProperties => _connectPacket?.UserProperties;
} }
public byte[] AuthenticationData => _connectPacket.AuthenticationData;
public string AuthenticationMethod => _connectPacket.AuthenticationMethod;
/// <summary>
/// Gets the client identifier of the connected client.
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
/// </summary>
public string ClientId => _connectPacket.ClientId;
/// <summary>
/// Gets the endpoint of the connected client.
/// </summary>
public EndPoint RemoteEndPoint { get; }
[Obsolete("Use RemoteEndPoint instead.")]
public string Endpoint => RemoteEndPoint?.ToString();
/// <summary>
/// Gets the protocol version which is used by the connected client.
/// </summary>
public MqttProtocolVersion ProtocolVersion { get; }
/// <summary>
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
/// </summary>
public IDictionary SessionItems { get; }
/// <summary>
/// Gets the user name of the connected client.
/// </summary>
public string UserName => _connectPacket.Username;
/// <summary>
/// Gets the password of the connected client.
/// </summary>
public string Password => Encoding.UTF8.GetString(_connectPacket.Password.AsSpan());
/// <summary>
/// Gets the user properties sent by the client.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public List<MqttUserProperty> UserProperties => _connectPacket?.UserProperties;
} }
@@ -2,91 +2,88 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System;
using System.Collections; using System.Collections;
using System.Collections.Generic;
using System.Net; using System.Net;
using System.Text; using System.Text;
using MQTTnet.Packets; using MQTTnet.Packets;
using MQTTnet.Protocol; using MQTTnet.Protocol;
namespace MQTTnet.Server namespace MQTTnet.Server;
public sealed class ClientDisconnectedEventArgs : EventArgs
{ {
public sealed class ClientDisconnectedEventArgs : EventArgs readonly MqttConnectPacket _connectPacket;
readonly MqttDisconnectPacket _disconnectPacket;
public ClientDisconnectedEventArgs(
MqttConnectPacket connectPacket,
MqttDisconnectPacket disconnectPacket,
MqttClientDisconnectType disconnectType,
EndPoint remoteEndPoint,
IDictionary sessionItems)
{ {
readonly MqttConnectPacket _connectPacket; DisconnectType = disconnectType;
readonly MqttDisconnectPacket _disconnectPacket; RemoteEndPoint = remoteEndPoint;
SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
public ClientDisconnectedEventArgs( _connectPacket = connectPacket;
MqttConnectPacket connectPacket, // The DISCONNECT packet can be null in case of a non clean disconnect or session takeover.
MqttDisconnectPacket disconnectPacket, _disconnectPacket = disconnectPacket;
MqttClientDisconnectType disconnectType,
EndPoint remoteEndPoint,
IDictionary sessionItems)
{
DisconnectType = disconnectType;
RemoteEndPoint = remoteEndPoint;
SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
_connectPacket = connectPacket;
// The DISCONNECT packet can be null in case of a non clean disconnect or session takeover.
_disconnectPacket = disconnectPacket;
}
/// <summary>
/// Gets the client identifier.
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
/// </summary>
public string ClientId => _connectPacket.ClientId;
/// <summary>
/// Gets the user name of the client.
/// </summary>
public string UserName => _connectPacket.Username;
/// <summary>
/// Gets the password of the client.
/// </summary>
public string Password => Encoding.UTF8.GetString(_connectPacket.Password.AsSpan());
public MqttClientDisconnectType DisconnectType { get; }
public EndPoint RemoteEndPoint { get; }
[Obsolete("Use RemoteEndPoint instead.")]
public string Endpoint => RemoteEndPoint?.ToString();
/// <summary>
/// Gets the reason code sent by the client.
/// Only available for clean disconnects.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public MqttDisconnectReasonCode? ReasonCode => _disconnectPacket?.ReasonCode;
/// <summary>
/// Gets the reason string sent by the client.
/// Only available for clean disconnects.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public string ReasonString => _disconnectPacket?.ReasonString;
/// <summary>
/// Gets the session expiry interval sent by the client.
/// Only available for clean disconnects.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public uint SessionExpiryInterval => _disconnectPacket?.SessionExpiryInterval ?? 0;
/// <summary>
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
/// </summary>
public IDictionary SessionItems { get; }
/// <summary>
/// Gets the user properties sent by the client.
/// Only available for clean disconnects.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public List<MqttUserProperty> UserProperties => _disconnectPacket?.UserProperties;
} }
/// <summary>
/// Gets the client identifier.
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
/// </summary>
public string ClientId => _connectPacket.ClientId;
/// <summary>
/// Gets the user name of the client.
/// </summary>
public string UserName => _connectPacket.Username;
/// <summary>
/// Gets the password of the client.
/// </summary>
public string Password => Encoding.UTF8.GetString(_connectPacket.Password.AsSpan());
public MqttClientDisconnectType DisconnectType { get; }
public EndPoint RemoteEndPoint { get; }
[Obsolete("Use RemoteEndPoint instead.")]
public string Endpoint => RemoteEndPoint?.ToString();
/// <summary>
/// Gets the reason code sent by the client.
/// Only available for clean disconnects.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public MqttDisconnectReasonCode? ReasonCode => _disconnectPacket?.ReasonCode;
/// <summary>
/// Gets the reason string sent by the client.
/// Only available for clean disconnects.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public string ReasonString => _disconnectPacket?.ReasonString;
/// <summary>
/// Gets the session expiry interval sent by the client.
/// Only available for clean disconnects.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public uint SessionExpiryInterval => _disconnectPacket?.SessionExpiryInterval ?? 0;
/// <summary>
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
/// </summary>
public IDictionary SessionItems { get; }
/// <summary>
/// Gets the user properties sent by the client.
/// Only available for clean disconnects.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public List<MqttUserProperty> UserProperties => _disconnectPacket?.UserProperties;
} }
@@ -2,42 +2,40 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System;
using System.Collections; using System.Collections;
using MQTTnet.Packets; using MQTTnet.Packets;
namespace MQTTnet.Server namespace MQTTnet.Server;
public sealed class ClientSubscribedTopicEventArgs : EventArgs
{ {
public sealed class ClientSubscribedTopicEventArgs : EventArgs public ClientSubscribedTopicEventArgs(string clientId, string userName, MqttTopicFilter topicFilter, IDictionary sessionItems)
{ {
public ClientSubscribedTopicEventArgs(string clientId, string userName, MqttTopicFilter topicFilter, IDictionary sessionItems) ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
{ UserName = userName;
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId)); TopicFilter = topicFilter ?? throw new ArgumentNullException(nameof(topicFilter));
UserName = userName; SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
TopicFilter = topicFilter ?? throw new ArgumentNullException(nameof(topicFilter));
SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
}
/// <summary>
/// Gets the client identifier.
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
/// </summary>
public string ClientId { get; }
/// <summary>
/// Gets the user name of the client.
/// </summary>
public string UserName { get; }
/// <summary>
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
/// </summary>
public IDictionary SessionItems { get; }
/// <summary>
/// Gets the topic filter.
/// The topic filter can contain topics and wildcards.
/// </summary>
public MqttTopicFilter TopicFilter { get; }
} }
/// <summary>
/// Gets the client identifier.
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
/// </summary>
public string ClientId { get; }
/// <summary>
/// Gets the user name of the client.
/// </summary>
public string UserName { get; }
/// <summary>
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
/// </summary>
public IDictionary SessionItems { get; }
/// <summary>
/// Gets the topic filter.
/// The topic filter can contain topics and wildcards.
/// </summary>
public MqttTopicFilter TopicFilter { get; }
} }

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