Compare commits

..
6 Commits
Author SHA1 Message Date
BillArmstrong 4fb3604ad6 Backport of: 2132 Fix support for enhanced authentication (#2171) 2025-08-23 14:16:01 +02:00
Christian 7636cd0fdb Backport of: 2129 cancellation token backport (#2144)
* Increase version

* Update release notes

* Adjust cancellation token handling

* Fix CI build
2025-03-02 11:57:02 +01:00
christian 740d605fd2 Fix Unit Tests 2024-09-07 11:32:14 +02:00
christian 9177b6ae68 Fix Unit Tests 2024-09-07 11:22:43 +02:00
Christian f0ea5c9ecd Fix encoding and decoding of AUTH paket according to RFC. (#2076) 2024-09-07 10:52:53 +02:00
Christian 76479ebabe Setup nuget and code signing for version 4.x.x (#2069)
* Update CI

* Update CI

* Update release notes
2024-08-24 14:58:13 +02:00
488 changed files with 19166 additions and 14669 deletions
+4 -4
View File
@@ -21,14 +21,14 @@ dotnet_naming_rule.static_readonly_rule.symbols = static_readonly_symbols
dotnet_naming_style.lower_camel_case_style.capitalization = camel_case dotnet_naming_style.lower_camel_case_style.capitalization = camel_case
dotnet_naming_style.lower_camel_case_style.required_prefix = _ dotnet_naming_style.lower_camel_case_style.required_prefix = _
dotnet_naming_style.upper_camel_case_style.capitalization = pascal_case dotnet_naming_style.upper_camel_case_style.capitalization = pascal_case
dotnet_naming_symbols.constants_symbols.applicable_accessibilities = public, internal, protected, protected_internal, private_protected dotnet_naming_symbols.constants_symbols.applicable_accessibilities = public,internal,protected,protected_internal,private_protected
dotnet_naming_symbols.constants_symbols.applicable_kinds = field dotnet_naming_symbols.constants_symbols.applicable_kinds = field
dotnet_naming_symbols.constants_symbols.required_modifiers = const dotnet_naming_symbols.constants_symbols.required_modifiers = const
dotnet_naming_symbols.public_fields_symbols.applicable_accessibilities = public, internal, protected, protected_internal, private_protected dotnet_naming_symbols.public_fields_symbols.applicable_accessibilities = public,internal,protected,protected_internal,private_protected
dotnet_naming_symbols.public_fields_symbols.applicable_kinds = field dotnet_naming_symbols.public_fields_symbols.applicable_kinds = field
dotnet_naming_symbols.static_readonly_symbols.applicable_accessibilities = public, internal, protected, protected_internal, private_protected dotnet_naming_symbols.static_readonly_symbols.applicable_accessibilities = public,internal,protected,protected_internal,private_protected
dotnet_naming_symbols.static_readonly_symbols.applicable_kinds = field dotnet_naming_symbols.static_readonly_symbols.applicable_kinds = field
dotnet_naming_symbols.static_readonly_symbols.required_modifiers = static, readonly dotnet_naming_symbols.static_readonly_symbols.required_modifiers = static,readonly
dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:none dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:none
dotnet_style_parentheses_in_other_binary_operators = never_if_unnecessary:none dotnet_style_parentheses_in_other_binary_operators = never_if_unnecessary:none
dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:none dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:none
+3
View File
@@ -0,0 +1,3 @@
* Client: Fixed wrong timeout for keep alive check (thanks to @Erw1nT, #2129)
* Client: Fixed extended authentication (thanks to @chkr1011, #2132).
* Server: Fixed extended authentication (thanks to @chkr1011, #2132).
+41 -82
View File
@@ -3,67 +3,53 @@ name: CI
on: [push, pull_request] on: [push, pull_request]
env: env:
VERSION: "5.0.1.${{github.run_number}}" VERSION: "4.3.8.${{github.run_number}}"
PACKAGE_SUFFIX: ""
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: windows-2022
steps: steps:
- name: Setup Windows SDK
uses: GuillaumeFalourd/setup-windows10-sdk-action@v2.4
with:
sdk-version: 18362
- name: Setup .NET SDK - name: Setup .NET SDK
uses: actions/setup-dotnet@v4 uses: actions/setup-dotnet@v4
with: with:
dotnet-version: | dotnet-version: |
8.0.x 3.1.x
- name: Checkout code
uses: actions/checkout@v4
- 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 }}
- name: Upload nuget packages
uses: actions/upload-artifact@v4
with:
name: nugets
path: |
${{ github.workspace }}/Source/**/*.nupkg
${{ github.workspace }}/Source/**/*.snupkg
test:
runs-on: ubuntu-latest
steps:
- name: Setup .NET SDK
uses: actions/setup-dotnet@v4
with:
dotnet-version: |
8.0.x
- name: Checkout code
uses: actions/checkout@v4
- name: Execute tests
run: dotnet test --framework net8.0 Source/MQTTnet.Tests/MQTTnet.Tests.csproj
sign:
needs: build
runs-on: windows-latest # Code signing must run on a Windows agent for Authenticode signing (dll/exe)
if: github.repository == 'dotnet/MQTTnet'
steps:
- name: Setup .NET SDK
uses: actions/setup-dotnet@v4
with:
dotnet-version: |
6.0.x 6.0.x
7.0.x
- name: Download nuget packages - name: Setup MSBuild
uses: actions/download-artifact@v4 uses: microsoft/setup-msbuild@v1.1
with:
name: nugets - name: Checkout Code
path: nugets uses: actions/checkout@v4
- name: Setup Signing Certificate
run: |
$secret = '${{ secrets.SNC_BASE64 }}'
$decoded = [System.Convert]::FromBase64CharArray($secret, 0, $secret.Length)
Set-Content -Path ${{ github.workspace }}\certificate.snk -Value $decoded -AsByteStream
- name: Restore nuget packages
run: msbuild MQTTnet.sln /t:Restore /p:Configuration="Release" /verbosity:m
- name: Build solution
run: msbuild MQTTnet.sln /t:Build /p:Configuration="Release" /verbosity:m /p:FileVersion=${{ env.VERSION }} /p:AssemblyVersion=${{ env.VERSION }} /p:PackageVersion=${{ env.VERSION }} /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=${{ github.workspace }}\certificate.snk
- name: Setup VSTest
uses: darenm/Setup-VSTest@v1
- name: Core Tests
run: vstest.console.exe Source\MQTTnet.Tests\bin\Release\net7.0\MQTTnet.Tests.dll
- name: ASP.NET Tests
run: vstest.console.exe Source\MQTTnet.AspNetCore.Tests\bin\Release\netcoreapp3.1\MQTTnet.AspNetCore.Tests.dll
- name: Install sign CLI tool - name: Install sign CLI tool
run: dotnet tool install --tool-path . sign --version 0.9.0-beta.23127.3 run: dotnet tool install --tool-path . sign --version 0.9.0-beta.23127.3
@@ -73,7 +59,7 @@ jobs:
run: > run: >
./sign code azure-key-vault ` ./sign code azure-key-vault `
"**/*.nupkg" ` "**/*.nupkg" `
--base-directory "${{ github.workspace }}\nugets" ` --base-directory "${{ github.workspace }}/Source" `
--publisher-name "MQTTnet" ` --publisher-name "MQTTnet" `
--description "MQTTnet" ` --description "MQTTnet" `
--description-url "https://github.com/dotnet/MQTTnet" ` --description-url "https://github.com/dotnet/MQTTnet" `
@@ -88,36 +74,9 @@ jobs:
with: with:
name: signed-nugets name: signed-nugets
path: | path: |
${{ github.workspace }}\nugets\**\*.nupkg ${{ github.workspace }}/Source/**/*.nupkg
${{ github.workspace }}\nugets\**\*.snupkg ${{ github.workspace }}/Source/**/*.snupkg
publish-myget: - name: Publish MyGet nugets
if: ${{ github.event_name == 'push' }} if: ${{ github.event_name == 'push' }}
needs: sign run: dotnet nuget push ${{ github.workspace }}/Source/**/*.nupkg -k ${{ secrets.MYGET_API_KEY }} -s https://www.myget.org/F/mqttnet/api/v3/index.json --skip-duplicate
runs-on: ubuntu-latest
steps:
- name: Download signed nuget packages
uses: actions/download-artifact@v4
with:
name: signed-nugets
path: nugets
- name: Publish myget.com nugets
run: dotnet nuget push ${{ github.workspace }}/nugets/**/*.nupkg -k ${{ secrets.MYGET_API_KEY }} -s https://www.myget.org/F/mqttnet/api/v3/index.json --skip-duplicate
publish-nuget:
# Tag schema is 'v5.0.0-rc' etc.
if: startsWith(github.ref, 'refs/tags/v')
needs: [sign, test]
runs-on: ubuntu-latest
steps:
- name: Download signed nuget packages
uses: actions/download-artifact@v4
with:
name: signed-nugets
path: nugets
- name: Publish nuget.org nugets
run: dotnet nuget push ${{ github.workspace }}/nugets/**/*.nupkg -k ${{ secrets.NUGET_API_KEY }} -s https://api.nuget.org/v3/index.json --skip-duplicate
+19 -9
View File
@@ -10,7 +10,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
CODE-OF-CONDUCT.md = CODE-OF-CONDUCT.md CODE-OF-CONDUCT.md = CODE-OF-CONDUCT.md
LICENSE = LICENSE LICENSE = LICENSE
README.md = README.md README.md = README.md
Source\ReleaseNotes.md = Source\ReleaseNotes.md .github\workflows\ReleaseNotes.md = .github\workflows\ReleaseNotes.md
.github\workflows\ci.yml = .github\workflows\ci.yml .github\workflows\ci.yml = .github\workflows\ci.yml
EndProjectSection EndProjectSection
EndProject EndProject
@@ -18,10 +18,16 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.AspNetCore", "Sourc
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Extensions.Rpc", "Source\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj", "{C444E9C8-95FA-430E-9126-274129DE16CD}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Extensions.Rpc", "Source\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj", "{C444E9C8-95FA-430E-9126-274129DE16CD}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Extensions.ManagedClient", "Source\MQTTnet.Extensions.ManagedClient\MQTTnet.Extensions.ManagedClient.csproj", "{C400533A-8EBA-4F0B-BF4D-295C3708604B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Extensions.WebSocket4Net", "Source\MQTTnet.Extensions.WebSocket4Net\MQTTnet.Extensions.WebSocket4Net.csproj", "{2BD01D53-4CA5-4142-BE8D-313876395E3E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Samples", "Samples\MQTTnet.Samples.csproj", "{71CF35F5-3327-4A91-AAF4-5340F6701771}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Samples", "Samples\MQTTnet.Samples.csproj", "{71CF35F5-3327-4A91-AAF4-5340F6701771}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Tests", "Source\MQTTnet.Tests\MQTTnet.Tests.csproj", "{B270F32A-9F3E-42EE-A989-813E35E29ADB}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Tests", "Source\MQTTnet.Tests\MQTTnet.Tests.csproj", "{B270F32A-9F3E-42EE-A989-813E35E29ADB}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.AspNetCore.Tests", "Source\MQTTnet.AspNetCore.Tests\MQTTnet.AspNetCore.Tests.csproj", "{A238BBBF-C75F-482D-9CC3-BB34ABA9B675}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Benchmarks", "Source\MQTTnet.Benchmarks\MQTTnet.Benchmarks.csproj", "{2F516E76-AAC4-4219-B7D1-34CDD3CFF381}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Benchmarks", "Source\MQTTnet.Benchmarks\MQTTnet.Benchmarks.csproj", "{2F516E76-AAC4-4219-B7D1-34CDD3CFF381}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.TestApp", "Source\MQTTnet.TestApp\MQTTnet.TestApp.csproj", "{175D5340-CC5B-4542-939D-4E7D15A0BC8D}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.TestApp", "Source\MQTTnet.TestApp\MQTTnet.TestApp.csproj", "{175D5340-CC5B-4542-939D-4E7D15A0BC8D}"
@@ -30,8 +36,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.AspTestApp", "Sourc
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Extensions.TopicTemplate", "Source\MQTTnet.Extensions.TopicTemplate\MQTTnet.Extensions.TopicTemplate.csproj", "{374D5861-F7A6-42A2-8CBE-040846000EF9}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Extensions.TopicTemplate", "Source\MQTTnet.Extensions.TopicTemplate\MQTTnet.Extensions.TopicTemplate.csproj", "{374D5861-F7A6-42A2-8CBE-040846000EF9}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MQTTnet.Server", "Source\MQTTnet.Server\MQTTnet.Server.csproj", "{C876EFFD-C5AD-4E26-BD9F-4C48252C02BC}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@@ -50,6 +54,14 @@ Global
{C444E9C8-95FA-430E-9126-274129DE16CD}.Debug|Any CPU.Build.0 = Debug|Any CPU {C444E9C8-95FA-430E-9126-274129DE16CD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C444E9C8-95FA-430E-9126-274129DE16CD}.Release|Any CPU.ActiveCfg = Release|Any CPU {C444E9C8-95FA-430E-9126-274129DE16CD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C444E9C8-95FA-430E-9126-274129DE16CD}.Release|Any CPU.Build.0 = Release|Any CPU {C444E9C8-95FA-430E-9126-274129DE16CD}.Release|Any CPU.Build.0 = Release|Any CPU
{C400533A-8EBA-4F0B-BF4D-295C3708604B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C400533A-8EBA-4F0B-BF4D-295C3708604B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C400533A-8EBA-4F0B-BF4D-295C3708604B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C400533A-8EBA-4F0B-BF4D-295C3708604B}.Release|Any CPU.Build.0 = Release|Any CPU
{2BD01D53-4CA5-4142-BE8D-313876395E3E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2BD01D53-4CA5-4142-BE8D-313876395E3E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2BD01D53-4CA5-4142-BE8D-313876395E3E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2BD01D53-4CA5-4142-BE8D-313876395E3E}.Release|Any CPU.Build.0 = Release|Any CPU
{71CF35F5-3327-4A91-AAF4-5340F6701771}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {71CF35F5-3327-4A91-AAF4-5340F6701771}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{71CF35F5-3327-4A91-AAF4-5340F6701771}.Debug|Any CPU.Build.0 = Debug|Any CPU {71CF35F5-3327-4A91-AAF4-5340F6701771}.Debug|Any CPU.Build.0 = Debug|Any CPU
{71CF35F5-3327-4A91-AAF4-5340F6701771}.Release|Any CPU.ActiveCfg = Release|Any CPU {71CF35F5-3327-4A91-AAF4-5340F6701771}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -58,6 +70,10 @@ Global
{B270F32A-9F3E-42EE-A989-813E35E29ADB}.Debug|Any CPU.Build.0 = Debug|Any CPU {B270F32A-9F3E-42EE-A989-813E35E29ADB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B270F32A-9F3E-42EE-A989-813E35E29ADB}.Release|Any CPU.ActiveCfg = Release|Any CPU {B270F32A-9F3E-42EE-A989-813E35E29ADB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B270F32A-9F3E-42EE-A989-813E35E29ADB}.Release|Any CPU.Build.0 = Release|Any CPU {B270F32A-9F3E-42EE-A989-813E35E29ADB}.Release|Any CPU.Build.0 = Release|Any CPU
{A238BBBF-C75F-482D-9CC3-BB34ABA9B675}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A238BBBF-C75F-482D-9CC3-BB34ABA9B675}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A238BBBF-C75F-482D-9CC3-BB34ABA9B675}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A238BBBF-C75F-482D-9CC3-BB34ABA9B675}.Release|Any CPU.Build.0 = Release|Any CPU
{2F516E76-AAC4-4219-B7D1-34CDD3CFF381}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2F516E76-AAC4-4219-B7D1-34CDD3CFF381}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2F516E76-AAC4-4219-B7D1-34CDD3CFF381}.Debug|Any CPU.Build.0 = Debug|Any CPU {2F516E76-AAC4-4219-B7D1-34CDD3CFF381}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2F516E76-AAC4-4219-B7D1-34CDD3CFF381}.Release|Any CPU.ActiveCfg = Release|Any CPU {2F516E76-AAC4-4219-B7D1-34CDD3CFF381}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -74,10 +90,6 @@ Global
{374D5861-F7A6-42A2-8CBE-040846000EF9}.Debug|Any CPU.Build.0 = Debug|Any CPU {374D5861-F7A6-42A2-8CBE-040846000EF9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{374D5861-F7A6-42A2-8CBE-040846000EF9}.Release|Any CPU.ActiveCfg = Release|Any CPU {374D5861-F7A6-42A2-8CBE-040846000EF9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{374D5861-F7A6-42A2-8CBE-040846000EF9}.Release|Any CPU.Build.0 = Release|Any CPU {374D5861-F7A6-42A2-8CBE-040846000EF9}.Release|Any CPU.Build.0 = Release|Any CPU
{C876EFFD-C5AD-4E26-BD9F-4C48252C02BC}.Debug|Any CPU.ActiveCfg = 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.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@@ -85,6 +97,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
-1
View File
@@ -236,7 +236,6 @@ See the LICENSE file in the project root for more information.</s:String>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpRenamePlacementToArrangementMigration/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpRenamePlacementToArrangementMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpUseContinuousIndentInsideBracesMigration/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpUseContinuousIndentInsideBracesMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002EMemberReordering_002EMigrations_002ECSharpFileLayoutPatternRemoveIsAttributeUpgrade/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAddAccessorOwnerDeclarationBracesMigration/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAddAccessorOwnerDeclarationBracesMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAlwaysTreatStructAsNotReorderableMigration/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAlwaysTreatStructAsNotReorderableMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002ECSharpPlaceAttributeOnSameLineMigration/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002ECSharpPlaceAttributeOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
+5 -12
View File
@@ -12,9 +12,7 @@
# MQTTnet # MQTTnet
MQTTnet is a high performance .NET library for MQTT based communication. It provides a MQTT client and a MQTT server ( MQTTnet is a high performance .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker) and supports the MQTT protocol up to version 5. It is compatible with mostly any supported .NET Framework version and CPU architecture.
broker) and supports the MQTT protocol up to version 5. It is compatible with mostly any supported .NET Framework
version and CPU architecture.
## Features ## Features
@@ -30,15 +28,13 @@ version and CPU architecture.
* Unit tested (~636 tests) * Unit tested (~636 tests)
* No external dependencies * No external dependencies
\* Tested on local machine (Intel i7 8700K) with MQTTnet client and server running in the same process using the TCP \* Tested on local machine (Intel i7 8700K) with MQTTnet client and server running in the same process using the TCP channel. The app for verification is part of this repository and stored in _/Tests/MQTTnet.TestApp.NetCore_.
channel. The app for verification is part of this repository and stored in _/Tests/MQTTnet.TestApp.NetCore_.
### Client ### Client
* Communication via TCP (+TLS) or WS (WebSocket) supported * Communication via TCP (+TLS) or WS (WebSocket) supported
* Included core _LowLevelMqttClient_ with low level functionality * Included core _LowLevelMqttClient_ with low level functionality
* Also included _ManagedMqttClient_ which maintains the connection and subscriptions automatically. Also application * Also included _ManagedMqttClient_ which maintains the connection and subscriptions automatically. Also application messages are queued and re-scheduled for higher QoS levels automatically.
messages are queued and re-scheduled for higher QoS levels automatically.
* Rx support (via another project) * Rx support (via another project)
* Compatible with Microsoft Azure IoT Hub * Compatible with Microsoft Azure IoT Hub
@@ -56,11 +52,9 @@ channel. The app for verification is part of this repository and stored in _/Tes
## Getting Started ## Getting Started
MQTTnet is delivered via <b>NuGet</b> package manager. You can find the packages MQTTnet is delivered via <b>NuGet</b> package manager. You can find the packages here: https://www.nuget.org/packages/MQTTnet/
here: https://www.nuget.org/packages/MQTTnet/
Use these command in the Package Manager console to install MQTTnet manually: Use these command in the Package Manager console to install MQTTnet manually:
``` ```
Install-Package MQTTnet Install-Package MQTTnet
``` ```
@@ -74,8 +68,7 @@ Samples for using MQTTnet are part of this repository. For starters these sample
## Code of Conduct ## Code of Conduct
This project has adopted the code of conduct defined by the Contributor Covenant to clarify expected behavior in our This project has adopted the code of conduct defined by the Contributor Covenant to clarify expected behavior in our community.
community.
For more information see the [.NET Foundation Code of Conduct](https://dotnetfoundation.org/code-of-conduct). For more information see the [.NET Foundation Code of Conduct](https://dotnetfoundation.org/code-of-conduct).
## .NET Foundation ## .NET Foundation
File diff suppressed because it is too large Load Diff
+10 -8
View File
@@ -6,6 +6,8 @@
// ReSharper disable UnusedMember.Global // ReSharper disable UnusedMember.Global
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming
using MQTTnet.Client;
namespace MQTTnet.Samples.Client; namespace MQTTnet.Samples.Client;
public static class Client_Publish_Samples public static class Client_Publish_Samples
@@ -21,7 +23,7 @@ public static class Client_Publish_Samples
* or at least provides backward compatibility where possible. * or at least provides backward compatibility where possible.
*/ */
var mqttFactory = new MqttClientFactory(); var mqttFactory = new MqttFactory();
using (var mqttClient = mqttFactory.CreateMqttClient()) using (var mqttClient = mqttFactory.CreateMqttClient())
{ {
@@ -39,11 +41,11 @@ public static class Client_Publish_Samples
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()
{ {
/* /*
@@ -52,7 +54,7 @@ public static class Client_Publish_Samples
* See sample _Publish_Application_Message_ for more details. * See sample _Publish_Application_Message_ for more details.
*/ */
var mqttFactory = new MqttClientFactory(); var mqttFactory = new MqttFactory();
using (var mqttClient = mqttFactory.CreateMqttClient()) using (var mqttClient = mqttFactory.CreateMqttClient())
{ {
@@ -68,23 +70,23 @@ public static class Client_Publish_Samples
.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.");
} }
} }
+23 -12
View File
@@ -7,6 +7,7 @@
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming
// ReSharper disable UnusedMember.Local // ReSharper disable UnusedMember.Local
using MQTTnet.Client;
using MQTTnet.Extensions.TopicTemplate; using MQTTnet.Extensions.TopicTemplate;
using MQTTnet.Packets; using MQTTnet.Packets;
using MQTTnet.Protocol; using MQTTnet.Protocol;
@@ -16,15 +17,15 @@ namespace MQTTnet.Samples.Client;
public static class Client_Subscribe_Samples public static class Client_Subscribe_Samples
{ {
static readonly MqttTopicTemplate sampleTemplate = new("mqttnet/samples/topic/{id}"); static MqttTopicTemplate sampleTemplate = new MqttTopicTemplate("mqttnet/samples/topic/{id}");
public static async Task Handle_Received_Application_Message() public static async Task Handle_Received_Application_Message()
{ {
/* /*
* This sample subscribes to a topic and processes the received message. * This sample subscribes to a topic and processes the received message.
*/ */
var mqttFactory = new MqttClientFactory(); var mqttFactory = new MqttFactory();
using (var mqttClient = mqttFactory.CreateMqttClient()) using (var mqttClient = mqttFactory.CreateMqttClient())
{ {
@@ -43,7 +44,9 @@ public static class Client_Subscribe_Samples
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);
@@ -60,7 +63,7 @@ public static class Client_Subscribe_Samples
* This sample subscribes to a topic and sends a response to the broker. This requires at least QoS level 1 to work! * This sample subscribes to a topic and sends a response to the broker. This requires at least QoS level 1 to work!
*/ */
var mqttFactory = new MqttClientFactory(); var mqttFactory = new MqttFactory();
using (var mqttClient = mqttFactory.CreateMqttClient()) using (var mqttClient = mqttFactory.CreateMqttClient())
{ {
@@ -83,7 +86,10 @@ public static class Client_Subscribe_Samples
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);
@@ -100,7 +106,7 @@ public static class Client_Subscribe_Samples
* This sample subscribes to several topics in a single request. * This sample subscribes to several topics in a single request.
*/ */
var mqttFactory = new MqttClientFactory(); var mqttFactory = new MqttFactory();
using (var mqttClient = mqttFactory.CreateMqttClient()) using (var mqttClient = mqttFactory.CreateMqttClient())
{ {
@@ -111,9 +117,12 @@ public static class Client_Subscribe_Samples
// 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(
.WithTopicTemplate(sampleTemplate.WithParameter("id", "2"), noLocal: true) sampleTemplate.WithParameter("id", "1"))
.WithTopicTemplate(sampleTemplate.WithParameter("id", "3"), retainHandling: MqttRetainHandling.SendAtSubscribe) .WithTopicTemplate(
sampleTemplate.WithParameter("id", "2"), noLocal: true)
.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);
@@ -131,7 +140,7 @@ public static class Client_Subscribe_Samples
* This sample subscribes to a topic. * This sample subscribes to a topic.
*/ */
var mqttFactory = new MqttClientFactory(); var mqttFactory = new MqttFactory();
using (var mqttClient = mqttFactory.CreateMqttClient()) using (var mqttClient = mqttFactory.CreateMqttClient())
{ {
@@ -139,7 +148,9 @@ public static class Client_Subscribe_Samples
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);
+3 -3
View File
@@ -7,7 +7,7 @@
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming
using System.Text; using System.Text;
using MQTTnet.Diagnostics.Logger; using MQTTnet.Diagnostics;
namespace MQTTnet.Samples.Diagnostics; namespace MQTTnet.Samples.Diagnostics;
@@ -20,7 +20,7 @@ public static class Logger_Samples
* to other loggers like Microsoft logger or Serilog or log4net etc. * to other loggers like Microsoft logger or Serilog or log4net etc.
*/ */
var mqttFactory = new MqttClientFactory(new MyLogger()); var mqttFactory = new MqttFactory(new MyLogger());
var mqttClientOptions = mqttFactory.CreateClientOptionsBuilder() var mqttClientOptions = mqttFactory.CreateClientOptionsBuilder()
.WithTcpServer("broker.hivemq.com") .WithTcpServer("broker.hivemq.com")
@@ -62,7 +62,7 @@ public static class Logger_Samples
Console.Write(output); Console.Write(output);
}; };
var mqttFactory = new MqttClientFactory(mqttEventLogger); var mqttFactory = new MqttFactory(mqttEventLogger);
var mqttClientOptions = mqttFactory.CreateClientOptionsBuilder() var mqttClientOptions = mqttFactory.CreateClientOptionsBuilder()
.WithTcpServer("broker.hivemq.com") .WithTcpServer("broker.hivemq.com")
@@ -6,7 +6,7 @@
// ReSharper disable UnusedMember.Global // ReSharper disable UnusedMember.Global
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming
using MQTTnet.Diagnostics.PacketInspection; using MQTTnet.Diagnostics;
namespace MQTTnet.Samples.Diagnostics; namespace MQTTnet.Samples.Diagnostics;
@@ -17,19 +17,19 @@ public static class PackageInspection_Samples
/* /*
* This sample covers the inspection of outgoing packages from the client. * This sample covers the inspection of outgoing packages from the client.
*/ */
var mqttFactory = new MqttClientFactory(); var mqttFactory = new MqttFactory();
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()
+7 -11
View File
@@ -2,28 +2,24 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers> <DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
<EnableNETAnalyzers>false</EnableNETAnalyzers> <EnableNETAnalyzers>false</EnableNETAnalyzers>
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild> <EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
<NoWarn>1591;NETSDK1138;NU1803;NU1901;NU1902</NoWarn> <NoWarn>1591;NETSDK1138</NoWarn>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<NuGetAuditMode>all</NuGetAuditMode>
<NuGetAudit>true</NuGetAudit>
<NuGetAuditLevel>low</NuGetAuditLevel>
<AnalysisLevel>latest-Recommended</AnalysisLevel>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj"/> <ProjectReference Include="..\Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj" />
<ProjectReference Include="..\Source\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj"/> <ProjectReference Include="..\Source\MQTTnet.Extensions.ManagedClient\MQTTnet.Extensions.ManagedClient.csproj" />
<ProjectReference Include="..\Source\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj" />
<ProjectReference Include="..\Source\MQTTnet.Extensions.TopicTemplate\MQTTnet.Extensions.TopicTemplate.csproj" /> <ProjectReference Include="..\Source\MQTTnet.Extensions.TopicTemplate\MQTTnet.Extensions.TopicTemplate.csproj" />
<ProjectReference Include="..\Source\MQTTnet.Server\MQTTnet.Server.csproj"/> <ProjectReference Include="..\Source\MQTTnet.Extensions.WebSocket4Net\MQTTnet.Extensions.WebSocket4Net.csproj" />
<ProjectReference Include="..\Source\MQTTnet\MQTTnet.csproj"/> <ProjectReference Include="..\Source\MQTTnet\MQTTnet.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>
@@ -0,0 +1,52 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
// ReSharper disable UnusedType.Global
// ReSharper disable UnusedMember.Global
// ReSharper disable InconsistentNaming
using MQTTnet.Client;
using MQTTnet.Extensions.ManagedClient;
namespace MQTTnet.Samples.ManagedClient;
public sealed class Managed_Client_Simple_Samples
{
public static async Task Connect_Client()
{
/*
* This sample creates a simple managed MQTT client and connects to a public broker.
*
* The managed client extends the existing _MqttClient_. It adds the following features.
* - Reconnecting when connection is lost.
* - Storing pending messages in an internal queue so that an enqueue is possible while the client remains not connected.
*/
var mqttFactory = new MqttFactory();
using (var managedMqttClient = mqttFactory.CreateManagedMqttClient())
{
var mqttClientOptions = new MqttClientOptionsBuilder()
.WithTcpServer("broker.hivemq.com")
.Build();
var managedMqttClientOptions = new ManagedMqttClientOptionsBuilder()
.WithClientOptions(mqttClientOptions)
.Build();
await managedMqttClient.StartAsync(managedMqttClientOptions);
// The application message is not sent. It is stored in an internal queue and
// will be sent when the client is connected.
await managedMqttClient.EnqueueAsync("Topic", "Payload");
Console.WriteLine("The managed MQTT client is connected.");
// Wait until the queue is fully processed.
SpinWait.SpinUntil(() => managedMqttClient.PendingApplicationMessagesCount == 0, 10000);
Console.WriteLine($"Pending messages = {managedMqttClient.PendingApplicationMessagesCount}");
}
}
}
@@ -0,0 +1,87 @@
// 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.
// ReSharper disable UnusedType.Global
// ReSharper disable UnusedMember.Global
// ReSharper disable InconsistentNaming
using MQTTnet.Client;
using MQTTnet.Extensions.ManagedClient;
namespace MQTTnet.Samples.ManagedClient;
public sealed class Managed_Client_Subscribe_Samples
{
public static async Task Connect_Client()
{
/*
* This sample creates a simple managed MQTT client and connects to a public broker, subscribe to a topic and verifies subscription result.
*
* The managed client extends the existing _MqttClient_. It adds the following features.
* - Reconnecting when connection is lost.
* - Storing pending messages in an internal queue so that an enqueue is possible while the client remains not connected.
*/
var mqttFactory = new MqttFactory();
var subscribed = false;
using (var managedMqttClient = mqttFactory.CreateManagedMqttClient())
{
var mqttClientOptions = new MqttClientOptionsBuilder()
.WithTcpServer("broker.hivemq.com")
.Build();
var managedMqttClientOptions = new ManagedMqttClientOptionsBuilder()
.WithClientOptions(mqttClientOptions)
.Build();
await managedMqttClient.StartAsync(managedMqttClientOptions);
// The application message is not sent. It is stored in an internal queue and
// will be sent when the client is connected.
await managedMqttClient.EnqueueAsync("Topic", "Payload");
Console.WriteLine("The managed MQTT client is connected.");
// Wait until the queue is fully processed.
SpinWait.SpinUntil(() => managedMqttClient.PendingApplicationMessagesCount == 0, 10000);
Console.WriteLine($"Pending messages = {managedMqttClient.PendingApplicationMessagesCount}");
managedMqttClient.SubscriptionsChangedAsync += args => SubscriptionsResultAsync(args, ref subscribed);
await managedMqttClient.SubscribeAsync("Topic").ConfigureAwait(false);
SpinWait.SpinUntil(() => subscribed, 1000);
Console.WriteLine($"Subscription properly done {subscribed}");
}
}
private static Task SubscriptionsResultAsync(SubscriptionsChangedEventArgs arg, ref bool subscribed)
{
foreach (var mqttClientSubscribeResult in arg.SubscribeResult)
{
Console.WriteLine($"Subscription reason {mqttClientSubscribeResult.ReasonString}");
foreach (var item in mqttClientSubscribeResult.Items)
{
Console.WriteLine($"For topic filter {item.TopicFilter}, result code: {item.ResultCode}");
if (item.TopicFilter.Topic == "Topic" && item.ResultCode == MqttClientSubscribeResultCode.GrantedQoS0 && !subscribed)
{
subscribed = true;
}
}
}
foreach (var mqttClientUnsubscribeResult in arg.UnsubscribeResult)
{
Console.WriteLine($"Unsubscription reason {mqttClientUnsubscribeResult.ReasonString}");
foreach (var item in mqttClientUnsubscribeResult.Items)
{
Console.WriteLine($"For topic filter {item.TopicFilter}, result code: {item.ResultCode}");
}
}
return Task.CompletedTask;
}
}
+9 -8
View File
@@ -6,6 +6,7 @@
// ReSharper disable UnusedMember.Global // ReSharper disable UnusedMember.Global
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming
using MQTTnet.Client;
using MQTTnet.Extensions.Rpc; using MQTTnet.Extensions.Rpc;
using MQTTnet.Protocol; using MQTTnet.Protocol;
@@ -18,14 +19,14 @@ public static class RpcClient_Samples
* This is done via defining a pattern which uses the topic to correlate the request and the response. * This is done via defining a pattern which uses the topic to correlate the request and the response.
* From client usage it is possible to define a timeout. * From client usage it is possible to define a timeout.
*/ */
public static async Task Send_Request() public static async Task Send_Request()
{ {
var mqttFactory = new MqttClientFactory(); var mqttFactory = new MqttFactory();
// 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() var mqttClientOptions = new MqttClientOptionsBuilder()
@@ -33,23 +34,23 @@ public static class RpcClient_Samples
.Build(); .Build();
await mqttClient.ConnectAsync(mqttClientOptions); await mqttClient.ConnectAsync(mqttClientOptions);
using (var mqttRpcClient = mqttFactory.CreateMqttRpcClient(mqttClient)) using (var mqttRpcClient = mqttFactory.CreateMqttRpcClient(mqttClient))
{ {
// Access to a fully featured application message is not supported for RPC calls! // 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. // The method will throw an exception when the response was not received in time.
await mqttRpcClient.ExecuteAsync(TimeSpan.FromSeconds(2), "ping", "", MqttQualityOfServiceLevel.AtMostOnce); await mqttRpcClient.ExecuteAsync(TimeSpan.FromSeconds(2), "ping", "", MqttQualityOfServiceLevel.AtMostOnce);
} }
Console.WriteLine("The RPC call was successful."); Console.WriteLine("The RPC call was successful.");
} }
} }
/* /*
* The device must respond to the request using the correct topic. The following C code shows how a * The device must respond to the request using the correct topic. The following C code shows how a
* smart device like an ESP8266 must respond to the above sample. * smart device like an ESP8266 must respond to the above sample.
* *
// If using the MQTT client PubSubClient it must be ensured // If using the MQTT client PubSubClient it must be ensured
// that the request topic for each method is subscribed like the following. // that the request topic for each method is subscribed like the following.
mqttClient.subscribe("MQTTnet.RPC/+/ping"); mqttClient.subscribe("MQTTnet.RPC/+/ping");
mqttClient.subscribe("MQTTnet.RPC/+/do_something"); mqttClient.subscribe("MQTTnet.RPC/+/do_something");
+1 -1
View File
@@ -51,7 +51,7 @@ public static class Server_ASP_NET_Samples
{ {
// Inject other services via constructor. // Inject other services via constructor.
} }
public Task OnClientConnected(ClientConnectedEventArgs eventArgs) public Task OnClientConnected(ClientConnectedEventArgs eventArgs)
{ {
Console.WriteLine($"Client '{eventArgs.ClientId}' connected."); Console.WriteLine($"Client '{eventArgs.ClientId}' connected.");
+8 -7
View File
@@ -13,6 +13,7 @@ namespace MQTTnet.Samples.Server;
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming
// ReSharper disable EmptyConstructor // ReSharper disable EmptyConstructor
// ReSharper disable MemberCanBeMadeStatic.Local // ReSharper disable MemberCanBeMadeStatic.Local
public static class Server_Diagnostics_Samples public static class Server_Diagnostics_Samples
{ {
public static async Task Get_Notified_When_Client_Received_Message() public static async Task Get_Notified_When_Client_Received_Message()
@@ -21,26 +22,26 @@ public static class Server_Diagnostics_Samples
* This sample starts a MQTT server and attaches an event which gets fired whenever * This sample starts a MQTT server and attaches an event which gets fired whenever
* a client received and acknowledged a PUBLISH packet. * a client received and acknowledged a PUBLISH packet.
*/ */
var mqttServerFactory = new MqttServerFactory(); var mqttFactory = new MqttFactory();
var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build(); var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
using (var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions)) using (var mqttServer = mqttFactory.CreateMqttServer(mqttServerOptions))
{ {
// Attach the event handler. // Attach the event handler.
mqttServer.ClientAcknowledgedPublishPacketAsync += e => mqttServer.ClientAcknowledgedPublishPacketAsync += e =>
{ {
Console.WriteLine($"Client '{e.ClientId}' acknowledged packet {e.PublishPacket.PacketIdentifier} with topic '{e.PublishPacket.Topic}'"); Console.WriteLine($"Client '{e.ClientId}' acknowledged packet {e.PublishPacket.PacketIdentifier} with topic '{e.PublishPacket.Topic}'");
// It is also possible to read additional data from the client response. This requires casting the response packet. // 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: {qos1AcknowledgePacket?.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.");
@@ -22,10 +22,10 @@ public static class Server_Intercepting_Samples
* Please see _Server_Simple_Samples_ for more details on how to start a server. * Please see _Server_Simple_Samples_ for more details on how to start a server.
*/ */
var mqttServerFactory = new MqttServerFactory(); var mqttFactory = new MqttFactory();
var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build(); var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
using (var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions)) using (var mqttServer = mqttFactory.CreateMqttServer(mqttServerOptions))
{ {
mqttServer.InterceptingPublishAsync += args => mqttServer.InterceptingPublishAsync += args =>
{ {
@@ -6,11 +6,9 @@
// ReSharper disable UnusedMember.Global // ReSharper disable UnusedMember.Global
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming
using System.Buffers;
using System.Text.Json; using System.Text.Json;
using MQTTnet.Packets; using MQTTnet.Packets;
using MQTTnet.Protocol; using MQTTnet.Protocol;
using MQTTnet.Server;
namespace MQTTnet.Samples.Server; namespace MQTTnet.Samples.Server;
@@ -24,12 +22,12 @@ public static class Server_Retained_Messages_Samples
var storePath = Path.Combine(Path.GetTempPath(), "RetainedMessages.json"); var storePath = Path.Combine(Path.GetTempPath(), "RetainedMessages.json");
var mqttServerFactory = new MqttServerFactory(); var mqttFactory = new MqttFactory();
// 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 = mqttFactory.CreateServerOptionsBuilder().WithDefaultEndpoint().Build();
using (var server = mqttServerFactory.CreateMqttServer(mqttServerOptions)) using (var server = mqttFactory.CreateMqttServer(mqttServerOptions))
{ {
// Make sure that the server will load the retained messages. // Make sure that the server will load the retained messages.
server.LoadingRetainedMessageAsync += async eventArgs => server.LoadingRetainedMessageAsync += async eventArgs =>
@@ -102,15 +100,18 @@ public static class Server_Retained_Messages_Samples
public static MqttRetainedMessageModel Create(MqttApplicationMessage message) public static MqttRetainedMessageModel Create(MqttApplicationMessage message)
{ {
ArgumentNullException.ThrowIfNull(message); if (message == null)
{
throw new ArgumentNullException(nameof(message));
}
return new MqttRetainedMessageModel return new MqttRetainedMessageModel
{ {
Topic = message.Topic, Topic = message.Topic,
// Create a copy of the buffer from the payload segment because // Create a copy of the buffer from the payload segment because
// it cannot be serialized and deserialized with the JSON serializer. // it cannot be serialized and deserialized with the JSON serializer.
Payload = message.Payload.ToArray(), Payload = message.PayloadSegment.ToArray(),
UserProperties = message.UserProperties, UserProperties = message.UserProperties,
ResponseTopic = message.ResponseTopic, ResponseTopic = message.ResponseTopic,
CorrelationData = message.CorrelationData, CorrelationData = message.CorrelationData,
+12 -12
View File
@@ -6,7 +6,7 @@
// ReSharper disable UnusedMember.Global // ReSharper disable UnusedMember.Global
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming
using MQTTnet.Diagnostics.Logger; using MQTTnet.Diagnostics;
using MQTTnet.Protocol; using MQTTnet.Protocol;
using MQTTnet.Server; using MQTTnet.Server;
@@ -64,7 +64,7 @@ public static class Server_Simple_Samples
* This sample starts a simple MQTT server which will accept any TCP connection. * This sample starts a simple MQTT server which will accept any TCP connection.
*/ */
var mqttServerFactory = new MqttServerFactory(); var mqttFactory = new MqttFactory();
// The port for the default endpoint is 1883. // The port for the default endpoint is 1883.
// The default endpoint is NOT encrypted! // The default endpoint is NOT encrypted!
@@ -77,7 +77,7 @@ public static class Server_Simple_Samples
// .WithDefaultEndpointPort(1234) // .WithDefaultEndpointPort(1234)
// .Build(); // .Build();
using (var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions)) using (var mqttServer = mqttFactory.CreateMqttServer(mqttServerOptions))
{ {
await mqttServer.StartAsync(); await mqttServer.StartAsync();
@@ -99,11 +99,11 @@ public static class Server_Simple_Samples
* See sample "Run_Minimal_Server" for more details. * See sample "Run_Minimal_Server" for more details.
*/ */
var mqttServerFactory = new MqttServerFactory(new ConsoleLogger()); var mqttFactory = new MqttFactory(new ConsoleLogger());
var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build(); var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
using (var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions)) using (var mqttServer = mqttFactory.CreateMqttServer(mqttServerOptions))
{ {
await mqttServer.StartAsync(); await mqttServer.StartAsync();
@@ -123,13 +123,13 @@ public static class Server_Simple_Samples
* See _Run_Minimal_Server_ for more information. * See _Run_Minimal_Server_ for more information.
*/ */
var mqttServerFactory = new MqttServerFactory(); var mqttFactory = new MqttFactory();
var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build(); var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
using (var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions)) using (var mqttServer = mqttFactory.CreateMqttServer(mqttServerOptions))
{ {
// Setup connection validation before starting the server so that there is // Setup connection validation before starting the server so that there is
// no change to connect without valid credentials. // no change to connect without valid credentials.
mqttServer.ValidatingConnectionAsync += e => mqttServer.ValidatingConnectionAsync += e =>
{ {
@@ -162,11 +162,11 @@ public static class Server_Simple_Samples
static async Task<MqttServer> StartMqttServer() static async Task<MqttServer> StartMqttServer()
{ {
var mqttServerFactory = new MqttServerFactory(); var mqttFactory = new MqttFactory();
// 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 = mqttFactory.CreateServerOptionsBuilder().WithDefaultEndpoint().Build();
var server = mqttServerFactory.CreateMqttServer(mqttServerOptions); var server = mqttFactory.CreateMqttServer(mqttServerOptions);
await server.StartAsync(); await server.StartAsync();
return server; return server;
} }
@@ -216,4 +216,4 @@ public static class Server_Simple_Samples
} }
} }
} }
} }
+3 -3
View File
@@ -24,14 +24,14 @@ public static class Server_TLS_Samples
* See sample "Run_Minimal_Server" for more details. * See sample "Run_Minimal_Server" for more details.
*/ */
var mqttServerFactory = new MqttServerFactory(); var mqttFactory = new MqttFactory();
// This certificate is self signed so that // This certificate is self signed so that
var certificate = CreateSelfSignedCertificate("1.3.6.1.5.5.7.3.1"); var certificate = CreateSelfSignedCertificate("1.3.6.1.5.5.7.3.1");
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 = mqttFactory.CreateMqttServer(mqttServerOptions))
{ {
await mqttServer.StartAsync(); await mqttServer.StartAsync();
@@ -0,0 +1,33 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<IsPackable>false</IsPackable>
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
<EnableNETAnalyzers>false</EnableNETAnalyzers>
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
<PackageReference Include="MSTest.TestAdapter" Version="2.2.10" />
<PackageReference Include="MSTest.TestFramework" Version="2.2.10" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netcoreapp3.1' ">
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="2.2.7" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.2.0" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' != 'netcoreapp3.1' ">
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="2.1.1" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.1.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MQTTnet\MQTTnet.csproj" />
<ProjectReference Include="..\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj" />
<ProjectReference Include="..\MQTTnet.Tests\MQTTnet.Tests.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,50 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections;
using MQTTnet.Adapter;
using MQTTnet.Diagnostics;
using MQTTnet.Formatter;
using MQTTnet.Server;
namespace MQTTnet.AspNetCore.Tests.Mockups
{
public sealed class ConnectionHandlerMockup : IMqttServerAdapter
{
public Func<IMqttChannelAdapter, Task> ClientHandler { get; set; }
public TaskCompletionSource<MqttConnectionContext> Context { get; } = new TaskCompletionSource<MqttConnectionContext>();
public void Dispose()
{
}
public async Task OnConnectedAsync(ConnectionContext connection)
{
try
{
var formatter = new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535));
var context = new MqttConnectionContext(formatter, connection);
Context.TrySetResult(context);
await ClientHandler(context);
}
catch (Exception ex)
{
Context.TrySetException(ex);
}
}
public Task StartAsync(MqttServerOptions options, IMqttNetLogger logger)
{
return Task.CompletedTask;
}
public Task StopAsync()
{
return Task.CompletedTask;
}
}
}
@@ -4,7 +4,7 @@
using System.IO.Pipelines; using System.IO.Pipelines;
namespace MQTTnet.Tests.ASP.Mockups namespace MQTTnet.AspNetCore.Tests.Mockups
{ {
public sealed class DuplexPipeMockup : IDuplexPipe public sealed class DuplexPipeMockup : IDuplexPipe
{ {
@@ -4,7 +4,7 @@
using System.Buffers; using System.Buffers;
namespace MQTTnet.Tests.ASP.Mockups namespace MQTTnet.AspNetCore.Tests.Mockups
{ {
public sealed class LimitedMemoryPool : MemoryPool<byte> public sealed class LimitedMemoryPool : MemoryPool<byte>
{ {
@@ -5,7 +5,7 @@
using System; using System;
using System.Buffers; using System.Buffers;
namespace MQTTnet.Tests.ASP.Mockups namespace MQTTnet.AspNetCore.Tests.Mockups
{ {
public sealed class MemoryOwner : IMemoryOwner<byte> public sealed class MemoryOwner : IMemoryOwner<byte>
{ {
@@ -3,21 +3,24 @@
// 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;
using System.Buffers; using System.Net;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.AspNetCore; using MQTTnet.Adapter;
using MQTTnet.AspNetCore.Tests.Mockups;
using MQTTnet.Client;
using MQTTnet.Exceptions; using MQTTnet.Exceptions;
using MQTTnet.Formatter; using MQTTnet.Formatter;
using MQTTnet.Packets; using MQTTnet.Packets;
using MQTTnet.Protocol; using MQTTnet.Protocol;
using MQTTnet.Tests.ASP.Mockups;
using MQTTnet.Tests.Helpers; using MQTTnet.Tests.Helpers;
namespace MQTTnet.Tests.ASP namespace MQTTnet.AspNetCore.Tests
{ {
[TestClass] [TestClass]
public class MqttConnectionContextTest public class MqttConnectionContextTest
@@ -40,35 +43,34 @@ namespace MQTTnet.Tests.ASP
await Assert.ThrowsExceptionAsync<InvalidOperationException>(() => ctx.ReceivePacketAsync(CancellationToken.None)); await Assert.ThrowsExceptionAsync<InvalidOperationException>(() => ctx.ReceivePacketAsync(CancellationToken.None));
} }
// TODO: Fix test [TestMethod]
// [TestMethod] public async Task TestEndpoint()
// public async Task TestEndpoint() {
// { var mockup = new ConnectionHandlerMockup();
// var mockup = new ConnectionHandlerMockup();
// using (var host = new WebHostBuilder().UseKestrel(kestrel => kestrel.ListenLocalhost(1883, listener => listener.Use((ctx, next) => mockup.OnConnectedAsync(ctx))))
// using (var host = new WebHostBuilder().UseKestrel(kestrel => kestrel.ListenLocalhost(1883, listener => listener.Use((ctx, next) => mockup.OnConnectedAsync(ctx)))) .UseStartup<Startup>()
// .UseStartup<Startup>() .ConfigureServices(
// .ConfigureServices( (hostContext, services) =>
// (hostContext, services) => {
// { services.AddHostedMqttServer(o => o.WithoutDefaultEndpoint());
// services.AddHostedMqttServer(o => o.WithoutDefaultEndpoint()); services.AddSingleton<IMqttServerAdapter>(mockup);
// services.AddSingleton<IMqttServerAdapter>(mockup); })
// }) .Build())
// .Build())
// using (var client = new MqttFactory().CreateMqttClient())
// using (var client = new MqttFactory().CreateMqttClient()) {
// { host.Start();
// host.Start(); await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("localhost").Build(), CancellationToken.None);
// await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("localhost").Build(), CancellationToken.None);
// var ctx = await mockup.Context.Task;
// var ctx = await mockup.Context.Task; #if NETCOREAPP3_1
// var ep = IPEndPoint.Parse(ctx.Endpoint);
// var ep = IPEndPoint.Parse(ctx.Endpoint); Assert.IsNotNull(ep);
// Assert.IsNotNull(ep); #endif
// Assert.IsNotNull(ctx);
// Assert.IsNotNull(ctx); }
// } }
// }
// COMMENTED OUT DUE TO DEAD LOCK? OR VERY VERY SLOW PERFORMANCE ON LOCAL DEV MACHINE. TEST WAS STILL RUNNING AFTER SEVERAL MINUTES! // COMMENTED OUT DUE TO DEAD LOCK? OR VERY VERY SLOW PERFORMANCE ON LOCAL DEV MACHINE. TEST WAS STILL RUNNING AFTER SEVERAL MINUTES!
//[TestMethod] //[TestMethod]
@@ -80,7 +82,7 @@ namespace MQTTnet.Tests.ASP
// connection.Transport = pipe; // connection.Transport = pipe;
// var ctx = new MqttConnectionContext(serializer, connection); // var ctx = new MqttConnectionContext(serializer, connection);
// var tasks = Enumerable.Range(1, 100).Select(_ => Task.Run(async () => // var tasks = Enumerable.Range(1, 100).Select(_ => Task.Run(async () =>
// { // {
// for (int i = 0; i < 100; i++) // for (int i = 0; i < 100; i++)
// { // {
@@ -100,7 +102,7 @@ namespace MQTTnet.Tests.ASP
connection.Transport = pipe; connection.Transport = pipe;
var ctx = new MqttConnectionContext(serializer, connection); var ctx = new MqttConnectionContext(serializer, connection);
await ctx.SendPacketAsync(new MqttPublishPacket { PayloadSegment = new byte[20_000] }, CancellationToken.None).ConfigureAwait(false); await ctx.SendPacketAsync(new MqttPublishPacket { PayloadSegment = new ArraySegment<byte>(new byte[20_000]) }, CancellationToken.None).ConfigureAwait(false);
var readResult = await pipe.Send.Reader.ReadAsync(); var readResult = await pipe.Send.Reader.ReadAsync();
Assert.IsTrue(readResult.Buffer.Length > 20000); Assert.IsTrue(readResult.Buffer.Length > 20000);
@@ -0,0 +1,51 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#if NETCOREAPP3_1
using System.Buffers;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.AspNetCore;
using MQTTnet.Formatter;
using MQTTnet.Packets;
namespace MQTTnet.AspNetCore.Tests
{
[TestClass]
public sealed class ReaderExtensionsTest
{
[TestMethod]
public void TestTryDeserialize()
{
var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535));
var buffer = serializer.Encode(new MqttPublishPacket {Topic = "a", PayloadSegment = new byte[5]}).Join();
var sequence = new ReadOnlySequence<byte>(buffer.Array, buffer.Offset, buffer.Count);
var part = sequence;
MqttPacket packet;
var consumed = part.Start;
var observed = part.Start;
var result = false;
var read = 0;
part = sequence.Slice(sequence.Start, 0); // empty message should fail
result = serializer.TryDecode(part, out packet, out consumed, out observed, out read);
Assert.IsFalse(result);
part = sequence.Slice(sequence.Start, 1); // partial fixed header should fail
result = serializer.TryDecode(part, out packet, out consumed, out observed, out read);
Assert.IsFalse(result);
part = sequence.Slice(sequence.Start, 4); // partial body should fail
result = serializer.TryDecode(part, out packet, out consumed, out observed, out read);
Assert.IsFalse(result);
part = sequence; // complete msg should work
result = serializer.TryDecode(part, out packet, out consumed, out observed, out read);
Assert.IsTrue(result);
}
}
}
#endif
@@ -1,29 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk.Web"> <Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers> <DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
<EnableNETAnalyzers>false</EnableNETAnalyzers> <EnableNETAnalyzers>false</EnableNETAnalyzers>
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild> <EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
<NoWarn>1591;NETSDK1138;NU1803;NU1901;NU1902</NoWarn> <NoWarn>1591;NETSDK1138</NoWarn>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<NuGetAuditMode>all</NuGetAuditMode>
<NuGetAudit>true</NuGetAudit>
<NuGetAuditLevel>low</NuGetAuditLevel>
<AnalysisLevel>latest-Recommended</AnalysisLevel>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj"/> <ProjectReference Include="..\..\Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj" />
<ProjectReference Include="..\..\Source\MQTTnet.Server\MQTTnet.Server.csproj"/>
<ProjectReference Include="..\MQTTnet\MQTTnet.csproj"/>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Folder Include="wwwroot\"/> <Folder Include="wwwroot\" />
</ItemGroup> </ItemGroup>
</Project> </Project>
+1 -1
View File
@@ -17,7 +17,7 @@
clientId: 'mqttnet_web_client' clientId: 'mqttnet_web_client'
} }
const client = mqtt.connect('ws://localhost:5000/mqtt') const client = mqtt.connect('ws://localhost:5049/mqtt')
client.on('connect', function () { client.on('connect', function () {
console.log('Connected') console.log('Connected')
+5 -2
View File
@@ -30,7 +30,10 @@ app.UseAuthorization();
app.MapRazorPages(); app.MapRazorPages();
// Setup MQTT stuff. // Setup MQTT stuff.
app.MapMqtt("/mqtt"); app.UseEndpoints(endpoints =>
{
endpoints.MapMqtt("/mqtt");
});
app.UseMqttServer(server => app.UseMqttServer(server =>
{ {
@@ -39,7 +42,7 @@ app.UseMqttServer(server =>
_ = Task.Run(async () => _ = Task.Run(async () =>
{ {
var mqttApplicationMessage = new MqttApplicationMessageBuilder() var mqttApplicationMessage = new MqttApplicationMessageBuilder()
.WithPayload("Test application message from MQTTnet server.") .WithPayload($"Test application message from MQTTnet server.")
.WithTopic("message") .WithTopic("message")
.Build(); .Build();
@@ -7,17 +7,15 @@ using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using MQTTnet.Server; using MQTTnet.Server;
namespace MQTTnet.AspNetCore; namespace MQTTnet.AspNetCore
public static class ApplicationBuilderExtensions
{ {
[Obsolete( public static class ApplicationBuilderExtensions
"This class is obsolete and will be removed in a future version. The recommended alternative is to use MapMqtt inside Microsoft.AspNetCore.Builder.UseEndpoints(...).")]
public static IApplicationBuilder UseMqttEndpoint(this IApplicationBuilder app, string path = "/mqtt")
{ {
app.UseWebSockets(); [Obsolete("This class is obsolete and will be removed in a future version. The recommended alternative is to use MapMqtt inside Microsoft.AspNetCore.Builder.UseEndpoints(...).")]
app.Use( public static IApplicationBuilder UseMqttEndpoint(this IApplicationBuilder app, string path = "/mqtt")
async (context, next) => {
app.UseWebSockets();
app.Use(async (context, next) =>
{ {
if (!context.WebSockets.IsWebSocketRequest || context.Request.Path != path) if (!context.WebSockets.IsWebSocketRequest || context.Request.Path != path)
{ {
@@ -39,15 +37,16 @@ public static class ApplicationBuilderExtensions
} }
}); });
return app; return app;
}
public static IApplicationBuilder UseMqttServer(this IApplicationBuilder app, Action<MqttServer> configure)
{
var server = app.ApplicationServices.GetRequiredService<MqttServer>();
configure(server);
return app;
}
} }
}
public static IApplicationBuilder UseMqttServer(this IApplicationBuilder app, Action<MqttServer> configure)
{
var server = app.ApplicationServices.GetRequiredService<MqttServer>();
configure(server);
return app;
}
}
@@ -2,17 +2,18 @@
// 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 MQTTnet.Server; using MQTTnet.Server;
using System;
namespace MQTTnet.AspNetCore; namespace MQTTnet.AspNetCore
public sealed class AspNetMqttServerOptionsBuilder : MqttServerOptionsBuilder
{ {
public AspNetMqttServerOptionsBuilder(IServiceProvider serviceProvider) public sealed class AspNetMqttServerOptionsBuilder : MqttServerOptionsBuilder
{ {
ServiceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); public AspNetMqttServerOptionsBuilder(IServiceProvider serviceProvider)
} {
ServiceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
}
public IServiceProvider ServiceProvider { get; } public IServiceProvider ServiceProvider { get; }
} }
}
@@ -1,21 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Runtime.InteropServices;
namespace MQTTnet.AspNetCore;
public static class BufferExtensions
{
public static ArraySegment<byte> GetArray(this ReadOnlyMemory<byte> memory)
{
if (!MemoryMarshal.TryGetArray(memory, out var result))
{
throw new InvalidOperationException("Buffer backed by array was expected");
}
return result;
}
}
@@ -2,12 +2,14 @@
// 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 MQTTnet.Adapter; using MQTTnet.Adapter;
using MQTTnet.Diagnostics.Logger; using MQTTnet.AspNetCore.Client.Tcp;
using MQTTnet.Formatter; using MQTTnet.Formatter;
using System;
using MQTTnet.Client;
using MQTTnet.Diagnostics;
namespace MQTTnet.AspNetCore namespace MQTTnet.AspNetCore.Client
{ {
public sealed class MqttClientConnectionContextFactory : IMqttClientAdapterFactory public sealed class MqttClientConnectionContextFactory : IMqttClientAdapterFactory
{ {
@@ -20,7 +22,7 @@ namespace MQTTnet.AspNetCore
case MqttClientTcpOptions tcpOptions: case MqttClientTcpOptions tcpOptions:
{ {
var tcpConnection = new SocketConnection(tcpOptions.RemoteEndpoint); var tcpConnection = new SocketConnection(tcpOptions.RemoteEndpoint);
var formatter = new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(4096, 65535)); var formatter = new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(4096, 65535));
return new MqttConnectionContext(formatter, tcpConnection); return new MqttConnectionContext(formatter, tcpConnection);
} }
@@ -0,0 +1,26 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Runtime.InteropServices;
namespace MQTTnet.AspNetCore.Client.Tcp
{
public static class BufferExtensions
{
public static ArraySegment<byte> GetArray(this Memory<byte> memory)
{
return ((ReadOnlyMemory<byte>)memory).GetArray();
}
public static ArraySegment<byte> GetArray(this ReadOnlyMemory<byte> memory)
{
if (!MemoryMarshal.TryGetArray(memory, out var result))
{
throw new InvalidOperationException("Buffer backed by array was expected");
}
return result;
}
}
}
@@ -0,0 +1,45 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.IO.Pipelines;
namespace MQTTnet.AspNetCore.Client.Tcp
{
public class DuplexPipe : IDuplexPipe
{
public DuplexPipe(PipeReader reader, PipeWriter writer)
{
Input = reader;
Output = writer;
}
public PipeReader Input { get; }
public PipeWriter Output { get; }
public static DuplexPipePair CreateConnectionPair(PipeOptions inputOptions, PipeOptions outputOptions)
{
var input = new Pipe(inputOptions);
var output = new Pipe(outputOptions);
var transportToApplication = new DuplexPipe(output.Reader, input.Writer);
var applicationToTransport = new DuplexPipe(input.Reader, output.Writer);
return new DuplexPipePair(applicationToTransport, transportToApplication);
}
// This class exists to work around issues with value tuple on .NET Framework
public readonly struct DuplexPipePair
{
public IDuplexPipe Transport { get; }
public IDuplexPipe Application { get; }
public DuplexPipePair(IDuplexPipe transport, IDuplexPipe application)
{
Transport = transport;
Application = application;
}
}
}
}
@@ -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;
using System.Diagnostics;
using System.IO.Pipelines;
using System.Net.Sockets;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
namespace MQTTnet.AspNetCore.Client.Tcp
{
public class SocketAwaitable : ICriticalNotifyCompletion
{
private static readonly Action _callbackCompleted = () => { };
private readonly PipeScheduler _ioScheduler;
private Action _callback;
private int _bytesTransferred;
private SocketError _error;
public SocketAwaitable(PipeScheduler ioScheduler)
{
_ioScheduler = ioScheduler;
}
public bool IsCompleted => ReferenceEquals(_callback, _callbackCompleted);
public SocketAwaitable GetAwaiter() => this;
public int GetResult()
{
Debug.Assert(ReferenceEquals(_callback, _callbackCompleted));
_callback = null;
if (_error != SocketError.Success)
{
throw new SocketException((int)_error);
}
return _bytesTransferred;
}
public void OnCompleted(Action continuation)
{
if (ReferenceEquals(_callback, _callbackCompleted) ||
ReferenceEquals(Interlocked.CompareExchange(ref _callback, continuation, null), _callbackCompleted))
{
Task.Run(continuation);
}
}
public void UnsafeOnCompleted(Action continuation)
{
OnCompleted(continuation);
}
public void Complete(int bytesTransferred, SocketError socketError)
{
_error = socketError;
_bytesTransferred = bytesTransferred;
var continuation = Interlocked.Exchange(ref _callback, _callbackCompleted);
if (continuation != null)
{
_ioScheduler.Schedule(state => ((Action)state)(), continuation);
}
}
}
}
@@ -0,0 +1,273 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Http.Features;
using MQTTnet.Exceptions;
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Pipelines;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;
namespace MQTTnet.AspNetCore.Client.Tcp
{
public sealed class SocketConnection : ConnectionContext
{
volatile bool _aborted;
readonly EndPoint _endPoint;
SocketSender _sender;
SocketReceiver _receiver;
Socket _socket;
IDuplexPipe _application;
public bool IsConnected { get; private set; }
public override string ConnectionId { get; set; }
public override IFeatureCollection Features { get; }
public override IDictionary<object, object> Items { get; set; }
public override IDuplexPipe Transport { get; set; }
public SocketConnection(EndPoint endPoint)
{
_endPoint = endPoint;
}
public SocketConnection(Socket socket)
{
_socket = socket;
_endPoint = socket.RemoteEndPoint;
_sender = new SocketSender(_socket, PipeScheduler.ThreadPool);
_receiver = new SocketReceiver(_socket, PipeScheduler.ThreadPool);
}
#if NETCOREAPP3_1_OR_GREATER
public override ValueTask DisposeAsync()
#else
public Task DisposeAsync()
#endif
{
IsConnected = false;
Transport?.Output.Complete();
Transport?.Input.Complete();
_socket?.Dispose();
#if NETCOREAPP3_1_OR_GREATER
return base.DisposeAsync();
}
#else
return Task.CompletedTask;
}
#endif
public async Task StartAsync()
{
if (_socket == null)
{
_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
_sender = new SocketSender(_socket, PipeScheduler.ThreadPool);
_receiver = new SocketReceiver(_socket, PipeScheduler.ThreadPool);
await _socket.ConnectAsync(_endPoint);
}
var pair = DuplexPipe.CreateConnectionPair(PipeOptions.Default, PipeOptions.Default);
Transport = pair.Transport;
_application = pair.Application;
_ = ExecuteAsync();
IsConnected = true;
}
async Task ExecuteAsync()
{
Exception sendError = null;
try
{
// Spawn send and receive logic
var receiveTask = DoReceive();
var sendTask = DoSend();
// If the sending task completes then close the receive
// We don't need to do this in the other direction because the kestrel
// will trigger the output closing once the input is complete.
if (await Task.WhenAny(receiveTask, sendTask) == sendTask)
{
// Tell the reader it's being aborted
_socket.Dispose();
}
// Now wait for both to complete
await receiveTask;
sendError = await sendTask;
// Dispose the socket(should noop if already called)
_socket.Dispose();
}
catch (Exception ex)
{
Console.WriteLine($"Unexpected exception in {nameof(SocketConnection)}.{nameof(StartAsync)}: " + ex);
}
finally
{
// Complete the output after disposing the socket
_application.Input.Complete(sendError);
}
}
async Task DoReceive()
{
Exception error = null;
try
{
await ProcessReceives();
}
catch (SocketException ex) when (ex.SocketErrorCode == SocketError.ConnectionReset)
{
error = new MqttCommunicationException(ex);
}
catch (SocketException ex) when (ex.SocketErrorCode == SocketError.OperationAborted ||
ex.SocketErrorCode == SocketError.ConnectionAborted ||
ex.SocketErrorCode == SocketError.Interrupted ||
ex.SocketErrorCode == SocketError.InvalidArgument)
{
if (!_aborted)
{
// Calling Dispose after ReceiveAsync can cause an "InvalidArgument" error on *nix.
error = ConnectionAborted();
}
}
catch (ObjectDisposedException)
{
if (!_aborted)
{
error = ConnectionAborted();
}
}
catch (IOException ex)
{
error = ex;
}
catch (Exception ex)
{
error = new IOException(ex.Message, ex);
}
finally
{
if (_aborted)
{
error = error ?? ConnectionAborted();
}
_application.Output.Complete(error);
}
}
async Task ProcessReceives()
{
while (true)
{
// Ensure we have some reasonable amount of buffer space
var buffer = _application.Output.GetMemory();
var bytesReceived = await _receiver.ReceiveAsync(buffer);
if (bytesReceived == 0)
{
// FIN
break;
}
_application.Output.Advance(bytesReceived);
var flushTask = _application.Output.FlushAsync();
if (!flushTask.IsCompleted)
{
await flushTask;
}
var result = flushTask.GetAwaiter().GetResult();
if (result.IsCompleted)
{
// Pipe consumer is shut down, do we stop writing
break;
}
}
}
Exception ConnectionAborted()
{
return new MqttCommunicationException("Connection Aborted");
}
async Task<Exception> DoSend()
{
Exception error = null;
try
{
await ProcessSends();
}
catch (SocketException ex) when (ex.SocketErrorCode == SocketError.OperationAborted)
{
}
catch (ObjectDisposedException)
{
}
catch (IOException ex)
{
error = ex;
}
catch (Exception ex)
{
error = new IOException(ex.Message, ex);
}
finally
{
_aborted = true;
_socket.Shutdown(SocketShutdown.Both);
}
return error;
}
async Task ProcessSends()
{
while (true)
{
// Wait for data to write from the pipe producer
var result = await _application.Input.ReadAsync();
var buffer = result.Buffer;
if (result.IsCanceled)
{
break;
}
var end = buffer.End;
var isCompleted = result.IsCompleted;
if (!buffer.IsEmpty)
{
await _sender.SendAsync(buffer);
}
_application.Input.AdvanceTo(end);
if (isCompleted)
{
break;
}
}
}
}
}
@@ -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;
using System.IO.Pipelines;
using System.Net.Sockets;
namespace MQTTnet.AspNetCore.Client.Tcp
{
public class SocketReceiver
{
private readonly Socket _socket;
private readonly SocketAsyncEventArgs _eventArgs = new SocketAsyncEventArgs();
private readonly SocketAwaitable _awaitable;
public SocketReceiver(Socket socket, PipeScheduler scheduler)
{
_socket = socket;
_awaitable = new SocketAwaitable(scheduler);
_eventArgs.UserToken = _awaitable;
_eventArgs.Completed += (_, e) => ((SocketAwaitable)e.UserToken).Complete(e.BytesTransferred, e.SocketError);
}
public SocketAwaitable ReceiveAsync(Memory<byte> buffer)
{
#if NETCOREAPP2_1
_eventArgs.SetBuffer(buffer);
#else
var segment = buffer.GetArray();
_eventArgs.SetBuffer(segment.Array, segment.Offset, segment.Count);
#endif
if (!_socket.ReceiveAsync(_eventArgs))
{
_awaitable.Complete(_eventArgs.BytesTransferred, _eventArgs.SocketError);
}
return _awaitable;
}
}
}
@@ -0,0 +1,105 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO.Pipelines;
using System.Net.Sockets;
#if NETCOREAPP2_1
using System.Runtime.InteropServices;
#endif
namespace MQTTnet.AspNetCore.Client.Tcp
{
public class SocketSender
{
private readonly Socket _socket;
private readonly SocketAsyncEventArgs _eventArgs = new SocketAsyncEventArgs();
private readonly SocketAwaitable _awaitable;
private List<ArraySegment<byte>> _bufferList;
public SocketSender(Socket socket, PipeScheduler scheduler)
{
_socket = socket;
_awaitable = new SocketAwaitable(scheduler);
_eventArgs.UserToken = _awaitable;
_eventArgs.Completed += (_, e) => ((SocketAwaitable)e.UserToken).Complete(e.BytesTransferred, e.SocketError);
}
public SocketAwaitable SendAsync(in ReadOnlySequence<byte> buffers)
{
if (buffers.IsSingleSegment)
{
return SendAsync(buffers.First);
}
#if NETCOREAPP2_1
if (!_eventArgs.MemoryBuffer.Equals(Memory<byte>.Empty))
#else
if (_eventArgs.Buffer != null)
#endif
{
_eventArgs.SetBuffer(null, 0, 0);
}
_eventArgs.BufferList = GetBufferList(buffers);
if (!_socket.SendAsync(_eventArgs))
{
_awaitable.Complete(_eventArgs.BytesTransferred, _eventArgs.SocketError);
}
return _awaitable;
}
private SocketAwaitable SendAsync(ReadOnlyMemory<byte> memory)
{
// The BufferList getter is much less expensive then the setter.
if (_eventArgs.BufferList != null)
{
_eventArgs.BufferList = null;
}
#if NETCOREAPP2_1
_eventArgs.SetBuffer(MemoryMarshal.AsMemory(memory));
#else
var segment = memory.GetArray();
_eventArgs.SetBuffer(segment.Array, segment.Offset, segment.Count);
#endif
if (!_socket.SendAsync(_eventArgs))
{
_awaitable.Complete(_eventArgs.BytesTransferred, _eventArgs.SocketError);
}
return _awaitable;
}
private List<ArraySegment<byte>> GetBufferList(in ReadOnlySequence<byte> buffer)
{
Debug.Assert(!buffer.IsEmpty);
Debug.Assert(!buffer.IsSingleSegment);
if (_bufferList == null)
{
_bufferList = new List<ArraySegment<byte>>();
}
else
{
// Buffers are pooled, so it's OK to root them until the next multi-buffer write.
_bufferList.Clear();
}
foreach (var b in buffer)
{
_bufferList.Add(b.GetArray());
}
return _bufferList;
}
}
}
@@ -0,0 +1,29 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Connections;
#if NETCOREAPP3_1
using System;
#endif
namespace MQTTnet.AspNetCore
{
public static class ConnectionRouteBuilderExtensions
{
#if NETCOREAPP3_1
[Obsolete("This class is obsolete and will be removed in a future version. The recommended alternative is to use MapMqtt inside Microsoft.AspNetCore.Builder.UseEndpoints(...).")]
#endif
#if NETCOREAPP3_1 || NETCOREAPP2_1 || NETSTANDARD
public static void MapMqtt(this ConnectionsRouteBuilder connection, PathString path)
{
connection.MapConnectionHandler<MqttConnectionHandler>(path, options =>
{
options.WebSockets.SubProtocolSelector = MqttSubProtocolSelector.SelectSubProtocol;
});
}
#endif
}
}
-44
View File
@@ -1,44 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.IO.Pipelines;
namespace MQTTnet.AspNetCore;
public class DuplexPipe : IDuplexPipe
{
public DuplexPipe(PipeReader reader, PipeWriter writer)
{
Input = reader;
Output = writer;
}
public PipeReader Input { get; }
public PipeWriter Output { get; }
public static DuplexPipePair CreateConnectionPair(PipeOptions inputOptions, PipeOptions outputOptions)
{
var input = new Pipe(inputOptions);
var output = new Pipe(outputOptions);
var transportToApplication = new DuplexPipe(output.Reader, input.Writer);
var applicationToTransport = new DuplexPipe(input.Reader, output.Writer);
return new DuplexPipePair(applicationToTransport, transportToApplication);
}
// This class exists to work around issues with value tuple on .NET Framework
public readonly struct DuplexPipePair
{
public IDuplexPipe Transport { get; }
public IDuplexPipe Application { get; }
public DuplexPipePair(IDuplexPipe transport, IDuplexPipe application)
{
Transport = transport;
Application = application;
}
}
}
@@ -2,7 +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.
using System;
#if NETCOREAPP3_1_OR_GREATER
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing;
@@ -10,10 +12,8 @@ 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 => endpoints.MapConnectionHandler<MqttConnectionHandler>(pattern, options =>
{ {
options.WebSockets.SubProtocolSelector = MqttSubProtocolSelector.SelectSubProtocol; options.WebSockets.SubProtocolSelector = MqttSubProtocolSelector.SelectSubProtocol;
@@ -22,3 +22,4 @@ namespace MQTTnet.AspNetCore
} }
} }
#endif
@@ -1,7 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFrameworks>netstandard2.0;netcoreapp2.1;netcoreapp3.1;net5.0;net6.0;net7.0</TargetFrameworks>
<AssemblyName>MQTTnet.AspNetCore</AssemblyName> <AssemblyName>MQTTnet.AspNetCore</AssemblyName>
<RootNamespace>MQTTnet.AspNetCore</RootNamespace> <RootNamespace>MQTTnet.AspNetCore</RootNamespace>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild> <GeneratePackageOnBuild>True</GeneratePackageOnBuild>
@@ -31,13 +32,8 @@
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers> <DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>1591;NETSDK1138;NU1803;NU1901;NU1902</NoWarn> <NoWarn>1591;NETSDK1138</NoWarn>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors> <LangVersion>7.3</LangVersion>
<NuGetAuditMode>all</NuGetAuditMode>
<NuGetAudit>true</NuGetAudit>
<NuGetAuditLevel>low</NuGetAuditLevel>
<NuGetAuditLevel>low</NuGetAuditLevel>
<AnalysisLevel>latest-Recommended</AnalysisLevel>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@@ -48,16 +44,23 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All"/> <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
<FrameworkReference Include="Microsoft.AspNetCore.App"/>
</ItemGroup> </ItemGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DefineConstants>RELEASE;NETSTANDARD2_0</DefineConstants>
</PropertyGroup> </PropertyGroup>
<ItemGroup Condition="'$(TargetFramework)' != 'netstandard2.0' and '$(TargetFramework)' != 'netcoreapp2.1' ">
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0' or '$(TargetFramework)' == 'netcoreapp2.1' ">
<PackageReference Include="Microsoft.AspNetCore.Http.Connections" Version="1.1.0" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\MQTTnet.Server\MQTTnet.Server.csproj"/> <ProjectReference Include="..\MQTTnet\MQTTnet.csproj" />
<ProjectReference Include="..\MQTTnet\MQTTnet.csproj"/>
</ItemGroup> </ItemGroup>
</Project> </Project>
+185 -183
View File
@@ -3,7 +3,6 @@
// 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;
using System.Buffers;
using System.IO.Pipelines; using System.IO.Pipelines;
using System.Net; using System.Net;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
@@ -13,227 +12,230 @@ using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Http.Connections.Features; using Microsoft.AspNetCore.Http.Connections.Features;
using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Http.Features;
using MQTTnet.Adapter; using MQTTnet.Adapter;
using MQTTnet.AspNetCore.Client.Tcp;
using MQTTnet.Exceptions; using MQTTnet.Exceptions;
using MQTTnet.Formatter; using MQTTnet.Formatter;
using MQTTnet.Internal; using MQTTnet.Internal;
using MQTTnet.Packets; using MQTTnet.Packets;
namespace MQTTnet.AspNetCore; namespace MQTTnet.AspNetCore
public sealed class MqttConnectionContext : IMqttChannelAdapter
{ {
readonly ConnectionContext _connection; public sealed class MqttConnectionContext : IMqttChannelAdapter
readonly AsyncLock _writerLock = new();
PipeReader _input;
PipeWriter _output;
public MqttConnectionContext(MqttPacketFormatterAdapter packetFormatterAdapter, ConnectionContext connection)
{ {
PacketFormatterAdapter = packetFormatterAdapter ?? throw new ArgumentNullException(nameof(packetFormatterAdapter)); readonly ConnectionContext _connection;
_connection = connection ?? throw new ArgumentNullException(nameof(connection)); readonly AsyncLock _writerLock = new AsyncLock();
if (!(_connection is SocketConnection tcp) || tcp.IsConnected) PipeReader _input;
PipeWriter _output;
public MqttConnectionContext(MqttPacketFormatterAdapter packetFormatterAdapter, ConnectionContext connection)
{ {
_input = connection.Transport.Input; PacketFormatterAdapter = packetFormatterAdapter ?? throw new ArgumentNullException(nameof(packetFormatterAdapter));
_output = connection.Transport.Output; _connection = connection ?? throw new ArgumentNullException(nameof(connection));
}
}
public long BytesReceived { get; private set; } if (!(_connection is SocketConnection tcp) || tcp.IsConnected)
public long BytesSent { get; private set; }
public X509Certificate2 ClientCertificate
{
get
{
// mqtt over tcp
var tlsFeature = _connection.Features.Get<ITlsConnectionFeature>();
if (tlsFeature != null)
{ {
return tlsFeature.ClientCertificate; _input = connection.Transport.Input;
_output = connection.Transport.Output;
} }
// mqtt over websocket
var httpFeature = _connection.Features.Get<IHttpContextFeature>();
return httpFeature?.HttpContext?.Connection.ClientCertificate;
}
}
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
{
get
{
// mqtt over tcp
var tlsFeature = _connection.Features.Get<ITlsConnectionFeature>();
if (tlsFeature != null)
{
return true;
}
// mqtt over websocket
var httpFeature = _connection.Features.Get<IHttpContextFeature>();
if (httpFeature?.HttpContext != null)
{
return httpFeature.HttpContext.Request.IsHttps;
}
return false;
}
}
public MqttPacketFormatterAdapter PacketFormatterAdapter { get; }
public async Task ConnectAsync(CancellationToken cancellationToken)
{
if (_connection is SocketConnection tcp && !tcp.IsConnected)
{
await tcp.StartAsync().ConfigureAwait(false);
} }
_input = _connection.Transport.Input; public long BytesReceived { get; private set; }
_output = _connection.Transport.Output;
}
public Task DisconnectAsync(CancellationToken cancellationToken) public long BytesSent { get; private set; }
{
_input?.Complete();
_output?.Complete();
return Task.CompletedTask; public X509Certificate2 ClientCertificate
}
public void Dispose()
{
_writerLock.Dispose();
}
public async Task<MqttPacket> ReceivePacketAsync(CancellationToken cancellationToken)
{
try
{ {
while (!cancellationToken.IsCancellationRequested) get
{ {
ReadResult readResult; // mqtt over tcp
var readTask = _input.ReadAsync(cancellationToken); var tlsFeature = _connection.Features.Get<ITlsConnectionFeature>();
if (readTask.IsCompleted) if (tlsFeature != null)
{ {
readResult = readTask.Result; return tlsFeature.ClientCertificate;
}
else
{
readResult = await readTask.ConfigureAwait(false);
} }
var buffer = readResult.Buffer; // mqtt over websocket
var httpFeature = _connection.Features.Get<IHttpContextFeature>();
var consumed = buffer.Start; return httpFeature?.HttpContext?.Connection.ClientCertificate;
var observed = buffer.Start;
try
{
if (!buffer.IsEmpty)
{
if (PacketFormatterAdapter.TryDecode(buffer, out var packet, out consumed, out observed, out var received))
{
BytesReceived += received;
return packet;
}
}
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);
}
} }
} }
catch (Exception exception)
{
// completing the channel makes sure that there is no more data read after a protocol error
_input?.Complete(exception);
_output?.Complete(exception);
throw; public string Endpoint
{
get
{
// mqtt over tcp
#if NETCOREAPP3_1_OR_GREATER
if (_connection.RemoteEndPoint != null)
{
return _connection.RemoteEndPoint.ToString();
}
#endif
// mqtt over websocket
var httpFeature = _connection.Features.Get<IHttpConnectionFeature>();
if (httpFeature?.RemoteIpAddress != null)
{
return new IPEndPoint(httpFeature.RemoteIpAddress, httpFeature.RemotePort).ToString();
}
return null;
}
} }
cancellationToken.ThrowIfCancellationRequested(); public bool IsSecureConnection
return null; {
} get
{
// mqtt over tcp
var tlsFeature = _connection.Features.Get<ITlsConnectionFeature>();
if (tlsFeature != null)
{
return true;
}
public void ResetStatistics() // mqtt over websocket
{ var httpFeature = _connection.Features.Get<IHttpContextFeature>();
BytesReceived = 0; if (httpFeature?.HttpContext != null)
BytesSent = 0; {
} return httpFeature.HttpContext.Request.IsHttps;
}
public async Task SendPacketAsync(MqttPacket packet, CancellationToken cancellationToken) return false;
{ }
using (await _writerLock.EnterAsync(cancellationToken).ConfigureAwait(false)) }
public MqttPacketFormatterAdapter PacketFormatterAdapter { get; }
public async Task ConnectAsync(CancellationToken cancellationToken)
{
if (_connection is SocketConnection tcp && !tcp.IsConnected)
{
await tcp.StartAsync().ConfigureAwait(false);
}
_input = _connection.Transport.Input;
_output = _connection.Transport.Output;
}
public Task DisconnectAsync(CancellationToken cancellationToken)
{
_input?.Complete();
_output?.Complete();
return Task.CompletedTask;
}
public void Dispose()
{
_writerLock.Dispose();
}
public async Task<MqttPacket> ReceivePacketAsync(CancellationToken cancellationToken)
{ {
try try
{ {
var buffer = PacketFormatterAdapter.Encode(packet); while (!cancellationToken.IsCancellationRequested)
if (buffer.Payload.Length == 0)
{ {
// zero copy ReadResult readResult;
// https://github.com/dotnet/runtime/blob/e31ddfdc4f574b26231233dc10c9a9c402f40590/src/libraries/System.IO.Pipelines/src/System/IO/Pipelines/StreamPipeWriter.cs#L279 var readTask = _input.ReadAsync(cancellationToken);
await _output.WriteAsync(buffer.Packet, cancellationToken).ConfigureAwait(false); if (readTask.IsCompleted)
} {
else readResult = readTask.Result;
{ }
WritePacketBuffer(_output, buffer); else
await _output.FlushAsync(cancellationToken).ConfigureAwait(false); {
} readResult = await readTask.ConfigureAwait(false);
}
BytesSent += buffer.Length; var buffer = readResult.Buffer;
var consumed = buffer.Start;
var observed = buffer.Start;
try
{
if (!buffer.IsEmpty)
{
if (PacketFormatterAdapter.TryDecode(buffer, out var packet, out consumed, out observed, out var received))
{
BytesReceived += received;
return packet;
}
}
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);
}
}
} }
finally catch (Exception exception)
{ {
PacketFormatterAdapter.Cleanup(); // completing the channel makes sure that there is no more data read after a protocol error
_input?.Complete(exception);
_output?.Complete(exception);
throw;
}
cancellationToken.ThrowIfCancellationRequested();
return null;
}
public void ResetStatistics()
{
BytesReceived = 0;
BytesSent = 0;
}
public async Task SendPacketAsync(MqttPacket packet, CancellationToken cancellationToken)
{
using (await _writerLock.EnterAsync(cancellationToken).ConfigureAwait(false))
{
try
{
var buffer = PacketFormatterAdapter.Encode(packet);
if (buffer.Payload.Count == 0)
{
// zero copy
// https://github.com/dotnet/runtime/blob/e31ddfdc4f574b26231233dc10c9a9c402f40590/src/libraries/System.IO.Pipelines/src/System/IO/Pipelines/StreamPipeWriter.cs#L279
await _output.WriteAsync(buffer.Packet, cancellationToken).ConfigureAwait(false);
}
else
{
WritePacketBuffer(_output, buffer);
await _output.FlushAsync(cancellationToken).ConfigureAwait(false);
}
BytesSent += buffer.Length;
}
finally
{
PacketFormatterAdapter.Cleanup();
}
} }
} }
}
static void WritePacketBuffer(PipeWriter output, MqttPacketBuffer buffer) static void WritePacketBuffer(PipeWriter output, MqttPacketBuffer buffer)
{ {
// copy MqttPacketBuffer's Packet and Payload to the same buffer block of PipeWriter // 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 // MqttPacket will be transmitted within the bounds of a WebSocket frame after PipeWriter.FlushAsync
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; buffer.Payload.AsSpan().CopyTo(span.Slice(buffer.Packet.Count));
buffer.Payload.CopyTo(destination: span.Slice(offset));
output.Advance(buffer.Length); output.Advance(buffer.Length);
}
} }
} }
@@ -2,56 +2,57 @@
// 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.Threading.Tasks;
using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Connections.Features; using Microsoft.AspNetCore.Connections.Features;
using MQTTnet.Adapter; using MQTTnet.Adapter;
using MQTTnet.Diagnostics.Logger;
using MQTTnet.Formatter;
using MQTTnet.Server; using MQTTnet.Server;
using System;
using System.Threading.Tasks;
using MQTTnet.Diagnostics;
using MQTTnet.Formatter;
namespace MQTTnet.AspNetCore; namespace MQTTnet.AspNetCore
public sealed class MqttConnectionHandler : ConnectionHandler, IMqttServerAdapter
{ {
MqttServerOptions _serverOptions; public sealed class MqttConnectionHandler : ConnectionHandler, IMqttServerAdapter
public Func<IMqttChannelAdapter, Task> ClientHandler { get; set; }
public void Dispose()
{ {
} MqttServerOptions _serverOptions;
public Func<IMqttChannelAdapter, Task> ClientHandler { get; set; }
public override async Task OnConnectedAsync(ConnectionContext connection) public override async Task OnConnectedAsync(ConnectionContext connection)
{
// required for websocket transport to work
var transferFormatFeature = connection.Features.Get<ITransferFormatFeature>();
if (transferFormatFeature != null)
{ {
transferFormatFeature.ActiveFormat = TransferFormat.Binary; // required for websocket transport to work
} var transferFormatFeature = connection.Features.Get<ITransferFormatFeature>();
if (transferFormatFeature != null)
var formatter = new MqttPacketFormatterAdapter(new MqttBufferWriter(_serverOptions.WriterBufferSize, _serverOptions.WriterBufferSizeMax));
using (var adapter = new MqttConnectionContext(formatter, connection))
{
var clientHandler = ClientHandler;
if (clientHandler != null)
{ {
await clientHandler(adapter).ConfigureAwait(false); transferFormatFeature.ActiveFormat = TransferFormat.Binary;
}
var formatter = new MqttPacketFormatterAdapter(new MqttBufferWriter(_serverOptions.WriterBufferSize, _serverOptions.WriterBufferSizeMax));
using (var adapter = new MqttConnectionContext(formatter, connection))
{
var clientHandler = ClientHandler;
if (clientHandler != null)
{
await clientHandler(adapter).ConfigureAwait(false);
}
} }
} }
}
public Task StartAsync(MqttServerOptions options, IMqttNetLogger logger) public Task StartAsync(MqttServerOptions options, IMqttNetLogger logger)
{ {
_serverOptions = options; _serverOptions = options;
return Task.CompletedTask; return Task.CompletedTask;
} }
public Task StopAsync() public Task StopAsync()
{ {
return Task.CompletedTask; return Task.CompletedTask;
}
public void Dispose()
{
}
} }
} }
+47 -29
View File
@@ -7,42 +7,60 @@ using System.Collections.Generic;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using MQTTnet.Diagnostics.Logger; using MQTTnet.Adapter;
using MQTTnet.Diagnostics;
using MQTTnet.Server; using MQTTnet.Server;
namespace MQTTnet.AspNetCore; namespace MQTTnet.AspNetCore
public sealed class MqttHostedServer : MqttServer, IHostedService
{ {
readonly IHostApplicationLifetime _hostApplicationLifetime; public sealed class MqttHostedServer : MqttServer, IHostedService
readonly MqttServerFactory _mqttFactory;
public MqttHostedServer(
IHostApplicationLifetime hostApplicationLifetime,
MqttServerFactory mqttFactory,
MqttServerOptions options,
IEnumerable<IMqttServerAdapter> adapters,
IMqttNetLogger logger) : base(options, adapters, logger)
{ {
_mqttFactory = mqttFactory ?? throw new ArgumentNullException(nameof(mqttFactory)); readonly MqttFactory _mqttFactory;
_hostApplicationLifetime = hostApplicationLifetime; #if NETCOREAPP3_1_OR_GREATER
} readonly IHostApplicationLifetime _hostApplicationLifetime;
public MqttHostedServer(IHostApplicationLifetime hostApplicationLifetime, MqttFactory mqttFactory,
MqttServerOptions options, IEnumerable<IMqttServerAdapter> adapters, IMqttNetLogger logger) : base(
options,
adapters,
logger)
{
_mqttFactory = mqttFactory ?? throw new ArgumentNullException(nameof(mqttFactory));
_hostApplicationLifetime = hostApplicationLifetime;
}
#else
public MqttHostedServer(MqttFactory mqttFactory,
MqttServerOptions options, IEnumerable<IMqttServerAdapter> adapters, IMqttNetLogger logger) : base(
options,
adapters,
logger)
{
_mqttFactory = mqttFactory ?? throw new ArgumentNullException(nameof(mqttFactory));
}
#endif
public async Task StartAsync(CancellationToken cancellationToken)
{
// The yield makes sure that the hosted service is considered up and running.
await Task.Yield();
_hostApplicationLifetime.ApplicationStarted.Register(OnStarted); public async Task StartAsync(CancellationToken cancellationToken)
} {
// The yield makes sure that the hosted service is considered up and running.
await Task.Yield();
#if NETCOREAPP3_1_OR_GREATER
_hostApplicationLifetime.ApplicationStarted.Register(OnStarted);
#else
_ = StartAsync();
#endif
public Task StopAsync(CancellationToken cancellationToken) }
{
return StopAsync(_mqttFactory.CreateMqttServerStopOptionsBuilder().Build());
}
void OnStarted() public Task StopAsync(CancellationToken cancellationToken)
{ {
_ = StartAsync(); return StopAsync(_mqttFactory.CreateMqttServerStopOptionsBuilder().Build());
}
#if NETCOREAPP3_1_OR_GREATER
private void OnStarted()
{
_ = StartAsync();
}
#endif
} }
} }
@@ -7,28 +7,31 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
namespace MQTTnet.AspNetCore; namespace MQTTnet.AspNetCore
public static class MqttSubProtocolSelector
{ {
public static string SelectSubProtocol(HttpRequest request) public static class MqttSubProtocolSelector
{ {
ArgumentNullException.ThrowIfNull(request); public static string SelectSubProtocol(HttpRequest request)
string subProtocol = null;
if (request.Headers.TryGetValue("Sec-WebSocket-Protocol", out var requestedSubProtocolValues))
{ {
subProtocol = SelectSubProtocol(requestedSubProtocolValues); if (request == null) throw new ArgumentNullException(nameof(request));
string subProtocol = null;
if (request.Headers.TryGetValue("Sec-WebSocket-Protocol", out var requestedSubProtocolValues))
{
subProtocol = SelectSubProtocol(requestedSubProtocolValues);
}
return subProtocol;
} }
public static string SelectSubProtocol(IList<string> requestedSubProtocolValues)
{
if (requestedSubProtocolValues == null) throw new ArgumentNullException(nameof(requestedSubProtocolValues));
return subProtocol; // 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"));
}
} }
}
public static string SelectSubProtocol(IList<string> requestedSubProtocolValues)
{
ArgumentNullException.ThrowIfNull(requestedSubProtocolValues);
// Order the protocols to also match "mqtt", "mqttv-3.1", "mqttv-3.11" etc.
return requestedSubProtocolValues.OrderByDescending(p => p.Length).FirstOrDefault(p => p.ToLower().StartsWith("mqtt"));
}
}
@@ -3,66 +3,68 @@
// 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;
using System.Net;
using System.Net.WebSockets; using System.Net.WebSockets;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using MQTTnet.Adapter; using MQTTnet.Adapter;
using MQTTnet.Diagnostics.Logger; using MQTTnet.Diagnostics;
using MQTTnet.Formatter; using MQTTnet.Formatter;
using MQTTnet.Implementations; using MQTTnet.Implementations;
using MQTTnet.Server; using MQTTnet.Server;
namespace MQTTnet.AspNetCore; namespace MQTTnet.AspNetCore
public sealed class MqttWebSocketServerAdapter : IMqttServerAdapter
{ {
IMqttNetLogger _logger = MqttNetNullLogger.Instance; public sealed class MqttWebSocketServerAdapter : IMqttServerAdapter
public Func<IMqttChannelAdapter, Task> ClientHandler { get; set; }
public void Dispose()
{ {
} IMqttNetLogger _logger = MqttNetNullLogger.Instance;
public async Task RunWebSocketConnectionAsync(WebSocket webSocket, HttpContext httpContext) public Func<IMqttChannelAdapter, Task> ClientHandler { get; set; }
{
ArgumentNullException.ThrowIfNull(webSocket);
var remoteAddress = httpContext.Connection.RemoteIpAddress; public void Dispose()
var remoteEndPoint = remoteAddress == null ? null : new IPEndPoint(remoteAddress, httpContext.Connection.RemotePort);
var clientCertificate = await httpContext.Connection.GetClientCertificateAsync().ConfigureAwait(false);
try
{ {
var isSecureConnection = clientCertificate != null; }
var clientHandler = ClientHandler; public async Task RunWebSocketConnectionAsync(WebSocket webSocket, HttpContext httpContext)
if (clientHandler != null) {
if (webSocket == null)
{ {
var formatter = new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535)); throw new ArgumentNullException(nameof(webSocket));
var channel = new MqttWebSocketChannel(webSocket, remoteEndPoint, isSecureConnection, clientCertificate); }
using (var channelAdapter = new MqttChannelAdapter(channel, formatter, _logger)) var endpoint = $"{httpContext.Connection.RemoteIpAddress}:{httpContext.Connection.RemotePort}";
var clientCertificate = await httpContext.Connection.GetClientCertificateAsync().ConfigureAwait(false);
try
{
var isSecureConnection = clientCertificate != null;
var clientHandler = ClientHandler;
if (clientHandler != null)
{ {
await clientHandler(channelAdapter).ConfigureAwait(false); var formatter = new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535));
var channel = new MqttWebSocketChannel(webSocket, endpoint, isSecureConnection, clientCertificate);
using (var channelAdapter = new MqttChannelAdapter(channel, formatter, _logger))
{
await clientHandler(channelAdapter).ConfigureAwait(false);
}
} }
} }
finally
{
clientCertificate?.Dispose();
}
} }
finally
public Task StartAsync(MqttServerOptions options, IMqttNetLogger logger)
{ {
clientCertificate?.Dispose(); _logger = logger ?? throw new ArgumentNullException(nameof(logger));
return Task.CompletedTask;
} }
}
public Task StartAsync(MqttServerOptions options, IMqttNetLogger logger) public Task StopAsync()
{ {
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); return Task.CompletedTask;
return Task.CompletedTask; }
}
public Task StopAsync()
{
return Task.CompletedTask;
} }
} }
+117 -112
View File
@@ -2,134 +2,139 @@
// 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.Runtime.InteropServices;
using MQTTnet.Adapter; using MQTTnet.Adapter;
using MQTTnet.Exceptions; using MQTTnet.Exceptions;
using MQTTnet.Formatter; using MQTTnet.Formatter;
using MQTTnet.Packets; using MQTTnet.Packets;
using System;
using System.Buffers;
using System.Linq;
using System.Runtime.InteropServices;
namespace MQTTnet.AspNetCore; namespace MQTTnet.AspNetCore
public static class ReaderExtensions
{ {
public static bool TryDecode( public static class ReaderExtensions
this MqttPacketFormatterAdapter formatter,
in ReadOnlySequence<byte> input,
out MqttPacket packet,
out SequencePosition consumed,
out SequencePosition observed,
out int bytesRead)
{ {
ArgumentNullException.ThrowIfNull(formatter); public static bool TryDecode(this MqttPacketFormatterAdapter formatter,
in ReadOnlySequence<byte> input,
packet = null; out MqttPacket packet,
consumed = input.Start; out SequencePosition consumed,
observed = input.End; out SequencePosition observed,
bytesRead = 0; out int bytesRead)
var copy = input;
if (copy.Length < 2)
{ {
return false; if (formatter == null) throw new ArgumentNullException(nameof(formatter));
}
if (!TryReadBodyLength(ref copy, out var headerLength, out var bodyLength)) packet = null;
{ consumed = input.Start;
return false; observed = input.End;
} bytesRead = 0;
var copy = input;
var fixedHeader = copy.First.Span[0]; if (copy.Length < 2)
copy = copy.Slice(headerLength);
if (copy.Length < bodyLength)
{
return false;
}
var bodySlice = copy.Slice(0, bodyLength);
var bodySegment = GetArraySegment(ref bodySlice);
var receivedMqttPacket = new ReceivedMqttPacket(fixedHeader, bodySegment, headerLength + bodyLength);
if (formatter.ProtocolVersion == MqttProtocolVersion.Unknown)
{
formatter.DetectProtocolVersion(receivedMqttPacket);
}
packet = formatter.Decode(receivedMqttPacket);
consumed = bodySlice.End;
observed = bodySlice.End;
bytesRead = headerLength + bodyLength;
return true;
}
static ArraySegment<byte> GetArraySegment(ref ReadOnlySequence<byte> input)
{
if (input.IsSingleSegment && MemoryMarshal.TryGetArray(input.First, out var segment))
{
return segment;
}
// Should be rare
var array = input.ToArray();
return new ArraySegment<byte>(array);
}
static void ThrowProtocolViolationException(ReadOnlySpan<byte> valueSpan, int index)
{
throw new MqttProtocolViolationException($"Remaining length is invalid (Data={string.Join(",", valueSpan.Slice(1, index).ToArray())}).");
}
static bool TryReadBodyLength(ref ReadOnlySequence<byte> input, out int headerLength, out int bodyLength)
{
var valueSequence = input.Slice(0, Math.Min(5, input.Length));
if (valueSequence.IsSingleSegment)
{
var valueSpan = valueSequence.FirstSpan;
return TryReadBodyLength(valueSpan, out headerLength, out bodyLength);
}
else
{
Span<byte> valueSpan = stackalloc byte[8];
valueSequence.CopyTo(valueSpan);
valueSpan = valueSpan.Slice(0, (int)valueSequence.Length);
return TryReadBodyLength(valueSpan, out headerLength, out bodyLength);
}
}
static bool TryReadBodyLength(ReadOnlySpan<byte> span, 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;
do
{
if (index == span.Length)
{ {
return false; return false;
} }
encodedByte = span[index]; if (!TryReadBodyLength(ref copy, out var headerLength, out var bodyLength))
index++;
value += (byte)(encodedByte & 127) * multiplier;
if (multiplier > 128 * 128 * 128)
{ {
ThrowProtocolViolationException(span, index); return false;
} }
multiplier *= 128; var fixedHeader = copy.First.Span[0];
} while ((encodedByte & 128) != 0); copy = copy.Slice(headerLength);
if (copy.Length < bodyLength)
{
return false;
}
headerLength = index; var bodySlice = copy.Slice(0, bodyLength);
bodyLength = value; var bodySegment = GetArraySegment(ref bodySlice);
return true;
var receivedMqttPacket = new ReceivedMqttPacket(fixedHeader, bodySegment, headerLength + bodyLength);
if (formatter.ProtocolVersion == MqttProtocolVersion.Unknown)
{
formatter.DetectProtocolVersion(receivedMqttPacket);
}
packet = formatter.Decode(receivedMqttPacket);
consumed = bodySlice.End;
observed = bodySlice.End;
bytesRead = headerLength + bodyLength;
return true;
}
static ArraySegment<byte> GetArraySegment(ref ReadOnlySequence<byte> input)
{
if (input.IsSingleSegment && MemoryMarshal.TryGetArray(input.First, out var segment))
{
return segment;
}
// Should be rare
var array = input.ToArray();
return new ArraySegment<byte>(array);
}
static bool TryReadBodyLength(ref ReadOnlySequence<byte> input, out int headerLength, out int bodyLength)
{
var valueSequence = input.Slice(0, Math.Min(5, input.Length));
if (valueSequence.IsSingleSegment)
{
var valueSpan =
#if NET5_0_OR_GREATER
valueSequence.FirstSpan;
#else
valueSequence.First.Span;
#endif
return TryReadBodyLength(valueSpan, out headerLength, out bodyLength);
}
else
{
Span<byte> valueSpan = stackalloc byte[8];
valueSequence.CopyTo(valueSpan);
valueSpan = valueSpan.Slice(0, (int)valueSequence.Length);
return TryReadBodyLength(valueSpan, out headerLength, out bodyLength);
}
}
static bool TryReadBodyLength(ReadOnlySpan<byte> span, 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;
do
{
if (index == span.Length)
{
return false;
}
encodedByte = span[index];
index++;
value += (byte)(encodedByte & 127) * multiplier;
if (multiplier > 128 * 128 * 128)
{
ThrowProtocolViolationException(span, index);
}
multiplier *= 128;
} while ((encodedByte & 128) != 0);
headerLength = index;
bodyLength = value;
return true;
}
static void ThrowProtocolViolationException(ReadOnlySpan<byte> valueSpan, int index)
{
throw new MqttProtocolViolationException($"Remaining length is invalid (Data={string.Join(",", valueSpan.Slice(1, index).ToArray())}).");
}
} }
} }
@@ -6,104 +6,125 @@ using System;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using MQTTnet.Diagnostics.Logger; using MQTTnet.Adapter;
using MQTTnet.Diagnostics;
using MQTTnet.Implementations;
using MQTTnet.Server; using MQTTnet.Server;
using MQTTnet.Server.Internal.Adapter;
namespace MQTTnet.AspNetCore; namespace MQTTnet.AspNetCore
public static class ServiceCollectionExtensions
{ {
public static IServiceCollection AddHostedMqttServer(this IServiceCollection services, MqttServerOptions options) public static class ServiceCollectionExtensions
{ {
ArgumentNullException.ThrowIfNull(services); public static IServiceCollection AddHostedMqttServer(this IServiceCollection services, MqttServerOptions options)
ArgumentNullException.ThrowIfNull(options); {
if (services == null)
services.AddSingleton(options);
services.AddHostedMqttServer();
return services;
}
public static IServiceCollection AddHostedMqttServer(this IServiceCollection services, Action<MqttServerOptionsBuilder> configure)
{
ArgumentNullException.ThrowIfNull(services);
var serverOptionsBuilder = new MqttServerOptionsBuilder();
configure?.Invoke(serverOptionsBuilder);
var options = serverOptionsBuilder.Build();
return AddHostedMqttServer(services, options);
}
public static void AddHostedMqttServer(this IServiceCollection services)
{
// The user may have these services already registered.
services.TryAddSingleton<IMqttNetLogger>(MqttNetNullLogger.Instance);
services.TryAddSingleton(new MqttServerFactory());
services.AddSingleton<MqttHostedServer>();
services.AddSingleton<IHostedService>(s => s.GetService<MqttHostedServer>());
services.AddSingleton<MqttServer>(s => s.GetService<MqttHostedServer>());
}
public static IServiceCollection AddHostedMqttServerWithServices(this IServiceCollection services, Action<AspNetMqttServerOptionsBuilder> configure)
{
ArgumentNullException.ThrowIfNull(services);
services.AddSingleton(
s =>
{ {
var builder = new AspNetMqttServerOptionsBuilder(s); throw new ArgumentNullException(nameof(services));
configure(builder); }
return builder.Build();
});
services.AddHostedMqttServer(); if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
return services; services.AddSingleton(options);
} services.AddHostedMqttServer();
public static IServiceCollection AddMqttConnectionHandler(this IServiceCollection services) return services;
{ }
services.AddSingleton<MqttConnectionHandler>();
services.AddSingleton<IMqttServerAdapter>(s => s.GetService<MqttConnectionHandler>());
return services; public static IServiceCollection AddHostedMqttServer(this IServiceCollection services, Action<MqttServerOptionsBuilder> configure)
} {
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
public static void AddMqttLogger(this IServiceCollection services, IMqttNetLogger logger) var serverOptionsBuilder = new MqttServerOptionsBuilder();
{
ArgumentNullException.ThrowIfNull(services);
services.AddSingleton(logger); configure?.Invoke(serverOptionsBuilder);
}
public static IServiceCollection AddMqttServer(this IServiceCollection serviceCollection, Action<MqttServerOptionsBuilder> configure = null) var options = serverOptionsBuilder.Build();
{
ArgumentNullException.ThrowIfNull(serviceCollection);
serviceCollection.AddMqttConnectionHandler(); return AddHostedMqttServer(services, options);
serviceCollection.AddHostedMqttServer(configure); }
return serviceCollection; public static void AddHostedMqttServer(this IServiceCollection services)
} {
// The user may have these services already registered.
services.TryAddSingleton<IMqttNetLogger>(MqttNetNullLogger.Instance);
services.TryAddSingleton(new MqttFactory());
public static IServiceCollection AddMqttTcpServerAdapter(this IServiceCollection services) services.AddSingleton<MqttHostedServer>();
{ services.AddSingleton<IHostedService>(s => s.GetService<MqttHostedServer>());
services.AddSingleton<MqttTcpServerAdapter>(); services.AddSingleton<MqttServer>(s => s.GetService<MqttHostedServer>());
services.AddSingleton<IMqttServerAdapter>(s => s.GetService<MqttTcpServerAdapter>()); }
return services; public static IServiceCollection AddHostedMqttServerWithServices(this IServiceCollection services, Action<AspNetMqttServerOptionsBuilder> configure)
} {
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
public static IServiceCollection AddMqttWebSocketServerAdapter(this IServiceCollection services) services.AddSingleton(
{ s =>
services.AddSingleton<MqttWebSocketServerAdapter>(); {
services.AddSingleton<IMqttServerAdapter>(s => s.GetService<MqttWebSocketServerAdapter>()); var builder = new AspNetMqttServerOptionsBuilder(s);
configure(builder);
return builder.Build();
});
return services; services.AddHostedMqttServer();
return services;
}
public static IServiceCollection AddMqttConnectionHandler(this IServiceCollection services)
{
services.AddSingleton<MqttConnectionHandler>();
services.AddSingleton<IMqttServerAdapter>(s => s.GetService<MqttConnectionHandler>());
return services;
}
public static void AddMqttLogger(this IServiceCollection services, IMqttNetLogger logger)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
services.AddSingleton(logger);
}
public static IServiceCollection AddMqttServer(this IServiceCollection serviceCollection, Action<MqttServerOptionsBuilder> configure = null)
{
if (serviceCollection is null)
{
throw new ArgumentNullException(nameof(serviceCollection));
}
serviceCollection.AddMqttConnectionHandler();
serviceCollection.AddHostedMqttServer(configure);
return serviceCollection;
}
public static IServiceCollection AddMqttTcpServerAdapter(this IServiceCollection services)
{
services.AddSingleton<MqttTcpServerAdapter>();
services.AddSingleton<IMqttServerAdapter>(s => s.GetService<MqttTcpServerAdapter>());
return services;
}
public static IServiceCollection AddMqttWebSocketServerAdapter(this IServiceCollection services)
{
services.AddSingleton<MqttWebSocketServerAdapter>();
services.AddSingleton<IMqttServerAdapter>(s => s.GetService<MqttWebSocketServerAdapter>());
return services;
}
} }
} }
@@ -1,77 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Diagnostics;
using System.IO.Pipelines;
using System.Net.Sockets;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
namespace MQTTnet.AspNetCore;
public class SocketAwaitable : ICriticalNotifyCompletion
{
static readonly Action _callbackCompleted = () =>
{
};
readonly PipeScheduler _ioScheduler;
int _bytesTransferred;
Action _callback;
SocketError _error;
public SocketAwaitable(PipeScheduler ioScheduler)
{
_ioScheduler = ioScheduler;
}
public bool IsCompleted => ReferenceEquals(_callback, _callbackCompleted);
public void Complete(int bytesTransferred, SocketError socketError)
{
_error = socketError;
_bytesTransferred = bytesTransferred;
var continuation = Interlocked.Exchange(ref _callback, _callbackCompleted);
if (continuation != null)
{
_ioScheduler.Schedule(state => ((Action)state)(), continuation);
}
}
public SocketAwaitable GetAwaiter()
{
return this;
}
public int GetResult()
{
Debug.Assert(ReferenceEquals(_callback, _callbackCompleted));
_callback = null;
if (_error != SocketError.Success)
{
throw new SocketException((int)_error);
}
return _bytesTransferred;
}
public void OnCompleted(Action continuation)
{
if (ReferenceEquals(_callback, _callbackCompleted) || ReferenceEquals(Interlocked.CompareExchange(ref _callback, continuation, null), _callbackCompleted))
{
Task.Run(continuation);
}
}
public void UnsafeOnCompleted(Action continuation)
{
OnCompleted(continuation);
}
}
@@ -1,261 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Pipelines;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Http.Features;
using MQTTnet.Exceptions;
namespace MQTTnet.AspNetCore;
public sealed class SocketConnection : ConnectionContext
{
readonly EndPoint _endPoint;
volatile bool _aborted;
IDuplexPipe _application;
SocketReceiver _receiver;
SocketSender _sender;
Socket _socket;
public SocketConnection(EndPoint endPoint)
{
_endPoint = endPoint;
}
public SocketConnection(Socket socket)
{
_socket = socket;
_endPoint = socket.RemoteEndPoint;
_sender = new SocketSender(_socket, PipeScheduler.ThreadPool);
_receiver = new SocketReceiver(_socket, PipeScheduler.ThreadPool);
}
public override string ConnectionId { get; set; }
public override IFeatureCollection Features { get; }
public bool IsConnected { get; private set; }
public override IDictionary<object, object> Items { get; set; }
public override IDuplexPipe Transport { get; set; }
public override ValueTask DisposeAsync()
{
IsConnected = false;
Transport?.Output.Complete();
Transport?.Input.Complete();
_socket?.Dispose();
return base.DisposeAsync();
}
public async Task StartAsync()
{
if (_socket == null)
{
_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
_sender = new SocketSender(_socket, PipeScheduler.ThreadPool);
_receiver = new SocketReceiver(_socket, PipeScheduler.ThreadPool);
await _socket.ConnectAsync(_endPoint);
}
var pair = DuplexPipe.CreateConnectionPair(PipeOptions.Default, PipeOptions.Default);
Transport = pair.Transport;
_application = pair.Application;
_ = ExecuteAsync();
IsConnected = true;
}
Exception ConnectionAborted()
{
return new MqttCommunicationException("Connection Aborted");
}
async Task DoReceive()
{
Exception error = null;
try
{
await ProcessReceives();
}
catch (SocketException ex) when (ex.SocketErrorCode == SocketError.ConnectionReset)
{
error = new MqttCommunicationException(ex);
}
catch (SocketException ex) when (ex.SocketErrorCode == SocketError.OperationAborted || ex.SocketErrorCode == SocketError.ConnectionAborted ||
ex.SocketErrorCode == SocketError.Interrupted || ex.SocketErrorCode == SocketError.InvalidArgument)
{
if (!_aborted)
{
// Calling Dispose after ReceiveAsync can cause an "InvalidArgument" error on *nix.
error = ConnectionAborted();
}
}
catch (ObjectDisposedException)
{
if (!_aborted)
{
error = ConnectionAborted();
}
}
catch (IOException ex)
{
error = ex;
}
catch (Exception ex)
{
error = new IOException(ex.Message, ex);
}
finally
{
if (_aborted)
{
error = error ?? ConnectionAborted();
}
_application.Output.Complete(error);
}
}
async Task<Exception> DoSend()
{
Exception error = null;
try
{
await ProcessSends();
}
catch (SocketException ex) when (ex.SocketErrorCode == SocketError.OperationAborted)
{
}
catch (ObjectDisposedException)
{
}
catch (IOException ex)
{
error = ex;
}
catch (Exception ex)
{
error = new IOException(ex.Message, ex);
}
finally
{
_aborted = true;
_socket.Shutdown(SocketShutdown.Both);
}
return error;
}
async Task ExecuteAsync()
{
Exception sendError = null;
try
{
// Spawn send and receive logic
var receiveTask = DoReceive();
var sendTask = DoSend();
// If the sending task completes then close the receive
// We don't need to do this in the other direction because the kestrel
// will trigger the output closing once the input is complete.
if (await Task.WhenAny(receiveTask, sendTask).ConfigureAwait(false) == sendTask)
{
// Tell the reader it's being aborted
_socket.Dispose();
}
// Now wait for both to complete
await receiveTask;
sendError = await sendTask;
// Dispose the socket(should noop if already called)
_socket.Dispose();
}
catch (Exception ex)
{
Console.WriteLine($"Unexpected exception in {nameof(SocketConnection)}.{nameof(StartAsync)}: " + ex);
}
finally
{
// Complete the output after disposing the socket
await _application.Input.CompleteAsync(sendError).ConfigureAwait(false);
}
}
async Task ProcessReceives()
{
while (true)
{
// Ensure we have some reasonable amount of buffer space
var buffer = _application.Output.GetMemory();
var bytesReceived = await _receiver.ReceiveAsync(buffer);
if (bytesReceived == 0)
{
// FIN
break;
}
_application.Output.Advance(bytesReceived);
var flushTask = _application.Output.FlushAsync();
if (!flushTask.IsCompleted)
{
await flushTask;
}
var result = flushTask.GetAwaiter().GetResult();
if (result.IsCompleted)
{
// Pipe consumer is shut down, do we stop writing
break;
}
}
}
async Task ProcessSends()
{
while (true)
{
// Wait for data to write from the pipe producer
var result = await _application.Input.ReadAsync();
var buffer = result.Buffer;
if (result.IsCanceled)
{
break;
}
var end = buffer.End;
var isCompleted = result.IsCompleted;
if (!buffer.IsEmpty)
{
await _sender.SendAsync(buffer);
}
_application.Input.AdvanceTo(end);
if (isCompleted)
{
break;
}
}
}
}
@@ -1,36 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.IO.Pipelines;
using System.Net.Sockets;
namespace MQTTnet.AspNetCore;
public sealed class SocketReceiver
{
readonly SocketAwaitable _awaitable;
readonly SocketAsyncEventArgs _eventArgs = new();
readonly Socket _socket;
public SocketReceiver(Socket socket, PipeScheduler scheduler)
{
_socket = socket;
_awaitable = new SocketAwaitable(scheduler);
_eventArgs.UserToken = _awaitable;
_eventArgs.Completed += (_, e) => ((SocketAwaitable)e.UserToken).Complete(e.BytesTransferred, e.SocketError);
}
public SocketAwaitable ReceiveAsync(Memory<byte> buffer)
{
_eventArgs.SetBuffer(buffer);
if (!_socket.ReceiveAsync(_eventArgs))
{
_awaitable.Complete(_eventArgs.BytesTransferred, _eventArgs.SocketError);
}
return _awaitable;
}
}
-93
View File
@@ -1,93 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO.Pipelines;
using System.Net.Sockets;
using System.Runtime.InteropServices;
namespace MQTTnet.AspNetCore;
public sealed class SocketSender
{
readonly SocketAwaitable _awaitable;
readonly SocketAsyncEventArgs _eventArgs = new();
readonly Socket _socket;
List<ArraySegment<byte>> _bufferList;
public SocketSender(Socket socket, PipeScheduler scheduler)
{
_socket = socket;
_awaitable = new SocketAwaitable(scheduler);
_eventArgs.UserToken = _awaitable;
_eventArgs.Completed += (_, e) => ((SocketAwaitable)e.UserToken).Complete(e.BytesTransferred, e.SocketError);
}
public SocketAwaitable SendAsync(in ReadOnlySequence<byte> buffers)
{
if (buffers.IsSingleSegment)
{
return SendAsync(buffers.First);
}
if (!_eventArgs.MemoryBuffer.Equals(Memory<byte>.Empty))
{
_eventArgs.SetBuffer(null, 0, 0);
}
_eventArgs.BufferList = GetBufferList(buffers);
if (!_socket.SendAsync(_eventArgs))
{
_awaitable.Complete(_eventArgs.BytesTransferred, _eventArgs.SocketError);
}
return _awaitable;
}
List<ArraySegment<byte>> GetBufferList(in ReadOnlySequence<byte> buffer)
{
Debug.Assert(!buffer.IsEmpty);
Debug.Assert(!buffer.IsSingleSegment);
if (_bufferList == null)
{
_bufferList = new List<ArraySegment<byte>>();
}
else
{
// Buffers are pooled, so it's OK to root them until the next multi-buffer write.
_bufferList.Clear();
}
foreach (var b in buffer)
{
_bufferList.Add(b.GetArray());
}
return _bufferList;
}
SocketAwaitable SendAsync(ReadOnlyMemory<byte> memory)
{
// The BufferList getter is much less expensive then the setter.
if (_eventArgs.BufferList != null)
{
_eventArgs.BufferList = null;
}
_eventArgs.SetBuffer(MemoryMarshal.AsMemory(memory));
if (!_socket.SendAsync(_eventArgs))
{
_awaitable.Complete(_eventArgs.BytesTransferred, _eventArgs.SocketError);
}
return _awaitable;
}
}
@@ -7,7 +7,7 @@ using System.IO;
using System.Threading; using System.Threading;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using MQTTnet.Adapter; using MQTTnet.Adapter;
using MQTTnet.Diagnostics.Logger; using MQTTnet.Diagnostics;
using MQTTnet.Formatter; using MQTTnet.Formatter;
using MQTTnet.Packets; using MQTTnet.Packets;
using MQTTnet.Tests.Mockups; using MQTTnet.Tests.Mockups;
+11 -11
View File
@@ -4,7 +4,7 @@
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
using MQTTnet.Diagnostics.Logger; using MQTTnet.Diagnostics;
namespace MQTTnet.Benchmarks namespace MQTTnet.Benchmarks
{ {
@@ -15,13 +15,13 @@ namespace MQTTnet.Benchmarks
{ {
MqttNetNullLogger _nullLogger; MqttNetNullLogger _nullLogger;
MqttNetSourceLogger _sourceNullLogger; MqttNetSourceLogger _sourceNullLogger;
MqttNetEventLogger _eventLogger; MqttNetEventLogger _eventLogger;
MqttNetSourceLogger _sourceEventLogger; MqttNetSourceLogger _sourceEventLogger;
MqttNetEventLogger _eventLoggerNoListener; MqttNetEventLogger _eventLoggerNoListener;
MqttNetSourceLogger _sourceEventLoggerNoListener; MqttNetSourceLogger _sourceEventLoggerNoListener;
bool _useHandler; bool _useHandler;
[GlobalSetup] [GlobalSetup]
@@ -29,11 +29,11 @@ namespace MQTTnet.Benchmarks
{ {
_nullLogger = new MqttNetNullLogger(); _nullLogger = new MqttNetNullLogger();
_sourceNullLogger = _nullLogger.WithSource("Source"); _sourceNullLogger = _nullLogger.WithSource("Source");
_eventLogger = new MqttNetEventLogger(); _eventLogger = new MqttNetEventLogger();
_eventLogger.LogMessagePublished += OnLogMessagePublished; _eventLogger.LogMessagePublished += OnLogMessagePublished;
_sourceEventLogger = _eventLogger.WithSource("Source"); _sourceEventLogger = _eventLogger.WithSource("Source");
_eventLoggerNoListener = new MqttNetEventLogger(); _eventLoggerNoListener = new MqttNetEventLogger();
_sourceEventLoggerNoListener = _eventLoggerNoListener.WithSource("Source"); _sourceEventLoggerNoListener = _eventLoggerNoListener.WithSource("Source");
} }
@@ -50,7 +50,7 @@ namespace MQTTnet.Benchmarks
public void Log_10000_Messages_No_Listener() public void Log_10000_Messages_No_Listener()
{ {
_useHandler = false; _useHandler = false;
for (var i = 0; i < 10000; i++) for (var i = 0; i < 10000; i++)
{ {
_sourceEventLoggerNoListener.Verbose("test log message {0}", "parameter"); _sourceEventLoggerNoListener.Verbose("test log message {0}", "parameter");
@@ -61,24 +61,24 @@ namespace MQTTnet.Benchmarks
public void Log_10000_Messages_With_To_String() public void Log_10000_Messages_With_To_String()
{ {
_useHandler = true; _useHandler = true;
for (var i = 0; i < 10000; i++) for (var i = 0; i < 10000; i++)
{ {
_sourceEventLogger.Verbose("test log message {0}", "parameter"); _sourceEventLogger.Verbose("test log message {0}", "parameter");
} }
} }
[Benchmark] [Benchmark]
public void Log_10000_Messages_Without_To_String() public void Log_10000_Messages_Without_To_String()
{ {
_useHandler = false; _useHandler = false;
for (var i = 0; i < 10000; i++) for (var i = 0; i < 10000; i++)
{ {
_sourceEventLogger.Verbose("test log message {0}", "parameter"); _sourceEventLogger.Verbose("test log message {0}", "parameter");
} }
} }
[Benchmark] [Benchmark]
public void Log_10000_Messages_With_NullLogger() public void Log_10000_Messages_With_NullLogger()
{ {
@@ -4,28 +4,35 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<DebugType>Full</DebugType> <DebugType>Full</DebugType>
<TargetFramework>net8.0</TargetFramework> <TargetFrameworks>net6.0;net7.0</TargetFrameworks>
<LangVersion>7.2</LangVersion>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<EnableNETAnalyzers>false</EnableNETAnalyzers> <EnableNETAnalyzers>false</EnableNETAnalyzers>
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild> <EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers> <DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
<NoWarn>1591;NETSDK1138;NU1803;NU1901;NU1902;CS8892</NoWarn> <NoWarn>1591;NETSDK1138</NoWarn>
<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.13.2" />
<PackageReference Include="Microsoft.AspNetCore" Version="2.2.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net5.0'">
<PackageReference Include="System.IO.Pipelines" Version="5.0.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net6.0'">
<PackageReference Include="System.IO.Pipelines" Version="5.0.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
<PackageReference Include="System.IO.Pipelines" Version="7.0.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\Source\MQTTnet.Server\MQTTnet.Server.csproj"/> <ProjectReference Include="..\..\Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj" />
<ProjectReference Include="..\..\Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj"/> <ProjectReference Include="..\MQTTnet.Tests\MQTTnet.Tests.csproj" />
<ProjectReference Include="..\MQTTnet.Tests\MQTTnet.Tests.csproj"/>
<ProjectReference Include="..\MQTTnet\MQTTnet.csproj"/>
</ItemGroup> </ItemGroup>
</Project> </Project>
@@ -2,216 +2,232 @@
// 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 BenchmarkDotNet.Attributes;
using MQTTnet.Client;
using MQTTnet.Server;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using MQTTnet.Packets;
using MQTTnet.Server;
namespace MQTTnet.Benchmarks; namespace MQTTnet.Benchmarks
[MemoryDiagnoser]
public class MessageDeliveryBenchmark : BaseBenchmark
{ {
List<string> _allSubscribedTopics; // Keep track of the subset of topics that are subscribed [MemoryDiagnoser]
CancellationTokenSource _cancellationTokenSource; public class MessageDeliveryBenchmark : BaseBenchmark
object _lockMsgCount;
int _messagesExpectedCount;
int _messagesReceivedCount;
Dictionary<string, IMqttClient> _mqttPublisherClientsByPublisherName;
MqttServer _mqttServer;
List<IMqttClient> _mqttSubscriberClients;
Dictionary<string, string> _publisherByTopic;
List<MqttApplicationMessage> _topicPublishMessages;
Dictionary<string, List<string>> _topicsByPublisher;
[Params(1000, 10000)] public int NumPublishers;
[Params(5, 10, 20, 50)] public int NumSubscribedTopicsPerSubscriber;
[Params(10)] public int NumSubscribers;
[Params(1, 5)] public int NumTopicsPerPublisher;
[GlobalCleanup]
public void Cleanup()
{ {
foreach (var mp in _mqttPublisherClientsByPublisherName) List<MqttApplicationMessage> _topicPublishMessages;
[Params(1, 5)]
public int NumTopicsPerPublisher;
[Params(1000, 10000)]
public int NumPublishers;
[Params(10)]
public int NumSubscribers;
[Params(5, 10, 20, 50)]
public int NumSubscribedTopicsPerSubscriber;
object _lockMsgCount;
int _messagesReceivedCount;
int _messagesExpectedCount;
CancellationTokenSource _cancellationTokenSource;
MqttServer _mqttServer;
List<IMqttClient> _mqttSubscriberClients;
Dictionary<string, IMqttClient> _mqttPublisherClientsByPublisherName;
Dictionary<string, List<string>> _topicsByPublisher;
Dictionary<string, string> _publisherByTopic;
List<string> _allSubscribedTopics; // Keep track of the subset of topics that are subscribed
[GlobalSetup]
public void Setup()
{ {
var mqttPublisherClient = mp.Value; _lockMsgCount = new object();
mqttPublisherClient.DisconnectAsync().GetAwaiter().GetResult();
mqttPublisherClient.Dispose();
}
_mqttPublisherClientsByPublisherName.Clear(); Dictionary<string, List<string>> singleWildcardTopicsByPublisher;
Dictionary<string, List<string>> multiWildcardTopicsByPublisher;
foreach (var mqttSubscriber in _mqttSubscriberClients) TopicGenerator.Generate(NumPublishers, NumTopicsPerPublisher, out _topicsByPublisher, out singleWildcardTopicsByPublisher, out multiWildcardTopicsByPublisher);
{
mqttSubscriber.DisconnectAsync().GetAwaiter().GetResult();
mqttSubscriber.Dispose();
}
_mqttSubscriberClients.Clear(); var topics = _topicsByPublisher.First().Value;
_topicPublishMessages = new List<MqttApplicationMessage>();
_mqttServer.StopAsync().GetAwaiter().GetResult(); // Prepare messages, same for each publisher
_mqttServer.Dispose(); foreach (var topic in topics)
_mqttServer = null;
}
/// <summary>
/// Publish messages and wait for messages sent to subscribers
/// </summary>
[Benchmark]
public void DeliverMessages()
{
// There should be one message received per publish for each subscribed topic
_messagesExpectedCount = NumSubscribedTopicsPerSubscriber * NumSubscribers;
// Loop for a while and exchange messages
_messagesReceivedCount = 0;
_cancellationTokenSource = new CancellationTokenSource();
// same payload for all messages
var payload = new byte[] { 1, 2, 3, 4 };
var tasks = new List<Task>();
// publish a message for each subscribed topic
foreach (var topic in _allSubscribedTopics)
{
var message = new MqttApplicationMessageBuilder().WithTopic(topic).WithPayload(payload).Build();
// pick the correct publisher
var publisherName = _publisherByTopic[topic];
var publisherClient = _mqttPublisherClientsByPublisherName[publisherName];
_ = publisherClient.PublishAsync(message);
}
// Wait one message per publish to be received by subscriber (in the subscriber's application message handler)
try
{
Task.Delay(30000, _cancellationTokenSource.Token).GetAwaiter().GetResult();
}
catch
{
}
_cancellationTokenSource.Dispose();
if (_messagesReceivedCount < _messagesExpectedCount)
{
throw new Exception(string.Format("Messages Received Count mismatch, expected {0}, received {1}", _messagesExpectedCount, _messagesReceivedCount));
}
}
[GlobalSetup]
public void Setup()
{
_lockMsgCount = new object();
Dictionary<string, List<string>> singleWildcardTopicsByPublisher;
Dictionary<string, List<string>> multiWildcardTopicsByPublisher;
TopicGenerator.Generate(NumPublishers, NumTopicsPerPublisher, out _topicsByPublisher, out singleWildcardTopicsByPublisher, out multiWildcardTopicsByPublisher);
var topics = _topicsByPublisher.First().Value;
_topicPublishMessages = new List<MqttApplicationMessage>();
// Prepare messages, same for each publisher
foreach (var topic in topics)
{
var message = new MqttApplicationMessageBuilder().WithTopic(topic).WithPayload(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 }).Build();
_topicPublishMessages.Add(message);
}
// Create server
var serverFactory = new MqttServerFactory();
var clientFactory = new MqttClientFactory();
var serverOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
_mqttServer = serverFactory.CreateMqttServer(serverOptions);
_mqttServer.StartAsync().GetAwaiter().GetResult();
// Create publisher clients
_mqttPublisherClientsByPublisherName = new Dictionary<string, IMqttClient>();
foreach (var pt in _topicsByPublisher)
{
var publisherName = pt.Key;
var mqttClient = clientFactory.CreateMqttClient();
var publisherOptions = new MqttClientOptionsBuilder().WithTcpServer("localhost").WithClientId(publisherName).WithKeepAlivePeriod(TimeSpan.FromSeconds(30)).Build();
mqttClient.ConnectAsync(publisherOptions).GetAwaiter().GetResult();
_mqttPublisherClientsByPublisherName.Add(publisherName, mqttClient);
}
// Create subscriber clients
_mqttSubscriberClients = new List<IMqttClient>();
for (var i = 0; i < NumSubscribers; i++)
{
var mqttSubscriberClient = clientFactory.CreateMqttClient();
_mqttSubscriberClients.Add(mqttSubscriberClient);
var subscriberOptions = new MqttClientOptionsBuilder().WithTcpServer("localhost").WithClientId("subscriber" + i).Build();
mqttSubscriberClient.ApplicationMessageReceivedAsync += r =>
{ {
// count messages and signal cancellation when expected message count is reached var message = new MqttApplicationMessageBuilder()
lock (_lockMsgCount) .WithTopic(topic)
.WithPayload(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 })
.Build();
_topicPublishMessages.Add(message);
}
// Create server
var factory = new MqttFactory();
var serverOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
_mqttServer = factory.CreateMqttServer(serverOptions);
_mqttServer.StartAsync().GetAwaiter().GetResult();
// Create publisher clients
_mqttPublisherClientsByPublisherName = new Dictionary<string, IMqttClient>();
foreach (var pt in _topicsByPublisher)
{
var publisherName = pt.Key;
var mqttClient = factory.CreateMqttClient();
var publisherOptions = new MqttClientOptionsBuilder()
.WithTcpServer("localhost")
.WithClientId(publisherName)
.WithKeepAlivePeriod(TimeSpan.FromSeconds(30))
.Build();
mqttClient.ConnectAsync(publisherOptions).GetAwaiter().GetResult();
_mqttPublisherClientsByPublisherName.Add(publisherName, mqttClient);
}
// Create subscriber clients
_mqttSubscriberClients = new List<IMqttClient>();
for (var i = 0; i < NumSubscribers; i++)
{
var mqttSubscriberClient = factory.CreateMqttClient();
_mqttSubscriberClients.Add(mqttSubscriberClient);
var subscriberOptions = new MqttClientOptionsBuilder()
.WithTcpServer("localhost")
.WithClientId("subscriber" + i)
.Build();
mqttSubscriberClient.ApplicationMessageReceivedAsync += r =>
{ {
++_messagesReceivedCount; // count messages and signal cancellation when expected message count is reached
if (_messagesReceivedCount == _messagesExpectedCount) lock (_lockMsgCount)
{ {
_cancellationTokenSource.Cancel(); ++_messagesReceivedCount;
if (_messagesReceivedCount == _messagesExpectedCount)
{
_cancellationTokenSource.Cancel();
}
} }
return Task.CompletedTask;
};
mqttSubscriberClient.ConnectAsync(subscriberOptions).GetAwaiter().GetResult();
}
List<string> allTopics = new List<string>();
_publisherByTopic = new Dictionary<string, string>();
foreach (var t in _topicsByPublisher)
{
foreach (var topic in t.Value)
{
_publisherByTopic.Add(topic, t.Key);
allTopics.Add(topic);
} }
}
return Task.CompletedTask; // Subscribe to NumSubscribedTopics topics spread across all topics
}; _allSubscribedTopics = new List<string>();
mqttSubscriberClient.ConnectAsync(subscriberOptions).GetAwaiter().GetResult();
var totalNumTopics = NumPublishers * NumTopicsPerPublisher;
int topicIndexStep = totalNumTopics / (NumSubscribedTopicsPerSubscriber * NumSubscribers);
if (topicIndexStep * NumSubscribedTopicsPerSubscriber * NumSubscribers != totalNumTopics)
{
throw new System.Exception(
String.Format("The total number of topics must be divisible by the number of subscribed topics across all subscribers. Total number of topics: {0}, topic step: {1}",
totalNumTopics, topicIndexStep
));
}
var topicIndex = 0;
foreach (var mqttSubscriber in _mqttSubscriberClients)
{
for (var i = 0; i < NumSubscribedTopicsPerSubscriber; ++i, topicIndex += topicIndexStep)
{
var topic = allTopics[topicIndex];
_allSubscribedTopics.Add(topic);
var subOptions = new Client.MqttClientSubscribeOptionsBuilder().WithTopicFilter(
new Packets.MqttTopicFilter() { Topic = topic })
.Build();
mqttSubscriber.SubscribeAsync(subOptions).GetAwaiter().GetResult();
}
}
Task.Delay(1000).GetAwaiter().GetResult();
} }
/// <summary>
var allTopics = new List<string>(); /// Publish messages and wait for messages sent to subscribers
_publisherByTopic = new Dictionary<string, string>(); /// </summary>
foreach (var t in _topicsByPublisher) [Benchmark]
public void DeliverMessages()
{ {
foreach (var topic in t.Value) // There should be one message received per publish for each subscribed topic
_messagesExpectedCount = NumSubscribedTopicsPerSubscriber * NumSubscribers;
// Loop for a while and exchange messages
_messagesReceivedCount = 0;
_cancellationTokenSource = new CancellationTokenSource();
// same payload for all messages
var payload = new byte[] { 1, 2, 3, 4 };
var tasks = new List<Task>();
// publish a message for each subscribed topic
foreach (var topic in _allSubscribedTopics)
{ {
_publisherByTopic.Add(topic, t.Key); var message = new MqttApplicationMessageBuilder()
allTopics.Add(topic); .WithTopic(topic)
.WithPayload(payload)
.Build();
// pick the correct publisher
var publisherName = _publisherByTopic[topic];
var publisherClient = _mqttPublisherClientsByPublisherName[publisherName];
_ = publisherClient.PublishAsync(message);
}
// Wait one message per publish to be received by subscriber (in the subscriber's application message handler)
try
{
Task.Delay(30000, _cancellationTokenSource.Token).GetAwaiter().GetResult();
}
catch
{
}
_cancellationTokenSource.Dispose();
if (_messagesReceivedCount < _messagesExpectedCount)
{
throw new Exception(string.Format("Messages Received Count mismatch, expected {0}, received {1}", _messagesExpectedCount, _messagesReceivedCount));
} }
} }
// Subscribe to NumSubscribedTopics topics spread across all topics [GlobalCleanup]
_allSubscribedTopics = new List<string>(); public void Cleanup()
var totalNumTopics = NumPublishers * NumTopicsPerPublisher;
var topicIndexStep = totalNumTopics / (NumSubscribedTopicsPerSubscriber * NumSubscribers);
if (topicIndexStep * NumSubscribedTopicsPerSubscriber * NumSubscribers != totalNumTopics)
{ {
throw new Exception( foreach (var mp in _mqttPublisherClientsByPublisherName)
string.Format(
"The total number of topics must be divisible by the number of subscribed topics across all subscribers. Total number of topics: {0}, topic step: {1}",
totalNumTopics,
topicIndexStep));
}
var topicIndex = 0;
foreach (var mqttSubscriber in _mqttSubscriberClients)
{
for (var i = 0; i < NumSubscribedTopicsPerSubscriber; ++i, topicIndex += topicIndexStep)
{ {
var topic = allTopics[topicIndex]; var mqttPublisherClient = mp.Value;
_allSubscribedTopics.Add(topic); mqttPublisherClient.DisconnectAsync().GetAwaiter().GetResult();
var subOptions = new MqttClientSubscribeOptionsBuilder().WithTopicFilter(new MqttTopicFilter { Topic = topic }).Build(); mqttPublisherClient.Dispose();
mqttSubscriber.SubscribeAsync(subOptions).GetAwaiter().GetResult();
} }
} _mqttPublisherClientsByPublisherName.Clear();
Task.Delay(1000).GetAwaiter().GetResult(); foreach (var mqttSubscriber in _mqttSubscriberClients)
{
mqttSubscriber.DisconnectAsync().GetAwaiter().GetResult();
mqttSubscriber.Dispose();
}
_mqttSubscriberClients.Clear();
_mqttServer.StopAsync().GetAwaiter().GetResult();
_mqttServer.Dispose();
_mqttServer = null;
}
} }
} }
@@ -4,45 +4,48 @@
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
using MQTTnet.Client;
using MQTTnet.Server; using MQTTnet.Server;
namespace MQTTnet.Benchmarks; namespace MQTTnet.Benchmarks
[SimpleJob(RuntimeMoniker.Net60)]
[RPlotExporter]
[RankColumn]
[MemoryDiagnoser]
public class MessageProcessingBenchmark : BaseBenchmark
{ {
MqttApplicationMessage _message; [SimpleJob(RuntimeMoniker.Net60)]
IMqttClient _mqttClient; [RPlotExporter, RankColumn]
MqttServer _mqttServer; [MemoryDiagnoser]
public class MessageProcessingBenchmark : BaseBenchmark
[Benchmark]
public void Send_10000_Messages()
{ {
for (var i = 0; i < 10000; i++) MqttServer _mqttServer;
IMqttClient _mqttClient;
MqttApplicationMessage _message;
[GlobalSetup]
public void Setup()
{ {
_mqttClient.PublishAsync(_message).GetAwaiter().GetResult(); var serverOptions = new MqttServerOptionsBuilder().Build();
var factory = new MqttFactory();
_mqttServer = factory.CreateMqttServer(serverOptions);
_mqttClient = factory.CreateMqttClient();
_mqttServer.StartAsync().GetAwaiter().GetResult();
var clientOptions = new MqttClientOptionsBuilder()
.WithTcpServer("localhost").Build();
_mqttClient.ConnectAsync(clientOptions).GetAwaiter().GetResult();
_message = new MqttApplicationMessageBuilder()
.WithTopic("A")
.Build();
}
[Benchmark]
public void Send_10000_Messages()
{
for (var i = 0; i < 10000; i++)
{
_mqttClient.PublishAsync(_message).GetAwaiter().GetResult();
}
} }
} }
}
[GlobalSetup]
public void Setup()
{
var serverOptions = new MqttServerOptionsBuilder().Build();
var serverFactory = new MqttServerFactory();
_mqttServer = serverFactory.CreateMqttServer(serverOptions);
var clientFactory = new MqttClientFactory();
_mqttClient = clientFactory.CreateMqttClient();
_mqttServer.StartAsync().GetAwaiter().GetResult();
var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("localhost").Build();
_mqttClient.ConnectAsync(clientOptions).GetAwaiter().GetResult();
_message = new MqttApplicationMessageBuilder().WithTopic("A").Build();
}
}
@@ -4,10 +4,12 @@
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
using MQTTnet.Client;
using Microsoft.AspNetCore; using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using MQTTnet.AspNetCore.Client;
using MQTTnet.AspNetCore; using MQTTnet.AspNetCore;
using MQTTnet.Diagnostics.Logger; using MQTTnet.Diagnostics;
namespace MQTTnet.Benchmarks namespace MQTTnet.Benchmarks
{ {
@@ -36,7 +38,7 @@ namespace MQTTnet.Benchmarks
}) })
.Build(); .Build();
var factory = new MqttClientFactory(); var factory = new MqttFactory();
_mqttClient = factory.CreateMqttClient(new MqttNetEventLogger(), new MqttClientConnectionContextFactory()); _mqttClient = factory.CreateMqttClient(new MqttNetEventLogger(), new MqttClientConnectionContextFactory());
_host.StartAsync().GetAwaiter().GetResult(); _host.StartAsync().GetAwaiter().GetResult();
@@ -3,86 +3,89 @@
// 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;
using System.Buffers;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using MQTTnet.Channel; using MQTTnet.Channel;
using MQTTnet.Diagnostics.Logger;
using MQTTnet.Implementations; using MQTTnet.Implementations;
using MQTTnet.Server; using MQTTnet.Server;
using MQTTnet.Server.Internal.Adapter; using System.Threading;
using System.Threading.Tasks;
using BenchmarkDotNet.Jobs;
using MQTTnet.Client;
using MQTTnet.Diagnostics;
namespace MQTTnet.Benchmarks; namespace MQTTnet.Benchmarks
[SimpleJob(RuntimeMoniker.Net60)]
[MemoryDiagnoser]
public class MqttTcpChannelBenchmark : BaseBenchmark
{ {
IMqttChannel _clientChannel; [SimpleJob(RuntimeMoniker.Net60)]
MqttServer _mqttServer; [MemoryDiagnoser]
IMqttChannel _serverChannel; public class MqttTcpChannelBenchmark : BaseBenchmark
[Benchmark]
public async Task Send_10000_Chunks()
{ {
var size = 5; MqttServer _mqttServer;
var iterations = 10000; IMqttChannel _serverChannel;
IMqttChannel _clientChannel;
await Task.WhenAll(WriteAsync(iterations, size), ReadAsync(iterations, size)); [GlobalSetup]
} public void Setup()
[GlobalSetup]
public void Setup()
{
var serverFactory = new MqttServerFactory();
var tcpServer = new MqttTcpServerAdapter();
tcpServer.ClientHandler += args =>
{ {
_serverChannel = (IMqttChannel)args.GetType().GetField("_channel", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(args); var factory = new MqttFactory();
var tcpServer = new MqttTcpServerAdapter();
tcpServer.ClientHandler += args =>
{
_serverChannel =
(IMqttChannel)args.GetType().GetField("_channel",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)
.GetValue(args);
return Task.CompletedTask; return Task.CompletedTask;
}; };
var serverOptions = new MqttServerOptionsBuilder().Build(); var serverOptions = new MqttServerOptionsBuilder().Build();
_mqttServer = serverFactory.CreateMqttServer(serverOptions, new[] { tcpServer }, new MqttNetEventLogger()); _mqttServer = factory.CreateMqttServer(serverOptions, new[] { tcpServer }, new MqttNetEventLogger());
_mqttServer.StartAsync().GetAwaiter().GetResult();
_mqttServer.StartAsync().GetAwaiter().GetResult(); var clientOptions = new MqttClientOptionsBuilder()
.WithTcpServer("localhost").Build();
var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("localhost").Build(); var tcpOptions = (MqttClientTcpOptions)clientOptions.ChannelOptions;
_clientChannel = new MqttTcpChannel(new MqttClientOptions { ChannelOptions = tcpOptions });
var tcpOptions = (MqttClientTcpOptions)clientOptions.ChannelOptions; _clientChannel.ConnectAsync(CancellationToken.None).GetAwaiter().GetResult();
_clientChannel = new MqttTcpChannel(new MqttClientOptions { ChannelOptions = tcpOptions });
_clientChannel.ConnectAsync(CancellationToken.None).GetAwaiter().GetResult();
}
async Task ReadAsync(int iterations, int size)
{
await Task.Yield();
var expected = iterations * size;
long read = 0;
while (read < expected)
{
var readResult = await _clientChannel.ReadAsync(new byte[size], 0, size, CancellationToken.None).ConfigureAwait(false);
read += readResult;
} }
}
async Task WriteAsync(int iterations, int size) [Benchmark]
{ public async Task Send_10000_Chunks()
await Task.Yield();
var buffer = new ReadOnlySequence<byte>(new byte[size]);
for (var i = 0; i < iterations; i++)
{ {
await _serverChannel.WriteAsync(buffer, true, CancellationToken.None).ConfigureAwait(false); var size = 5;
var iterations = 10000;
await Task.WhenAll(WriteAsync(iterations, size), ReadAsync(iterations, size));
}
async Task ReadAsync(int iterations, int size)
{
await Task.Yield();
var expected = iterations * size;
long read = 0;
while (read < expected)
{
var readResult = await _clientChannel.ReadAsync(new byte[size], 0, size, CancellationToken.None).ConfigureAwait(false);
read += readResult;
}
}
async Task WriteAsync(int iterations, int size)
{
await Task.Yield();
var buffer = new ArraySegment<byte>(new byte[size]);
for (var i = 0; i < iterations; i++)
{
await _serverChannel.WriteAsync(buffer, true, CancellationToken.None).ConfigureAwait(false);
}
} }
} }
} }
+3 -3
View File
@@ -71,7 +71,7 @@ namespace MQTTnet.Benchmarks
{ {
if (type != typeof(BaseBenchmark)) if (type != typeof(BaseBenchmark))
{ {
benchmarks.Add(type); benchmarks.Add(type);
} }
} }
} }
@@ -87,7 +87,7 @@ namespace MQTTnet.Benchmarks
} }
// Allow for preselection to avoid developer frustration. // Allow for preselection to avoid developer frustration.
if (int.TryParse(arguments[0], out var benchmarkIndex)) if (int.TryParse(arguments[0], out var benchmarkIndex))
{ {
_selectedBenchmarkIndex = benchmarkIndex; _selectedBenchmarkIndex = benchmarkIndex;
@@ -111,7 +111,7 @@ namespace MQTTnet.Benchmarks
{ {
Console.Clear(); Console.Clear();
Console.WriteLine($"MQTTnet - Benchmarks"); Console.WriteLine($"MQTTnet - Benchmarks ({TargetFrameworkProvider.TargetFramework})");
Console.WriteLine("-----------------------------------------------"); Console.WriteLine("-----------------------------------------------");
Console.ForegroundColor = ConsoleColor.Yellow; Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine("Press arrow keys for benchmark selection"); Console.WriteLine("Press arrow keys for benchmark selection");
@@ -31,12 +31,12 @@ namespace MQTTnet.Benchmarks
.WithPayload(new byte[10 * 1024]) .WithPayload(new byte[10 * 1024])
.Build(); .Build();
var packet = MqttPublishPacketFactory.Create(mqttMessage); var packet = MqttPacketFactories.Publish.Create(mqttMessage);
var buffer = mqttPacketFormatter.Encode(packet); var buffer = mqttPacketFormatter.Encode(packet);
stream = new MemoryStream(); stream = new MemoryStream();
stream.Write(buffer.Packet); stream.Write(buffer.Packet);
stream.Write(buffer.Payload.ToArray()); stream.Write(buffer.Payload);
mqttPacketFormatter.Cleanup(); mqttPacketFormatter.Cleanup();
} }
@@ -145,7 +145,7 @@ namespace MQTTnet.Benchmarks
out SequencePosition observed, out SequencePosition observed,
out int bytesRead) out int bytesRead)
{ {
ArgumentNullException.ThrowIfNull(formatter); if (formatter == null) throw new ArgumentNullException(nameof(formatter));
packet = null; packet = null;
consumed = input.Start; consumed = input.Start;
@@ -2,7 +2,6 @@ using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
using MQTTnet.Formatter; using MQTTnet.Formatter;
using System; using System;
using System.Buffers;
using System.IO; using System.IO;
using System.IO.Pipelines; using System.IO.Pipelines;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -41,7 +40,7 @@ namespace MQTTnet.Benchmarks
stream.Position = 0; stream.Position = 0;
var output = PipeWriter.Create(stream); var output = PipeWriter.Create(stream);
if (buffer.Payload.Length == 0) if (buffer.Payload.Count == 0)
{ {
await output.WriteAsync(buffer.Packet).ConfigureAwait(false); await output.WriteAsync(buffer.Packet).ConfigureAwait(false);
} }
@@ -61,7 +60,7 @@ namespace MQTTnet.Benchmarks
var span = output.GetSpan(buffer.Length); var span = output.GetSpan(buffer.Length);
buffer.Packet.AsSpan().CopyTo(span); buffer.Packet.AsSpan().CopyTo(span);
buffer.Payload.CopyTo(span.Slice(buffer.Packet.Count)); buffer.Payload.AsSpan().CopyTo(span.Slice(buffer.Packet.Count));
output.Advance(buffer.Length); output.Advance(buffer.Length);
} }
@@ -13,9 +13,7 @@ using MQTTnet.Channel;
using MQTTnet.Formatter; using MQTTnet.Formatter;
using MQTTnet.Formatter.V3; using MQTTnet.Formatter.V3;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
using MQTTnet.Diagnostics.Logger; using MQTTnet.Diagnostics;
using System.Buffers;
using System.Net;
namespace MQTTnet.Benchmarks namespace MQTTnet.Benchmarks
{ {
@@ -77,7 +75,7 @@ namespace MQTTnet.Benchmarks
_position = _buffer.Offset; _position = _buffer.Offset;
} }
public EndPoint RemoteEndPoint { get; set; } public string Endpoint { get; } = string.Empty;
public bool IsSecureConnection { get; } = false; public bool IsSecureConnection { get; } = false;
@@ -106,7 +104,7 @@ namespace MQTTnet.Benchmarks
return Task.FromResult(count); return Task.FromResult(count);
} }
public Task WriteAsync(ReadOnlySequence<byte> buffer, bool isEndOfPacket, CancellationToken cancellationToken) public Task WriteAsync(ArraySegment<byte> buffer, bool isEndOfPacket, CancellationToken cancellationToken)
{ {
throw new NotSupportedException(); throw new NotSupportedException();
} }
+50 -44
View File
@@ -2,59 +2,65 @@
// 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 BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using MQTTnet.Client;
using MQTTnet.Server;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using BenchmarkDotNet.Attributes;
using MQTTnet.Server;
namespace MQTTnet.Benchmarks; namespace MQTTnet.Benchmarks
[MemoryDiagnoser]
public class SubscribeBenchmark : BaseBenchmark
{ {
const int NumPublishers = 1; [MemoryDiagnoser]
const int NumTopicsPerPublisher = 10000; public class SubscribeBenchmark : BaseBenchmark
IMqttClient _mqttClient;
MqttServer _mqttServer;
List<string> _topics;
[GlobalCleanup]
public void Cleanup()
{ {
_mqttClient.DisconnectAsync().GetAwaiter().GetResult(); MqttServer _mqttServer;
_mqttServer.StopAsync().GetAwaiter().GetResult(); IMqttClient _mqttClient;
_mqttServer.Dispose();
}
[GlobalSetup] const int NumPublishers = 1;
public void Setup() const int NumTopicsPerPublisher = 10000;
{
TopicGenerator.Generate(NumPublishers, NumTopicsPerPublisher, out var topicsByPublisher, out var singleWildcardTopicsByPublisher, out var multiWildcardTopicsByPublisher);
_topics = topicsByPublisher.Values.First();
var serverOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build(); List<string> _topics;
var serverFactory = new MqttServerFactory(); [GlobalSetup]
_mqttServer = serverFactory.CreateMqttServer(serverOptions); public void Setup()
var clientFactory = new MqttClientFactory();
_mqttClient = clientFactory.CreateMqttClient();
_mqttServer.StartAsync().GetAwaiter().GetResult();
var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("localhost").Build();
_mqttClient.ConnectAsync(clientOptions).GetAwaiter().GetResult();
}
[Benchmark]
public void Subscribe_10000_Topics()
{
foreach (var topic in _topics)
{ {
var subscribeOptions = new MqttClientSubscribeOptionsBuilder().WithTopicFilter(topic).Build(); TopicGenerator.Generate(NumPublishers, NumTopicsPerPublisher, out var topicsByPublisher, out var singleWildcardTopicsByPublisher, out var multiWildcardTopicsByPublisher);
_topics = topicsByPublisher.Values.First();
_mqttClient.SubscribeAsync(subscribeOptions).GetAwaiter().GetResult(); var serverOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
var factory = new MqttFactory();
_mqttServer = factory.CreateMqttServer(serverOptions);
_mqttClient = factory.CreateMqttClient();
_mqttServer.StartAsync().GetAwaiter().GetResult();
var clientOptions = new MqttClientOptionsBuilder()
.WithTcpServer("localhost").Build();
_mqttClient.ConnectAsync(clientOptions).GetAwaiter().GetResult();
}
[GlobalCleanup]
public void Cleanup()
{
_mqttClient.DisconnectAsync().GetAwaiter().GetResult();
_mqttServer.StopAsync().GetAwaiter().GetResult();
_mqttServer.Dispose();
}
[Benchmark]
public void Subscribe_10000_Topics()
{
foreach (var topic in _topics)
{
var subscribeOptions = new MqttClientSubscribeOptionsBuilder()
.WithTopicFilter(topic, Protocol.MqttQualityOfServiceLevel.AtMostOnce)
.Build();
_mqttClient.SubscribeAsync(subscribeOptions).GetAwaiter().GetResult();
}
} }
} }
} }
@@ -9,7 +9,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
using MQTTnet.AspNetCore; using MQTTnet.AspNetCore.Client.Tcp;
namespace MQTTnet.Benchmarks namespace MQTTnet.Benchmarks
{ {
@@ -73,8 +73,15 @@ namespace MQTTnet.Benchmarks
static bool LegacyMethodByStringSplit(string topic, string filter) static bool LegacyMethodByStringSplit(string topic, string filter)
{ {
ArgumentNullException.ThrowIfNull(topic); if (topic == null)
ArgumentNullException.ThrowIfNull(filter); {
throw new ArgumentNullException(nameof(topic));
}
if (filter == null)
{
throw new ArgumentNullException(nameof(filter));
}
if (string.Equals(topic, filter, StringComparison.Ordinal)) if (string.Equals(topic, filter, StringComparison.Ordinal))
{ {
@@ -2,56 +2,63 @@
// 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 BenchmarkDotNet.Attributes;
using MQTTnet.Client;
using MQTTnet.Server;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using BenchmarkDotNet.Attributes;
using MQTTnet.Server;
namespace MQTTnet.Benchmarks; namespace MQTTnet.Benchmarks
[MemoryDiagnoser]
public class UnsubscribeBenchmark : BaseBenchmark
{ {
const int NumPublishers = 1; [MemoryDiagnoser]
const int NumTopicsPerPublisher = 10000; public class UnsubscribeBenchmark : BaseBenchmark
IMqttClient _mqttClient;
MqttServer _mqttServer;
List<string> _topics;
[GlobalSetup]
public void Setup()
{ {
TopicGenerator.Generate(NumPublishers, NumTopicsPerPublisher, out var topicsByPublisher, out var singleWildcardTopicsByPublisher, out var multiWildcardTopicsByPublisher); MqttServer _mqttServer;
_topics = topicsByPublisher.Values.First(); IMqttClient _mqttClient;
var serverOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build(); const int NumPublishers = 1;
const int NumTopicsPerPublisher = 10000;
var serverFactory = new MqttServerFactory(); List<string> _topics;
_mqttServer = serverFactory.CreateMqttServer(serverOptions);
var clientFactory = new MqttClientFactory();
_mqttClient = clientFactory.CreateMqttClient();
_mqttServer.StartAsync().GetAwaiter().GetResult(); [GlobalSetup]
public void Setup()
var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("localhost").Build();
_mqttClient.ConnectAsync(clientOptions).GetAwaiter().GetResult();
foreach (var topic in _topics)
{ {
var subscribeOptions = new MqttClientSubscribeOptionsBuilder().WithTopicFilter(topic).Build(); TopicGenerator.Generate(NumPublishers, NumTopicsPerPublisher, out var topicsByPublisher, out var singleWildcardTopicsByPublisher, out var multiWildcardTopicsByPublisher);
_mqttClient.SubscribeAsync(subscribeOptions).GetAwaiter().GetResult(); _topics = topicsByPublisher.Values.First();
var serverOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
var factory = new MqttFactory();
_mqttServer = factory.CreateMqttServer(serverOptions);
_mqttClient = factory.CreateMqttClient();
_mqttServer.StartAsync().GetAwaiter().GetResult();
var clientOptions = new MqttClientOptionsBuilder()
.WithTcpServer("localhost").Build();
_mqttClient.ConnectAsync(clientOptions).GetAwaiter().GetResult();
foreach (var topic in _topics)
{
var subscribeOptions = new MqttClientSubscribeOptionsBuilder()
.WithTopicFilter(topic)
.Build();
_mqttClient.SubscribeAsync(subscribeOptions).GetAwaiter().GetResult();
}
}
[Benchmark]
public void Unsubscribe_10000_Topics()
{
foreach (var topic in _topics)
{
var unsubscribeOptions = new MqttClientUnsubscribeOptionsBuilder()
.WithTopicFilter(topic)
.Build();
_mqttClient.UnsubscribeAsync(unsubscribeOptions).GetAwaiter().GetResult();
}
} }
} }
}
[Benchmark]
public void Unsubscribe_10000_Topics()
{
foreach (var topic in _topics)
{
var unsubscribeOptions = new MqttClientUnsubscribeOptionsBuilder().WithTopicFilter(topic).Build();
_mqttClient.UnsubscribeAsync(unsubscribeOptions).GetAwaiter().GetResult();
}
}
}
@@ -0,0 +1,24 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
namespace MQTTnet.Extensions.ManagedClient
{
public sealed class ApplicationMessageProcessedEventArgs : EventArgs
{
public ApplicationMessageProcessedEventArgs(ManagedMqttApplicationMessage applicationMessage, Exception exception)
{
ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
Exception = exception;
}
public ManagedMqttApplicationMessage ApplicationMessage { get; }
/// <summary>
/// Then this is _null_ the message was processed successfully without any error.
/// </summary>
public Exception Exception { get; }
}
}
@@ -0,0 +1,18 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
namespace MQTTnet.Extensions.ManagedClient
{
public sealed class ApplicationMessageSkippedEventArgs : EventArgs
{
public ApplicationMessageSkippedEventArgs(ManagedMqttApplicationMessage applicationMessage)
{
ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
}
public ManagedMqttApplicationMessage ApplicationMessage { get; }
}
}
@@ -0,0 +1,21 @@
using System;
using MQTTnet.Client;
namespace MQTTnet.Extensions.ManagedClient
{
public sealed class ConnectingFailedEventArgs : EventArgs
{
public ConnectingFailedEventArgs(MqttClientConnectResult connectResult, Exception exception)
{
ConnectResult = connectResult;
Exception = exception;
}
/// <summary>
/// This is null when the connection was failing and the server was not reachable.
/// </summary>
public MqttClientConnectResult ConnectResult { get; }
public Exception Exception { get; }
}
}
@@ -0,0 +1,54 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Client;
using MQTTnet.Packets;
namespace MQTTnet.Extensions.ManagedClient
{
public interface IManagedMqttClient : IDisposable
{
event Func<ApplicationMessageProcessedEventArgs, Task> ApplicationMessageProcessedAsync;
event Func<MqttApplicationMessageReceivedEventArgs, Task> ApplicationMessageReceivedAsync;
event Func<ApplicationMessageSkippedEventArgs, Task> ApplicationMessageSkippedAsync;
event Func<MqttClientConnectedEventArgs, Task> ConnectedAsync;
event Func<ConnectingFailedEventArgs, Task> ConnectingFailedAsync;
event Func<EventArgs, Task> ConnectionStateChangedAsync;
event Func<MqttClientDisconnectedEventArgs, Task> DisconnectedAsync;
event Func<ManagedProcessFailedEventArgs, Task> SynchronizingSubscriptionsFailedAsync;
event Func<SubscriptionsChangedEventArgs, Task> SubscriptionsChangedAsync;
IMqttClient InternalClient { get; }
bool IsConnected { get; }
bool IsStarted { get; }
ManagedMqttClientOptions Options { get; }
int PendingApplicationMessagesCount { get; }
Task EnqueueAsync(MqttApplicationMessage applicationMessage);
Task EnqueueAsync(ManagedMqttApplicationMessage applicationMessage);
Task PingAsync(CancellationToken cancellationToken = default);
Task StartAsync(ManagedMqttClientOptions options);
Task StopAsync(bool cleanDisconnect = true);
Task SubscribeAsync(IEnumerable<MqttTopicFilter> topicFilters);
Task UnsubscribeAsync(IEnumerable<string> topics);
}
}
@@ -0,0 +1,16 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using System.Threading.Tasks;
namespace MQTTnet.Extensions.ManagedClient
{
public interface IManagedMqttClientStorage
{
Task SaveQueuedMessagesAsync(IList<ManagedMqttApplicationMessage> messages);
Task<IList<ManagedMqttApplicationMessage>> LoadQueuedMessagesAsync();
}
}
@@ -0,0 +1,20 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
namespace MQTTnet.Extensions.ManagedClient
{
public sealed class InterceptingPublishMessageEventArgs : EventArgs
{
public InterceptingPublishMessageEventArgs(ManagedMqttApplicationMessage applicationMessage)
{
ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
}
public ManagedMqttApplicationMessage ApplicationMessage { get; }
public bool AcceptPublish { get; set; } = true;
}
}
@@ -1,18 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0</TargetFrameworks>
<AssemblyName>MQTTnet.Server</AssemblyName> <TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461;net48</TargetFrameworks>
<RootNamespace>MQTTnet.Server</RootNamespace> <TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
<AssemblyName>MQTTnet.Extensions.ManagedClient</AssemblyName>
<RootNamespace>MQTTnet.Extensions.ManagedClient</RootNamespace>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild> <GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<Company>The contributors of MQTTnet</Company> <Company>The contributors of MQTTnet</Company>
<Product>MQTTnet</Product> <Product>MQTTnet</Product>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <Description>This is an extension library which provides a managed MQTT client with additional features using MQTTnet.</Description>
<Description>This is the server implementation of MQTTnet.</Description>
<Authors>The contributors of MQTTnet</Authors> <Authors>The contributors of MQTTnet</Authors>
<PackageId>MQTTnet.Server</PackageId> <PackageId>MQTTnet.Extensions.ManagedClient</PackageId>
<DelaySign>false</DelaySign>
<SignAssembly>false</SignAssembly> <SignAssembly>false</SignAssembly>
<DelaySign>false</DelaySign>
<PublishRepositoryUrl>true</PublishRepositoryUrl> <PublishRepositoryUrl>true</PublishRepositoryUrl>
<IncludeSymbols>true</IncludeSymbols> <IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat> <SymbolPackageFormat>snupkg</SymbolPackageFormat>
@@ -28,20 +30,24 @@
<EmbedUntrackedSources>true</EmbedUntrackedSources> <EmbedUntrackedSources>true</EmbedUntrackedSources>
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild> <ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
<PackageLicenseExpression>MIT</PackageLicenseExpression> <PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageReleaseNotes>For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).</PackageReleaseNotes>
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers> <DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
<NoWarn>1591;NETSDK1138;NU1803;NU1901;NU1902</NoWarn> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors> <NoWarn>1591;NETSDK1138</NoWarn>
<NuGetAuditMode>all</NuGetAuditMode>
<NuGetAudit>true</NuGetAudit>
<NuGetAuditLevel>low</NuGetAuditLevel>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>disable</Nullable>
<AnalysisLevel>latest-Recommended</AnalysisLevel>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0'">
<ProjectReference Include="..\MQTTnet\MQTTnet.csproj"/> <CopyLocalLockFileAssemblies>false</CopyLocalLockFileAssemblies>
</ItemGroup> <NugetTargetMoniker>UAP,Version=v10.0</NugetTargetMoniker>
<TargetPlatformIdentifier>UAP</TargetPlatformIdentifier>
<TargetPlatformVersion>10.0.18362.0</TargetPlatformVersion>
<TargetPlatformMinVersion>10.0.10240.0</TargetPlatformMinVersion>
<TargetFrameworkIdentifier>.NETCore</TargetFrameworkIdentifier>
<TargetFrameworkVersion>v5.0</TargetFrameworkVersion>
<DefineConstants>$(DefineConstants);WINDOWS_UWP</DefineConstants>
<DefaultLanguage>en</DefaultLanguage>
<LanguageTargets>$(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets</LanguageTargets>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<None Include="..\..\Images\nuget.png"> <None Include="..\..\Images\nuget.png">
@@ -49,9 +55,13 @@
<PackagePath>\</PackagePath> <PackagePath>\</PackagePath>
</None> </None>
</ItemGroup> </ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All"/> <ProjectReference Include="..\MQTTnet\MQTTnet.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>
@@ -0,0 +1,15 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
namespace MQTTnet.Extensions.ManagedClient
{
public class ManagedMqttApplicationMessage
{
public Guid Id { get; set; } = Guid.NewGuid();
public MqttApplicationMessage ApplicationMessage { get; set; }
}
}
@@ -0,0 +1,51 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
namespace MQTTnet.Extensions.ManagedClient
{
public class ManagedMqttApplicationMessageBuilder
{
private Guid _id = Guid.NewGuid();
private MqttApplicationMessage _applicationMessage;
public ManagedMqttApplicationMessageBuilder WithId(Guid id)
{
_id = id;
return this;
}
public ManagedMqttApplicationMessageBuilder WithApplicationMessage(MqttApplicationMessage applicationMessage)
{
_applicationMessage = applicationMessage;
return this;
}
public ManagedMqttApplicationMessageBuilder WithApplicationMessage(Action<MqttApplicationMessageBuilder> builder)
{
if (builder == null) throw new ArgumentNullException(nameof(builder));
var internalBuilder = new MqttApplicationMessageBuilder();
builder(internalBuilder);
_applicationMessage = internalBuilder.Build();
return this;
}
public ManagedMqttApplicationMessage Build()
{
if (_applicationMessage == null)
{
throw new InvalidOperationException("The ApplicationMessage cannot be null.");
}
return new ManagedMqttApplicationMessage
{
Id = _id,
ApplicationMessage = _applicationMessage
};
}
}
}
@@ -0,0 +1,827 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Client;
using MQTTnet.Diagnostics;
using MQTTnet.Exceptions;
using MQTTnet.Internal;
using MQTTnet.Packets;
using MQTTnet.Protocol;
using MQTTnet.Server;
namespace MQTTnet.Extensions.ManagedClient
{
public sealed class ManagedMqttClient : Disposable, IManagedMqttClient
{
readonly MqttNetSourceLogger _logger;
readonly AsyncEvent<InterceptingPublishMessageEventArgs> _interceptingPublishMessageEvent = new AsyncEvent<InterceptingPublishMessageEventArgs>();
readonly AsyncEvent<ApplicationMessageProcessedEventArgs> _applicationMessageProcessedEvent = new AsyncEvent<ApplicationMessageProcessedEventArgs>();
readonly AsyncEvent<ApplicationMessageSkippedEventArgs> _applicationMessageSkippedEvent = new AsyncEvent<ApplicationMessageSkippedEventArgs>();
readonly AsyncEvent<ConnectingFailedEventArgs> _connectingFailedEvent = new AsyncEvent<ConnectingFailedEventArgs>();
readonly AsyncEvent<EventArgs> _connectionStateChangedEvent = new AsyncEvent<EventArgs>();
readonly AsyncEvent<ManagedProcessFailedEventArgs> _synchronizingSubscriptionsFailedEvent = new AsyncEvent<ManagedProcessFailedEventArgs>();
readonly AsyncEvent<SubscriptionsChangedEventArgs> _subscriptionsChangedEvent = new AsyncEvent<SubscriptionsChangedEventArgs>();
readonly BlockingQueue<ManagedMqttApplicationMessage> _messageQueue = new BlockingQueue<ManagedMqttApplicationMessage>();
readonly AsyncLock _messageQueueLock = new AsyncLock();
/// <summary>
/// The subscriptions are managed in 2 separate buckets:
/// <see
/// cref="_subscriptions" />
/// and
/// <see
/// cref="_unsubscriptions" />
/// are processed during normal operation
/// and are moved to the
/// <see
/// cref="_reconnectSubscriptions" />
/// when they get processed. They can be accessed by
/// any thread and are therefore mutex'ed.
/// <see
/// cref="_reconnectSubscriptions" />
/// get sent to the broker
/// at reconnect and are solely owned by
/// <see
/// cref="MaintainConnectionAsync" />
/// .
/// </summary>
readonly Dictionary<string, MqttTopicFilter> _reconnectSubscriptions = new Dictionary<string, MqttTopicFilter>();
readonly Dictionary<string, MqttTopicFilter> _subscriptions = new Dictionary<string, MqttTopicFilter>();
readonly SemaphoreSlim _subscriptionsQueuedSignal = new SemaphoreSlim(0);
readonly HashSet<string> _unsubscriptions = new HashSet<string>();
CancellationTokenSource _connectionCancellationToken;
Task _maintainConnectionTask;
CancellationTokenSource _publishingCancellationToken;
ManagedMqttClientStorageManager _storageManager;
bool _isCleanDisconnect;
public ManagedMqttClient(IMqttClient mqttClient, IMqttNetLogger logger)
{
InternalClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
if (logger == null)
{
throw new ArgumentNullException(nameof(logger));
}
_logger = logger.WithSource(nameof(ManagedMqttClient));
}
public event Func<ApplicationMessageSkippedEventArgs, Task> ApplicationMessageSkippedAsync
{
add => _applicationMessageSkippedEvent.AddHandler(value);
remove => _applicationMessageSkippedEvent.RemoveHandler(value);
}
public event Func<ApplicationMessageProcessedEventArgs, Task> ApplicationMessageProcessedAsync
{
add => _applicationMessageProcessedEvent.AddHandler(value);
remove => _applicationMessageProcessedEvent.RemoveHandler(value);
}
public event Func<InterceptingPublishMessageEventArgs, Task> InterceptPublishMessageAsync
{
add => _interceptingPublishMessageEvent.AddHandler(value);
remove => _interceptingPublishMessageEvent.RemoveHandler(value);
}
public event Func<MqttApplicationMessageReceivedEventArgs, Task> ApplicationMessageReceivedAsync
{
add => InternalClient.ApplicationMessageReceivedAsync += value;
remove => InternalClient.ApplicationMessageReceivedAsync -= value;
}
public event Func<MqttClientConnectedEventArgs, Task> ConnectedAsync
{
add => InternalClient.ConnectedAsync += value;
remove => InternalClient.ConnectedAsync -= value;
}
public event Func<ConnectingFailedEventArgs, Task> ConnectingFailedAsync
{
add => _connectingFailedEvent.AddHandler(value);
remove => _connectingFailedEvent.RemoveHandler(value);
}
public event Func<EventArgs, Task> ConnectionStateChangedAsync
{
add => _connectionStateChangedEvent.AddHandler(value);
remove => _connectionStateChangedEvent.RemoveHandler(value);
}
public event Func<MqttClientDisconnectedEventArgs, Task> DisconnectedAsync
{
add => InternalClient.DisconnectedAsync += value;
remove => InternalClient.DisconnectedAsync -= value;
}
public event Func<ManagedProcessFailedEventArgs, Task> SynchronizingSubscriptionsFailedAsync
{
add => _synchronizingSubscriptionsFailedEvent.AddHandler(value);
remove => _synchronizingSubscriptionsFailedEvent.RemoveHandler(value);
}
public event Func<SubscriptionsChangedEventArgs, Task> SubscriptionsChangedAsync
{
add => _subscriptionsChangedEvent.AddHandler(value);
remove => _subscriptionsChangedEvent.RemoveHandler(value);
}
public IMqttClient InternalClient { get; }
public bool IsConnected => InternalClient.IsConnected;
public bool IsStarted => _connectionCancellationToken != null;
public ManagedMqttClientOptions Options { get; private set; }
public int PendingApplicationMessagesCount => _messageQueue.Count;
public async Task EnqueueAsync(MqttApplicationMessage applicationMessage)
{
ThrowIfDisposed();
if (applicationMessage == null)
{
throw new ArgumentNullException(nameof(applicationMessage));
}
var managedMqttApplicationMessage = new ManagedMqttApplicationMessageBuilder().WithApplicationMessage(applicationMessage);
await EnqueueAsync(managedMqttApplicationMessage.Build()).ConfigureAwait(false);
}
public async Task EnqueueAsync(ManagedMqttApplicationMessage applicationMessage)
{
ThrowIfDisposed();
if (applicationMessage == null)
{
throw new ArgumentNullException(nameof(applicationMessage));
}
if (Options == null)
{
throw new InvalidOperationException("call StartAsync before publishing messages");
}
MqttTopicValidator.ThrowIfInvalid(applicationMessage.ApplicationMessage);
ManagedMqttApplicationMessage removedMessage = null;
ApplicationMessageSkippedEventArgs applicationMessageSkippedEventArgs = null;
try
{
using (await _messageQueueLock.EnterAsync().ConfigureAwait(false))
{
if (_messageQueue.Count >= Options.MaxPendingMessages)
{
if (Options.PendingMessagesOverflowStrategy == MqttPendingMessagesOverflowStrategy.DropNewMessage)
{
_logger.Verbose("Skipping publish of new application message because internal queue is full.");
applicationMessageSkippedEventArgs = new ApplicationMessageSkippedEventArgs(applicationMessage);
return;
}
if (Options.PendingMessagesOverflowStrategy == MqttPendingMessagesOverflowStrategy.DropOldestQueuedMessage)
{
removedMessage = _messageQueue.RemoveFirst();
_logger.Verbose("Removed oldest application message from internal queue because it is full.");
applicationMessageSkippedEventArgs = new ApplicationMessageSkippedEventArgs(removedMessage);
}
}
_messageQueue.Enqueue(applicationMessage);
if (_storageManager != null)
{
if (removedMessage != null)
{
await _storageManager.RemoveAsync(removedMessage).ConfigureAwait(false);
}
await _storageManager.AddAsync(applicationMessage).ConfigureAwait(false);
}
}
}
finally
{
if (applicationMessageSkippedEventArgs != null && _applicationMessageSkippedEvent.HasHandlers)
{
await _applicationMessageSkippedEvent.InvokeAsync(applicationMessageSkippedEventArgs).ConfigureAwait(false);
}
}
}
public Task PingAsync(CancellationToken cancellationToken = default)
{
return InternalClient.PingAsync(cancellationToken);
}
public async Task StartAsync(ManagedMqttClientOptions options)
{
ThrowIfDisposed();
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
if (options.ClientOptions == null)
{
throw new ArgumentException("The client options are not set.", nameof(options));
}
if (!_maintainConnectionTask?.IsCompleted ?? false)
{
throw new InvalidOperationException("The managed client is already started.");
}
Options = options;
if (options.Storage != null)
{
_storageManager = new ManagedMqttClientStorageManager(options.Storage);
var messages = await _storageManager.LoadQueuedMessagesAsync().ConfigureAwait(false);
foreach (var message in messages)
{
_messageQueue.Enqueue(message);
}
}
var cancellationTokenSource = new CancellationTokenSource();
var cancellationToken = cancellationTokenSource.Token;
_connectionCancellationToken = cancellationTokenSource;
_maintainConnectionTask = Task.Run(() => MaintainConnectionAsync(cancellationToken), cancellationToken);
_maintainConnectionTask.RunInBackground(_logger);
_logger.Info("Started");
}
public async Task StopAsync(bool cleanDisconnect = true)
{
ThrowIfDisposed();
_isCleanDisconnect = cleanDisconnect;
StopPublishing();
StopMaintainingConnection();
_messageQueue.Clear();
if (_maintainConnectionTask != null)
{
await Task.WhenAny(_maintainConnectionTask);
_maintainConnectionTask = null;
}
}
public Task SubscribeAsync(IEnumerable<MqttTopicFilter> topicFilters)
{
ThrowIfDisposed();
if (topicFilters == null)
{
throw new ArgumentNullException(nameof(topicFilters));
}
foreach (var topicFilter in topicFilters)
{
MqttTopicValidator.ThrowIfInvalidSubscribe(topicFilter.Topic);
}
lock (_subscriptions)
{
foreach (var topicFilter in topicFilters)
{
_subscriptions[topicFilter.Topic] = topicFilter;
_unsubscriptions.Remove(topicFilter.Topic);
}
}
_subscriptionsQueuedSignal.Release();
return CompletedTask.Instance;
}
public Task UnsubscribeAsync(IEnumerable<string> topics)
{
ThrowIfDisposed();
if (topics == null)
{
throw new ArgumentNullException(nameof(topics));
}
lock (_subscriptions)
{
foreach (var topic in topics)
{
_subscriptions.Remove(topic);
_unsubscriptions.Add(topic);
}
}
_subscriptionsQueuedSignal.Release();
return CompletedTask.Instance;
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
StopPublishing();
StopMaintainingConnection();
if (_maintainConnectionTask != null)
{
_maintainConnectionTask.GetAwaiter().GetResult();
_maintainConnectionTask = null;
}
_messageQueue.Dispose();
_messageQueueLock.Dispose();
InternalClient.Dispose();
_subscriptionsQueuedSignal.Dispose();
}
base.Dispose(disposing);
}
static TimeSpan GetRemainingTime(DateTime endTime)
{
var remainingTime = endTime - DateTime.UtcNow;
return remainingTime < TimeSpan.Zero ? TimeSpan.Zero : remainingTime;
}
CancellationTokenSource NewTimeoutToken(CancellationToken linkedToken)
{
var newTimeoutToken = CancellationTokenSource.CreateLinkedTokenSource(linkedToken);
newTimeoutToken.CancelAfter(Options.ClientOptions.Timeout);
return newTimeoutToken;
}
async Task HandleSubscriptionExceptionAsync(Exception exception, List<MqttTopicFilter> addedSubscriptions, List<string> removedSubscriptions)
{
_logger.Warning(exception, "Synchronizing subscriptions failed.");
if (_synchronizingSubscriptionsFailedEvent.HasHandlers)
{
await _synchronizingSubscriptionsFailedEvent.InvokeAsync(new ManagedProcessFailedEventArgs(exception, addedSubscriptions, removedSubscriptions)).ConfigureAwait(false);
}
}
async Task HandleSubscriptionsResultAsync(SendSubscribeUnsubscribeResult subscribeUnsubscribeResult)
{
if (_subscriptionsChangedEvent.HasHandlers)
{
await _subscriptionsChangedEvent.InvokeAsync(new SubscriptionsChangedEventArgs(subscribeUnsubscribeResult.SubscribeResults, subscribeUnsubscribeResult.UnsubscribeResults)).ConfigureAwait(false);
}
}
async Task MaintainConnectionAsync(CancellationToken cancellationToken)
{
try
{
while (!cancellationToken.IsCancellationRequested)
{
await TryMaintainConnectionAsync(cancellationToken).ConfigureAwait(false);
}
}
catch (OperationCanceledException)
{
}
catch (Exception exception)
{
_logger.Error(exception, "Error exception while maintaining connection.");
}
finally
{
if (!IsDisposed)
{
try
{
if (_isCleanDisconnect)
{
using (var disconnectTimeout = NewTimeoutToken(CancellationToken.None))
{
await InternalClient.DisconnectAsync(new MqttClientDisconnectOptions(), disconnectTimeout.Token).ConfigureAwait(false);
}
}
}
catch (OperationCanceledException)
{
_logger.Warning("Timeout while sending DISCONNECT packet.");
}
catch (Exception exception)
{
_logger.Error(exception, "Error while disconnecting.");
}
_logger.Info("Stopped");
}
_reconnectSubscriptions.Clear();
lock (_subscriptions)
{
_subscriptions.Clear();
_unsubscriptions.Clear();
}
}
}
async Task PublishQueuedMessagesAsync(CancellationToken cancellationToken)
{
try
{
while (!cancellationToken.IsCancellationRequested && InternalClient.IsConnected)
{
// Peek at the message without dequeueing in order to prevent the
// possibility of the queue growing beyond the configured cap.
// Previously, messages could be re-enqueued if there was an
// exception, and this re-enqueueing did not honor the cap.
// Furthermore, because re-enqueueing would shuffle the order
// of the messages, the DropOldestQueuedMessage strategy would
// be unable to know which message is actually the oldest and would
// instead drop the first item in the queue.
var message = _messageQueue.PeekAndWait(cancellationToken);
if (message == null)
{
continue;
}
cancellationToken.ThrowIfCancellationRequested();
await TryPublishQueuedMessageAsync(message, cancellationToken).ConfigureAwait(false);
}
}
catch (OperationCanceledException)
{
}
catch (Exception exception)
{
_logger.Error(exception, "Error while publishing queued application messages.");
}
finally
{
_logger.Verbose("Stopped publishing messages.");
}
}
async Task PublishReconnectSubscriptionsAsync(CancellationToken cancellationToken)
{
_logger.Info("Publishing subscriptions at reconnect");
List<MqttTopicFilter> topicFilters = null;
try
{
if (_reconnectSubscriptions.Any())
{
topicFilters = new List<MqttTopicFilter>();
SendSubscribeUnsubscribeResult subscribeUnsubscribeResult;
foreach (var sub in _reconnectSubscriptions)
{
topicFilters.Add(sub.Value);
if (topicFilters.Count == Options.MaxTopicFiltersInSubscribeUnsubscribePackets)
{
subscribeUnsubscribeResult = await SendSubscribeUnsubscribe(topicFilters, null, cancellationToken).ConfigureAwait(false);
topicFilters.Clear();
await HandleSubscriptionsResultAsync(subscribeUnsubscribeResult).ConfigureAwait(false);
}
}
subscribeUnsubscribeResult = await SendSubscribeUnsubscribe(topicFilters, null, cancellationToken).ConfigureAwait(false);
await HandleSubscriptionsResultAsync(subscribeUnsubscribeResult).ConfigureAwait(false);
}
}
catch (Exception exception)
{
await HandleSubscriptionExceptionAsync(exception, topicFilters, null).ConfigureAwait(false);
}
}
async Task PublishSubscriptionsAsync(TimeSpan timeout, CancellationToken cancellationToken)
{
var endTime = DateTime.UtcNow + timeout;
while (await _subscriptionsQueuedSignal.WaitAsync(GetRemainingTime(endTime), cancellationToken).ConfigureAwait(false))
{
List<MqttTopicFilter> subscriptions;
SendSubscribeUnsubscribeResult subscribeUnsubscribeResult;
HashSet<string> unsubscriptions;
lock (_subscriptions)
{
subscriptions = _subscriptions.Values.ToList();
_subscriptions.Clear();
unsubscriptions = new HashSet<string>(_unsubscriptions);
_unsubscriptions.Clear();
}
if (!subscriptions.Any() && !unsubscriptions.Any())
{
continue;
}
_logger.Verbose("Publishing {0} added and {1} removed subscriptions", subscriptions.Count, unsubscriptions.Count);
foreach (var unsubscription in unsubscriptions)
{
_reconnectSubscriptions.Remove(unsubscription);
}
foreach (var subscription in subscriptions)
{
_reconnectSubscriptions[subscription.Topic] = subscription;
}
var addedTopicFilters = new List<MqttTopicFilter>();
foreach (var subscription in subscriptions)
{
addedTopicFilters.Add(subscription);
if (addedTopicFilters.Count == Options.MaxTopicFiltersInSubscribeUnsubscribePackets)
{
subscribeUnsubscribeResult = await SendSubscribeUnsubscribe(addedTopicFilters, null, cancellationToken).ConfigureAwait(false);
addedTopicFilters.Clear();
await HandleSubscriptionsResultAsync(subscribeUnsubscribeResult).ConfigureAwait(false);
}
}
subscribeUnsubscribeResult = await SendSubscribeUnsubscribe(addedTopicFilters, null, cancellationToken).ConfigureAwait(false);
await HandleSubscriptionsResultAsync(subscribeUnsubscribeResult).ConfigureAwait(false);
var removedTopicFilters = new List<string>();
foreach (var unSub in unsubscriptions)
{
removedTopicFilters.Add(unSub);
if (removedTopicFilters.Count == Options.MaxTopicFiltersInSubscribeUnsubscribePackets)
{
subscribeUnsubscribeResult = await SendSubscribeUnsubscribe(null, removedTopicFilters, cancellationToken).ConfigureAwait(false);
removedTopicFilters.Clear();
await HandleSubscriptionsResultAsync(subscribeUnsubscribeResult).ConfigureAwait(false);
}
}
subscribeUnsubscribeResult = await SendSubscribeUnsubscribe(null, removedTopicFilters, cancellationToken).ConfigureAwait(false);
await HandleSubscriptionsResultAsync(subscribeUnsubscribeResult).ConfigureAwait(false);
}
}
async Task<ReconnectionResult> ReconnectIfRequiredAsync(CancellationToken cancellationToken)
{
if (InternalClient.IsConnected)
{
return ReconnectionResult.StillConnected;
}
MqttClientConnectResult connectResult = null;
try
{
using (var connectTimeout = NewTimeoutToken(cancellationToken))
{
connectResult = await InternalClient.ConnectAsync(Options.ClientOptions, connectTimeout.Token).ConfigureAwait(false);
}
if (connectResult.ResultCode != MqttClientConnectResultCode.Success)
{
throw new MqttCommunicationException($"Client connected but server denied connection with reason '{connectResult.ResultCode}'.");
}
return connectResult.IsSessionPresent ? ReconnectionResult.Recovered : ReconnectionResult.Reconnected;
}
catch (Exception exception)
{
await _connectingFailedEvent.InvokeAsync(new ConnectingFailedEventArgs(connectResult, exception));
return ReconnectionResult.NotConnected;
}
}
async Task<SendSubscribeUnsubscribeResult> SendSubscribeUnsubscribe(List<MqttTopicFilter> addedSubscriptions, List<string> removedSubscriptions, CancellationToken cancellationToken)
{
var subscribeResults = new List<MqttClientSubscribeResult>();
var unsubscribeResults = new List<MqttClientUnsubscribeResult>();
try
{
if (removedSubscriptions != null && removedSubscriptions.Any())
{
var unsubscribeOptionsBuilder = new MqttClientUnsubscribeOptionsBuilder();
foreach (var removedSubscription in removedSubscriptions)
{
unsubscribeOptionsBuilder.WithTopicFilter(removedSubscription);
}
using (var unsubscribeTimeout = NewTimeoutToken(cancellationToken))
{
var unsubscribeResult = await InternalClient.UnsubscribeAsync(unsubscribeOptionsBuilder.Build(), unsubscribeTimeout.Token).ConfigureAwait(false);
unsubscribeResults.Add(unsubscribeResult);
}
//clear because these worked, maybe the subscribe below will fail, only report those
removedSubscriptions.Clear();
}
if (addedSubscriptions != null && addedSubscriptions.Any())
{
var subscribeOptionsBuilder = new MqttClientSubscribeOptionsBuilder();
foreach (var addedSubscription in addedSubscriptions)
{
subscribeOptionsBuilder.WithTopicFilter(addedSubscription);
}
using (var subscribeTimeout = NewTimeoutToken(cancellationToken))
{
var subscribeResult = await InternalClient.SubscribeAsync(subscribeOptionsBuilder.Build(), subscribeTimeout.Token).ConfigureAwait(false);
subscribeResults.Add(subscribeResult);
}
}
}
catch (Exception exception)
{
await HandleSubscriptionExceptionAsync(exception, addedSubscriptions, removedSubscriptions).ConfigureAwait(false);
}
return new SendSubscribeUnsubscribeResult(subscribeResults, unsubscribeResults);
}
void StartPublishing()
{
StopPublishing();
var cancellationTokenSource = new CancellationTokenSource();
var cancellationToken = cancellationTokenSource.Token;
_publishingCancellationToken = cancellationTokenSource;
Task.Run(() => PublishQueuedMessagesAsync(cancellationToken), cancellationToken).RunInBackground(_logger);
}
void StopMaintainingConnection()
{
try
{
_connectionCancellationToken?.Cancel(false);
}
finally
{
_connectionCancellationToken?.Dispose();
_connectionCancellationToken = null;
}
}
void StopPublishing()
{
try
{
_publishingCancellationToken?.Cancel(false);
}
finally
{
_publishingCancellationToken?.Dispose();
_publishingCancellationToken = null;
}
}
async Task TryMaintainConnectionAsync(CancellationToken cancellationToken)
{
try
{
var oldConnectionState = InternalClient.IsConnected;
var connectionState = await ReconnectIfRequiredAsync(cancellationToken).ConfigureAwait(false);
if (connectionState == ReconnectionResult.NotConnected)
{
StopPublishing();
await Task.Delay(Options.AutoReconnectDelay, cancellationToken).ConfigureAwait(false);
}
else if (connectionState == ReconnectionResult.Reconnected)
{
await PublishReconnectSubscriptionsAsync(cancellationToken).ConfigureAwait(false);
StartPublishing();
}
else if (connectionState == ReconnectionResult.Recovered)
{
StartPublishing();
}
else if (connectionState == ReconnectionResult.StillConnected)
{
await PublishSubscriptionsAsync(Options.ConnectionCheckInterval, cancellationToken).ConfigureAwait(false);
}
if (oldConnectionState != InternalClient.IsConnected)
{
await _connectionStateChangedEvent.InvokeAsync(EventArgs.Empty).ConfigureAwait(false);
}
}
catch (OperationCanceledException)
{
}
catch (MqttCommunicationException exception)
{
_logger.Warning(exception, "Communication error while maintaining connection.");
}
catch (Exception exception)
{
_logger.Error(exception, "Error exception while maintaining connection.");
}
}
async Task TryPublishQueuedMessageAsync(ManagedMqttApplicationMessage message, CancellationToken cancellationToken)
{
Exception transmitException = null;
bool acceptPublish = true;
try
{
if (_interceptingPublishMessageEvent.HasHandlers)
{
var interceptEventArgs = new InterceptingPublishMessageEventArgs(message);
await _interceptingPublishMessageEvent.InvokeAsync(interceptEventArgs).ConfigureAwait(false);
acceptPublish = interceptEventArgs.AcceptPublish;
}
if (acceptPublish)
{
using (var publishTimeout = NewTimeoutToken(cancellationToken))
{
await InternalClient.PublishAsync(message.ApplicationMessage, publishTimeout.Token).ConfigureAwait(false);
}
}
using (await _messageQueueLock.EnterAsync().ConfigureAwait(false)) //lock to avoid conflict with this.PublishAsync
{
// While publishing this message, this.PublishAsync could have booted this
// message off the queue to make room for another (when using a cap
// with the DropOldestQueuedMessage strategy). If the first item
// in the queue is equal to this message, then it's safe to remove
// it from the queue. If not, that means this.PublishAsync has already
// removed it, in which case we don't want to do anything.
_messageQueue.RemoveFirst(i => i.Id.Equals(message.Id));
if (_storageManager != null)
{
await _storageManager.RemoveAsync(message).ConfigureAwait(false);
}
}
}
catch (MqttCommunicationException exception)
{
transmitException = exception;
_logger.Warning(exception, "Publishing application message ({0}) failed.", message.Id);
if (message.ApplicationMessage.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce)
{
//If QoS 0, we don't want this message to stay on the queue.
//If QoS 1 or 2, it's possible that, when using a cap, this message
//has been booted off the queue by this.PublishAsync, in which case this
//thread will not continue to try to publish it. While this does
//contradict the expected behavior of QoS 1 and 2, that's also true
//for the usage of a message queue cap, so it's still consistent
//with prior behavior in that way.
using (await _messageQueueLock.EnterAsync().ConfigureAwait(false)) //lock to avoid conflict with this.PublishAsync
{
_messageQueue.RemoveFirst(i => i.Id.Equals(message.Id));
if (_storageManager != null)
{
await _storageManager.RemoveAsync(message).ConfigureAwait(false);
}
}
}
}
catch (Exception exception)
{
transmitException = exception;
_logger.Error(exception, "Error while publishing application message ({0}).", message.Id);
}
finally
{
if (_applicationMessageProcessedEvent.HasHandlers)
{
var eventArgs = new ApplicationMessageProcessedEventArgs(message, transmitException);
await _applicationMessageProcessedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
}
}
}
}
}
@@ -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;
using System.Collections.Generic;
using System.Threading.Tasks;
using MQTTnet.Packets;
using MQTTnet.Protocol;
namespace MQTTnet.Extensions.ManagedClient
{
public static class ManagedMqttClientExtensions
{
public static Task EnqueueAsync(
this IManagedMqttClient managedMqttClient,
string topic,
string payload = null,
MqttQualityOfServiceLevel qualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce,
bool retain = false)
{
if (managedMqttClient == null)
{
throw new ArgumentNullException(nameof(managedMqttClient));
}
if (topic == null)
{
throw new ArgumentNullException(nameof(topic));
}
var applicationMessage = new MqttApplicationMessageBuilder().WithTopic(topic)
.WithPayload(payload)
.WithRetainFlag(retain)
.WithQualityOfServiceLevel(qualityOfServiceLevel)
.Build();
return managedMqttClient.EnqueueAsync(applicationMessage);
}
public static Task SubscribeAsync(
this IManagedMqttClient managedMqttClient,
string topic,
MqttQualityOfServiceLevel qualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce)
{
if (managedMqttClient == null)
{
throw new ArgumentNullException(nameof(managedMqttClient));
}
if (topic == null)
{
throw new ArgumentNullException(nameof(topic));
}
return managedMqttClient.SubscribeAsync(
new List<MqttTopicFilter>
{
new MqttTopicFilterBuilder().WithTopic(topic).WithQualityOfServiceLevel(qualityOfServiceLevel).Build()
});
}
public static Task UnsubscribeAsync(this IManagedMqttClient managedMqttClient, string topic)
{
if (managedMqttClient == null)
{
throw new ArgumentNullException(nameof(managedMqttClient));
}
if (topic == null)
{
throw new ArgumentNullException(nameof(topic));
}
return managedMqttClient.UnsubscribeAsync(new List<string> { topic });
}
}
}
@@ -0,0 +1,31 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using MQTTnet.Client;
using MQTTnet.Server;
namespace MQTTnet.Extensions.ManagedClient
{
public sealed class ManagedMqttClientOptions
{
public MqttClientOptions ClientOptions { get; set; }
public TimeSpan AutoReconnectDelay { get; set; } = TimeSpan.FromSeconds(5);
public TimeSpan ConnectionCheckInterval { get; set; } = TimeSpan.FromSeconds(1);
public IManagedMqttClientStorage Storage { get; set; }
public int MaxPendingMessages { get; set; } = int.MaxValue;
public MqttPendingMessagesOverflowStrategy PendingMessagesOverflowStrategy { get; set; } = MqttPendingMessagesOverflowStrategy.DropNewMessage;
/// <summary>
/// Defines the maximum amount of topic filters which will be sent in a SUBSCRIBE/UNSUBSCRIBE packet.
/// Amazon Web Services (AWS) limits this number to 8. The default is int.MaxValue.
/// </summary>
public int MaxTopicFiltersInSubscribeUnsubscribePackets { get; set; } = int.MaxValue;
}
}
@@ -0,0 +1,97 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using MQTTnet.Client;
using MQTTnet.Server;
namespace MQTTnet.Extensions.ManagedClient
{
public class ManagedMqttClientOptionsBuilder
{
readonly ManagedMqttClientOptions _options = new ManagedMqttClientOptions();
MqttClientOptionsBuilder _clientOptionsBuilder;
public ManagedMqttClientOptionsBuilder WithMaxPendingMessages(int value)
{
_options.MaxPendingMessages = value;
return this;
}
public ManagedMqttClientOptionsBuilder WithPendingMessagesOverflowStrategy(MqttPendingMessagesOverflowStrategy value)
{
_options.PendingMessagesOverflowStrategy = value;
return this;
}
public ManagedMqttClientOptionsBuilder WithAutoReconnectDelay(TimeSpan value)
{
_options.AutoReconnectDelay = value;
return this;
}
public ManagedMqttClientOptionsBuilder WithStorage(IManagedMqttClientStorage value)
{
_options.Storage = value;
return this;
}
public ManagedMqttClientOptionsBuilder WithClientOptions(MqttClientOptions value)
{
if (_clientOptionsBuilder != null)
{
throw new InvalidOperationException("Cannot use client options builder and client options at the same time.");
}
_options.ClientOptions = value ?? throw new ArgumentNullException(nameof(value));
return this;
}
public ManagedMqttClientOptionsBuilder WithClientOptions(MqttClientOptionsBuilder builder)
{
if (_options.ClientOptions != null)
{
throw new InvalidOperationException("Cannot use client options builder and client options at the same time.");
}
_clientOptionsBuilder = builder;
return this;
}
public ManagedMqttClientOptionsBuilder WithClientOptions(Action<MqttClientOptionsBuilder> options)
{
if (options == null) throw new ArgumentNullException(nameof(options));
if (_clientOptionsBuilder == null)
{
_clientOptionsBuilder = new MqttClientOptionsBuilder();
}
options(_clientOptionsBuilder);
return this;
}
public ManagedMqttClientOptionsBuilder WithMaxTopicFiltersInSubscribeUnsubscribePackets(int value)
{
_options.MaxTopicFiltersInSubscribeUnsubscribePackets = value;
return this;
}
public ManagedMqttClientOptions Build()
{
if (_clientOptionsBuilder != null)
{
_options.ClientOptions = _clientOptionsBuilder.Build();
}
if (_options.ClientOptions == null)
{
throw new InvalidOperationException("The ClientOptions cannot be null.");
}
return _options;
}
}
}
@@ -0,0 +1,66 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Internal;
namespace MQTTnet.Extensions.ManagedClient
{
public class ManagedMqttClientStorageManager
{
private readonly List<ManagedMqttApplicationMessage> _messages = new List<ManagedMqttApplicationMessage>();
private readonly AsyncLock _messagesLock = new AsyncLock();
private readonly IManagedMqttClientStorage _storage;
public ManagedMqttClientStorageManager(IManagedMqttClientStorage storage)
{
_storage = storage ?? throw new ArgumentNullException(nameof(storage));
}
public async Task<List<ManagedMqttApplicationMessage>> LoadQueuedMessagesAsync()
{
var loadedMessages = await _storage.LoadQueuedMessagesAsync().ConfigureAwait(false);
_messages.AddRange(loadedMessages);
return _messages;
}
public async Task AddAsync(ManagedMqttApplicationMessage applicationMessage)
{
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
using (await _messagesLock.EnterAsync().ConfigureAwait(false))
{
_messages.Add(applicationMessage);
await SaveAsync().ConfigureAwait(false);
}
}
public async Task RemoveAsync(ManagedMqttApplicationMessage applicationMessage)
{
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
using (await _messagesLock.EnterAsync().ConfigureAwait(false))
{
var index = _messages.IndexOf(applicationMessage);
if (index == -1)
{
return;
}
_messages.RemoveAt(index);
await SaveAsync().ConfigureAwait(false);
}
}
private Task SaveAsync()
{
return _storage.SaveQueuedMessagesAsync(_messages);
}
}
}
@@ -0,0 +1,34 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using MQTTnet.Packets;
using System;
using System.Collections.Generic;
using System.Linq;
namespace MQTTnet.Extensions.ManagedClient
{
public class ManagedProcessFailedEventArgs : EventArgs
{
public ManagedProcessFailedEventArgs(Exception exception, List<MqttTopicFilter> addedSubscriptions, List<string> removedSubscriptions)
{
Exception = exception ?? throw new ArgumentNullException(nameof(exception));
if (addedSubscriptions != null)
{
AddedSubscriptions = new List<string>(addedSubscriptions.Select(item => item.Topic));
}
if (removedSubscriptions != null)
{
RemovedSubscriptions = new List<string>(removedSubscriptions);
}
}
public Exception Exception { get; }
public List<string> AddedSubscriptions { get; }
public List<string> RemovedSubscriptions { get; }
}
}
@@ -0,0 +1,48 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using MQTTnet.Client;
using MQTTnet.Diagnostics;
namespace MQTTnet.Extensions.ManagedClient
{
public static class MqttFactoryExtensions
{
public static IManagedMqttClient CreateManagedMqttClient(this MqttFactory factory, IMqttClient mqttClient = null)
{
if (factory == null)
{
throw new ArgumentNullException(nameof(factory));
}
if (mqttClient == null)
{
return new ManagedMqttClient(factory.CreateMqttClient(), factory.DefaultLogger);
}
return new ManagedMqttClient(mqttClient, factory.DefaultLogger);
}
public static IManagedMqttClient CreateManagedMqttClient(this MqttFactory factory, IMqttNetLogger logger)
{
if (factory == null)
{
throw new ArgumentNullException(nameof(factory));
}
if (logger == null)
{
throw new ArgumentNullException(nameof(logger));
}
return new ManagedMqttClient(factory.CreateMqttClient(logger), logger);
}
public static ManagedMqttClientOptionsBuilder CreateManagedMqttClientOptionsBuilder(this MqttFactory _)
{
return new ManagedMqttClientOptionsBuilder();
}
}
}
@@ -2,9 +2,13 @@
// 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; namespace MQTTnet.Extensions.ManagedClient
public class InjectMqttApplicationMessageResult
{ {
public ushort PacketIdentifier { get; init; } public enum ReconnectionResult
} {
StillConnected,
Reconnected,
Recovered,
NotConnected
}
}
@@ -0,0 +1,23 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using MQTTnet.Client;
namespace MQTTnet.Extensions.ManagedClient
{
public sealed class SendSubscribeUnsubscribeResult
{
public SendSubscribeUnsubscribeResult(List<MqttClientSubscribeResult> subscribeResults, List<MqttClientUnsubscribeResult> unsubscribeResults)
{
SubscribeResults = subscribeResults ?? throw new ArgumentNullException(nameof(subscribeResults));
UnsubscribeResults = unsubscribeResults ?? throw new ArgumentNullException(nameof(unsubscribeResults));
}
public List<MqttClientSubscribeResult> SubscribeResults { get; private set; }
public List<MqttClientUnsubscribeResult> UnsubscribeResults { get; private set; }
}
}
@@ -0,0 +1,23 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using MQTTnet.Client;
using System;
using System.Collections.Generic;
namespace MQTTnet.Extensions.ManagedClient
{
public sealed class SubscriptionsChangedEventArgs : EventArgs
{
public SubscriptionsChangedEventArgs(List<MqttClientSubscribeResult> subscribeResult, List<MqttClientUnsubscribeResult> unsubscribeResult)
{
SubscribeResult = subscribeResult ?? throw new ArgumentNullException(nameof(subscribeResult));
UnsubscribeResult = unsubscribeResult ?? throw new ArgumentNullException(nameof(unsubscribeResult));
}
public List<MqttClientSubscribeResult> SubscribeResult { get; }
public List<MqttClientUnsubscribeResult> UnsubscribeResult { get; }
}
}
@@ -1,7 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461;net48</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
<AssemblyName>MQTTnet.Extensions.Rpc</AssemblyName> <AssemblyName>MQTTnet.Extensions.Rpc</AssemblyName>
<RootNamespace>MQTTnet.Extensions.Rpc</RootNamespace> <RootNamespace>MQTTnet.Extensions.Rpc</RootNamespace>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild> <GeneratePackageOnBuild>True</GeneratePackageOnBuild>
@@ -30,12 +33,21 @@
<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> <DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>1591;NETSDK1138;NU1803;NU1901;NU1902</NoWarn> <NoWarn>1591;NETSDK1138</NoWarn>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors> <LangVersion>7.3</LangVersion>
<NuGetAuditMode>all</NuGetAuditMode> </PropertyGroup>
<NuGetAudit>true</NuGetAudit>
<NuGetAuditLevel>low</NuGetAuditLevel> <PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0'">
<AnalysisLevel>latest-Recommended</AnalysisLevel> <CopyLocalLockFileAssemblies>false</CopyLocalLockFileAssemblies>
<NugetTargetMoniker>UAP,Version=v10.0</NugetTargetMoniker>
<TargetPlatformIdentifier>UAP</TargetPlatformIdentifier>
<TargetPlatformVersion>10.0.18362.0</TargetPlatformVersion>
<TargetPlatformMinVersion>10.0.10240.0</TargetPlatformMinVersion>
<TargetFrameworkIdentifier>.NETCore</TargetFrameworkIdentifier>
<TargetFrameworkVersion>v5.0</TargetFrameworkVersion>
<DefineConstants>$(DefineConstants);WINDOWS_UWP</DefineConstants>
<DefaultLanguage>en</DefaultLanguage>
<LanguageTargets>$(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets</LanguageTargets>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@@ -44,13 +56,13 @@
<PackagePath>\</PackagePath> <PackagePath>\</PackagePath>
</None> </None>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All"/> <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\MQTTnet\MQTTnet.csproj"/> <ProjectReference Include="..\MQTTnet\MQTTnet.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>
@@ -1,7 +1,3 @@
<wpf:ResourceDictionary xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" <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">
xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation" <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=options/@EntryIndexedValue">True</s:Boolean>
xml:space="preserve"> <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=options_005Ctopicgeneration/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
<s:Boolean
x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=options/@EntryIndexedValue">True</s:Boolean>
<s:Boolean
x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=options_005Ctopicgeneration/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
@@ -3,14 +3,15 @@
// 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;
using MQTTnet.Client;
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 MqttFactory factory, IMqttClient mqttClient)
{ {
return clientFactory.CreateMqttRpcClient( return factory.CreateMqttRpcClient(
mqttClient, mqttClient,
new MqttRpcClientOptions new MqttRpcClientOptions
{ {
@@ -18,10 +19,17 @@ namespace MQTTnet.Extensions.Rpc
}); });
} }
public static IMqttRpcClient CreateMqttRpcClient(this MqttClientFactory _, IMqttClient mqttClient, MqttRpcClientOptions rpcClientOptions) public static IMqttRpcClient CreateMqttRpcClient(this MqttFactory _, IMqttClient mqttClient, MqttRpcClientOptions rpcClientOptions)
{ {
ArgumentNullException.ThrowIfNull(mqttClient); if (mqttClient == null)
ArgumentNullException.ThrowIfNull(rpcClientOptions); {
throw new ArgumentNullException(nameof(mqttClient));
}
if (rpcClientOptions == null)
{
throw new ArgumentNullException(nameof(rpcClientOptions));
}
return new MqttRpcClient(mqttClient, rpcClientOptions); return new MqttRpcClient(mqttClient, rpcClientOptions);
} }
@@ -3,12 +3,12 @@
// 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;
using System.Buffers;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Client;
using MQTTnet.Exceptions; using MQTTnet.Exceptions;
using MQTTnet.Formatter; using MQTTnet.Formatter;
using MQTTnet.Internal; using MQTTnet.Internal;
@@ -65,7 +65,10 @@ namespace MQTTnet.Extensions.Rpc
public async Task<byte[]> ExecuteAsync(string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary<string, object> parameters = null, CancellationToken cancellationToken = default) public async Task<byte[]> ExecuteAsync(string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary<string, object> parameters = null, CancellationToken cancellationToken = default)
{ {
ArgumentNullException.ThrowIfNull(methodName); if (methodName == null)
{
throw new ArgumentNullException(nameof(methodName));
}
var context = new TopicGenerationContext(_mqttClient, _options, methodName, parameters, qualityOfServiceLevel); var context = new TopicGenerationContext(_mqttClient, _options, methodName, parameters, qualityOfServiceLevel);
var topicNames = _options.TopicGenerationStrategy.CreateRpcTopics(context); var topicNames = _options.TopicGenerationStrategy.CreateRpcTopics(context);
@@ -129,7 +132,7 @@ namespace MQTTnet.Extensions.Rpc
return CompletedTask.Instance; return CompletedTask.Instance;
} }
var payloadBuffer = eventArgs.ApplicationMessage.Payload.ToArray(); var payloadBuffer = eventArgs.ApplicationMessage.PayloadSegment.ToArray();
awaitable.TrySetResult(payloadBuffer); awaitable.TrySetResult(payloadBuffer);
// Set this message to handled to that other code can avoid execution etc. // Set this message to handled to that other code can avoid execution etc.
@@ -10,13 +10,16 @@ namespace MQTTnet.Extensions.Rpc
{ {
public MqttRpcTopicPair CreateRpcTopics(TopicGenerationContext context) public MqttRpcTopicPair CreateRpcTopics(TopicGenerationContext context)
{ {
ArgumentNullException.ThrowIfNull(context); if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (context.MethodName.Contains("/") || context.MethodName.Contains("+") || context.MethodName.Contains("#")) if (context.MethodName.Contains("/") || context.MethodName.Contains("+") || context.MethodName.Contains("#"))
{ {
throw new ArgumentException("The method name cannot contain /, + or #."); throw new ArgumentException("The method name cannot contain /, + or #.");
} }
var requestTopic = $"MQTTnet.RPC/{Guid.NewGuid():N}/{context.MethodName}"; var requestTopic = $"MQTTnet.RPC/{Guid.NewGuid():N}/{context.MethodName}";
var responseTopic = requestTopic + "/response"; var responseTopic = requestTopic + "/response";
@@ -4,6 +4,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using MQTTnet.Client;
using MQTTnet.Protocol; using MQTTnet.Protocol;
namespace MQTTnet.Extensions.Rpc namespace MQTTnet.Extensions.Rpc

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