mirror of
https://github.com/dotnet/MQTTnet.git
synced 2026-10-15 00:15:15 +08:00
Compare commits
108
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b477ddbe60 | ||
|
|
d74d83453f | ||
|
|
99e70fdbbb | ||
|
|
535a4e010c | ||
|
|
37c1eecce5 | ||
|
|
e791d9aba4 | ||
|
|
5ebc5fcb7e | ||
|
|
51979fa5a4 | ||
|
|
7ac7f256ef | ||
|
|
e7ccd897b0 | ||
|
|
d5d72ac37d | ||
|
|
a5ef10fddb | ||
|
|
c97bd82334 | ||
|
|
75ebf116cf | ||
|
|
2f7f55e3f5 | ||
|
|
e43e7f8e36 | ||
|
|
247988e633 | ||
|
|
93a9d8d1db | ||
|
|
24c418cf43 | ||
|
|
948c7c2b69 | ||
|
|
1cdc343e6d | ||
|
|
27771f0146 | ||
|
|
e7de2b913a | ||
|
|
3dd3279656 | ||
|
|
c5934bf18b | ||
|
|
7715bf37dc | ||
|
|
9dbe1e4b12 | ||
|
|
f13b3eb6bb | ||
|
|
8eb884fa5d | ||
|
|
9f81a2bb71 | ||
|
|
e2f8d0f0ac | ||
|
|
981ea02149 | ||
|
|
8566fd5395 | ||
|
|
8089c6bdbc | ||
|
|
ef5b6c9b32 | ||
|
|
4aa4a12e47 | ||
|
|
6bf2b4eaca | ||
|
|
6ef6b57064 | ||
|
|
7d4027958c | ||
|
|
bdb74642c2 | ||
|
|
63d85184e1 | ||
|
|
0506857f0d | ||
|
|
6417baa081 | ||
|
|
74bb68ad73 | ||
|
|
81a4e7cb86 | ||
|
|
2fdbb02dc8 | ||
|
|
c4eb05105b | ||
|
|
bda21f0fcd | ||
|
|
980a5d0a6d | ||
|
|
e18a91a4b5 | ||
|
|
b95d352236 | ||
|
|
c5f4cf1f9b | ||
|
|
8650002523 | ||
|
|
25c1da6a21 | ||
|
|
4e3ede42c6 | ||
|
|
918a3c8d8b | ||
|
|
290419b3b6 | ||
|
|
32b6146532 | ||
|
|
3a73ec21f7 | ||
|
|
41d5b70344 | ||
|
|
b4769778af | ||
|
|
473f9c31b1 | ||
|
|
ae4f168066 | ||
|
|
c5578a93e8 | ||
|
|
f4b2a10616 | ||
|
|
ed63aa4910 | ||
|
|
4481869d42 | ||
|
|
abbf14b1ec | ||
|
|
4f9e54ba1d | ||
|
|
d80188167a | ||
|
|
4ab956abd1 | ||
|
|
1aa91e3828 | ||
|
|
2410d7b4c1 | ||
|
|
b7328e5fd2 | ||
|
|
7f5c437c46 | ||
|
|
ae5e038548 | ||
|
|
67e0fb8b0b | ||
|
|
b763542329 | ||
|
|
d061da6077 | ||
|
|
82a235aef4 | ||
|
|
fd55982d98 | ||
|
|
aabf180b23 | ||
|
|
1245d8299a | ||
|
|
1ffffaff47 | ||
|
|
c204f6d8b2 | ||
|
|
b775fb256b | ||
|
|
f82d382f23 | ||
|
|
4214af283a | ||
|
|
b32a5c9fa0 | ||
|
|
e7e13a631f | ||
|
|
c12096705f | ||
|
|
eb25500f50 | ||
|
|
4df06980a1 | ||
|
|
f3499013f4 | ||
|
|
158a7fca46 | ||
|
|
09ab30fb0f | ||
|
|
ae84aa43d2 | ||
|
|
b431a5d3fe | ||
|
|
b9683c2943 | ||
|
|
1ba8f74555 | ||
|
|
ec637f7946 | ||
|
|
2599b3dfdc | ||
|
|
d22caa7b70 | ||
|
|
01c90badf9 | ||
|
|
e4f605fbd5 | ||
|
|
6fdee9e0db | ||
|
|
cae7206733 | ||
|
|
e2e7aacc09 |
+5
-6
@@ -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.required_prefix = _
|
||||
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.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.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.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_other_binary_operators = never_if_unnecessary:none
|
||||
dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:none
|
||||
@@ -40,7 +40,6 @@ dotnet_style_qualification_for_method = false:suggestion
|
||||
dotnet_style_qualification_for_property = false:suggestion
|
||||
|
||||
# ReSharper properties
|
||||
resharper_autodetect_indent_settings = true
|
||||
resharper_default_private_modifier = implicit
|
||||
resharper_place_accessorholder_attribute_on_same_line = false
|
||||
resharper_use_indent_from_vs = false
|
||||
@@ -61,7 +60,7 @@ resharper_web_config_wrong_module_highlighting = warning
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = crlf
|
||||
trim_trailing_whitespace = false
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = false
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
* [Client] Fixed _PlatformNotSupportedException_ when using Blazor (#1755, thanks to @Nickztar).
|
||||
* [Client] Added hot reload of client certificates (#1781).
|
||||
* [Client] Added several new option builders and aligned usage (#1781, BREAKING CHANGE!).
|
||||
* [Client] Added support for _RemoteCertificateValidationCallback_ for .NET 4.5.2, 4.6.1 and 4.8 (#1806, thanks to @troky).
|
||||
* [Client] Fixed wrong logging of obsolete feature when connection was not successful (#1801, thanks to @ramonsmits).
|
||||
* [Client] Fixed _NullReferenceException_ when performing several actions when not connected (#1800, thanks to @ramonsmits).
|
||||
* [RpcClient] Added support for passing custom parameters to topic generation context (#1798, thanks to @Temppus).
|
||||
* [Server] Fixed _NullReferenceException_ in retained messages management (#1762, thanks to @logicaloud).
|
||||
* [Server] Exposed new option which allows disabling packet fragmentation (#1753).
|
||||
* [Server] Expired sessions will no longer be used when a client connects (#1756).
|
||||
* [Server] Fixed an issue in connection handling for ASP.NET connections (#1819, thanks to @CZEMacLeod).
|
||||
+105
-39
@@ -3,62 +3,128 @@ name: CI
|
||||
on: [push, pull_request]
|
||||
|
||||
env:
|
||||
VERSION: "4.3.0.${{github.run_number}}"
|
||||
VERSION: "5.1.0.${{github.run_number}}"
|
||||
PACKAGE_SUFFIX: ""
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: windows-2022
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Setup Windows SDK
|
||||
uses: GuillaumeFalourd/setup-windows10-sdk-action@v1
|
||||
with:
|
||||
sdk-version: 18362
|
||||
|
||||
- name: Setup .NET SDK
|
||||
uses: actions/setup-dotnet@v1.9.0
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: |
|
||||
3.1.x
|
||||
6.0.x
|
||||
7.0.x
|
||||
8.0.x
|
||||
|
||||
- name: Setup MSBuild
|
||||
uses: microsoft/setup-msbuild@v1.1
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Setup Signing Certificate
|
||||
- name: Load strong name certificate
|
||||
run: |
|
||||
$secret = '${{ secrets.SNC_BASE64 }}'
|
||||
$decoded = [System.Convert]::FromBase64CharArray($secret, 0, $secret.Length)
|
||||
Set-Content -Path ${{ github.workspace }}\certificate.snk -Value $decoded -AsByteStream
|
||||
echo "$SNC_BASE64" | base64 --decode > "${{ github.workspace }}/certificate.snk"
|
||||
shell: bash
|
||||
env:
|
||||
SNC_BASE64: ${{ secrets.SNC_BASE64 }}
|
||||
|
||||
- name: Restore nuget packages
|
||||
run: msbuild MQTTnet.sln /t:Restore /p:Configuration="Release" /verbosity:m
|
||||
- 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 }} /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=${{ github.workspace }}/certificate.snk
|
||||
|
||||
- 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: Collect nuget Packages
|
||||
uses: actions/upload-artifact@v2
|
||||
- name: Upload nuget packages
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: nuget Packages
|
||||
name: nugets
|
||||
path: |
|
||||
**\*.nupkg
|
||||
**\*.snupkg
|
||||
${{ github.workspace }}/Source/**/*.nupkg
|
||||
${{ github.workspace }}/Source/**/*.snupkg
|
||||
|
||||
- name: Setup VSTest
|
||||
uses: darenm/Setup-VSTest@v1
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
- name: Core Tests
|
||||
run: vstest.console.exe Source\MQTTnet.Tests\bin\Release\net6.0\MQTTnet.Tests.dll
|
||||
steps:
|
||||
- name: Setup .NET SDK
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: |
|
||||
8.0.x
|
||||
|
||||
- name: ASP.NET Tests
|
||||
run: vstest.console.exe Source\MQTTnet.AspNetCore.Tests\bin\Release\netcoreapp3.1\MQTTnet.AspNetCore.Tests.dll
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Publish MyGet nugets
|
||||
if: ${{ github.event_name == 'push' }}
|
||||
run: dotnet nuget push **/*.nupkg -k ${{ secrets.MYGET_API_KEY }} -s https://www.myget.org/F/mqttnet/api/v3/index.json --skip-duplicate
|
||||
- name: Execute tests
|
||||
run: dotnet test --framework net8.0 --configuration Release 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
|
||||
|
||||
- name: Download nuget packages
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: nugets
|
||||
path: nugets
|
||||
|
||||
- name: Install sign CLI tool
|
||||
run: dotnet tool install --tool-path . sign --version 0.9.0-beta.23127.3
|
||||
|
||||
- name: Sign nugets
|
||||
shell: pwsh
|
||||
run: >
|
||||
./sign code azure-key-vault `
|
||||
"**/*.nupkg" `
|
||||
--base-directory "${{ github.workspace }}\nugets" `
|
||||
--publisher-name "MQTTnet" `
|
||||
--description "MQTTnet" `
|
||||
--description-url "https://github.com/dotnet/MQTTnet" `
|
||||
--azure-key-vault-tenant-id "${{ secrets.AZURE_TENANT_ID }}" `
|
||||
--azure-key-vault-client-id "${{ secrets.AZURE_CLIENT_ID }}" `
|
||||
--azure-key-vault-client-secret "${{ secrets.AZURE_CLIENT_SECRET }}" `
|
||||
--azure-key-vault-certificate "${{ secrets.KEY_VAULT_CERTIFICATE_ID }}" `
|
||||
--azure-key-vault-url "${{ secrets.KEY_VAULT_URL }}"
|
||||
|
||||
- name: Upload signed nuget packages
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: signed-nugets
|
||||
path: |
|
||||
${{ github.workspace }}\nugets\**\*.nupkg
|
||||
${{ github.workspace }}\nugets\**\*.snupkg
|
||||
|
||||
publish-myget:
|
||||
if: ${{ github.event_name == 'push' }}
|
||||
needs: sign
|
||||
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
|
||||
@@ -0,0 +1,58 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Product>MQTTnet</Product>
|
||||
<Authors>The contributors of MQTTnet</Authors>
|
||||
<Company>The contributors of MQTTnet</Company>
|
||||
<Copyright>Copyright (c) .NET Foundation and Contributors</Copyright>
|
||||
|
||||
<DelaySign>false</DelaySign>
|
||||
<SignAssembly>false</SignAssembly>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
|
||||
<NeutralLanguage>en-US</NeutralLanguage>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<NoWarn>1591;NETSDK1138;NU1803;NU1901;NU1902</NoWarn>
|
||||
|
||||
<NuGetAuditMode>all</NuGetAuditMode>
|
||||
<NuGetAudit>true</NuGetAudit>
|
||||
<NuGetAuditLevel>low</NuGetAuditLevel>
|
||||
|
||||
<EnableNETAnalyzers>true</EnableNETAnalyzers>
|
||||
<AnalysisLevel>latest</AnalysisLevel>
|
||||
<AnalysisMode>recommended</AnalysisMode>
|
||||
<AnalysisModeStyle>default</AnalysisModeStyle>
|
||||
|
||||
<TestingPlatformShowTestsFailure>true</TestingPlatformShowTestsFailure>
|
||||
<TestingPlatformDotnetTestSupport>true</TestingPlatformDotnetTestSupport>
|
||||
<EnableMSTestRunner>true</EnableMSTestRunner>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(GITHUB_ACTIONS)' == 'true'">
|
||||
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(GeneratePackageOnBuild)'=='true'">
|
||||
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
||||
<PackageIcon>nuget.png</PackageIcon>
|
||||
<PackageProjectUrl>https://github.com/dotnet/MQTTnet</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/dotnet/MQTTnet.git</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<IncludeSymbols>true</IncludeSymbols>
|
||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||
<EmbedUntrackedSources>true</EmbedUntrackedSources>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup Condition="'$(GeneratePackageOnBuild)'=='true'">
|
||||
<None Include="README.md" Pack="true" PackagePath="" Condition="Exists('README.md')" />
|
||||
<None Include="..\..\Images\nuget.png" Pack="true" PackagePath="" />
|
||||
<None Include="..\..\LICENSE" Pack="true" PackagePath="" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(IncludeSymbols)'=='true'">
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All"/>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
+19
-24
@@ -7,30 +7,31 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet", "Source\MQTTnet\M
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{B3F60ECB-45BA-4C66-8903-8BB89CA67998}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
.github\workflows\ci.yml = .github\workflows\ci.yml
|
||||
CODE-OF-CONDUCT.md = CODE-OF-CONDUCT.md
|
||||
Directory.Build.props = Directory.Build.props
|
||||
LICENSE = LICENSE
|
||||
README.md = README.md
|
||||
CODE-OF-CONDUCT.md = CODE-OF-CONDUCT.md
|
||||
Source\ReleaseNotes.md = Source\ReleaseNotes.md
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.AspNetCore", "Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj", "{F10C4060-F7EE-4A83-919F-FF723E72F94A}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Extensions.Rpc", "Source\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj", "{C444E9C8-95FA-430E-9126-274129DE16CD}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Extensions.ManagedClient", "Source\MQTTnet.Extensions.ManagedClient\MQTTnet.Extensions.ManagedClient.csproj", "{C400533A-8EBA-4F0B-BF4D-295C3708604B}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Samples", "Samples\MQTTnet.Samples.csproj", "{71CF35F5-3327-4A91-AAF4-5340F6701771}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Extensions.WebSocket4Net", "Source\MQTTnet.Extensions.WebSocket4Net\MQTTnet.Extensions.WebSocket4Net.csproj", "{2BD01D53-4CA5-4142-BE8D-313876395E3E}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Tests", "Source\MQTTnet.Tests\MQTTnet.Tests.csproj", "{B270F32A-9F3E-42EE-A989-813E35E29ADB}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MQTTnet.Samples", "Samples\MQTTnet.Samples.csproj", "{71CF35F5-3327-4A91-AAF4-5340F6701771}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Benchmarks", "Source\MQTTnet.Benchmarks\MQTTnet.Benchmarks.csproj", "{2F516E76-AAC4-4219-B7D1-34CDD3CFF381}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MQTTnet.Tests", "Source\MQTTnet.Tests\MQTTnet.Tests.csproj", "{B270F32A-9F3E-42EE-A989-813E35E29ADB}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.TestApp", "Source\MQTTnet.TestApp\MQTTnet.TestApp.csproj", "{175D5340-CC5B-4542-939D-4E7D15A0BC8D}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MQTTnet.AspNetCore.Tests", "Source\MQTTnet.AspNetCore.Tests\MQTTnet.AspNetCore.Tests.csproj", "{A238BBBF-C75F-482D-9CC3-BB34ABA9B675}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.AspTestApp", "Source\MQTTnet.AspTestApp\MQTTnet.AspTestApp.csproj", "{72867E4C-4E15-4E8E-8FAB-AE9253286BBC}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MQTTnet.Benchmarks", "Source\MQTTnet.Benchmarks\MQTTnet.Benchmarks.csproj", "{2F516E76-AAC4-4219-B7D1-34CDD3CFF381}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Extensions.TopicTemplate", "Source\MQTTnet.Extensions.TopicTemplate\MQTTnet.Extensions.TopicTemplate.csproj", "{374D5861-F7A6-42A2-8CBE-040846000EF9}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MQTTnet.TestApp", "Source\MQTTnet.TestApp\MQTTnet.TestApp.csproj", "{175D5340-CC5B-4542-939D-4E7D15A0BC8D}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MQTTnet.AspTestApp", "Source\MQTTnet.AspTestApp\MQTTnet.AspTestApp.csproj", "{72867E4C-4E15-4E8E-8FAB-AE9253286BBC}"
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MQTTnet.Server", "Source\MQTTnet.Server\MQTTnet.Server.csproj", "{C876EFFD-C5AD-4E26-BD9F-4C48252C02BC}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
@@ -50,14 +51,6 @@ Global
|
||||
{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.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.Build.0 = Debug|Any CPU
|
||||
{71CF35F5-3327-4A91-AAF4-5340F6701771}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
@@ -66,10 +59,6 @@ Global
|
||||
{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.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.Build.0 = Debug|Any CPU
|
||||
{2F516E76-AAC4-4219-B7D1-34CDD3CFF381}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
@@ -82,12 +71,18 @@ Global
|
||||
{72867E4C-4E15-4E8E-8FAB-AE9253286BBC}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{72867E4C-4E15-4E8E-8FAB-AE9253286BBC}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{72867E4C-4E15-4E8E-8FAB-AE9253286BBC}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{374D5861-F7A6-42A2-8CBE-040846000EF9}.Debug|Any CPU.ActiveCfg = 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.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
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {07536672-5CBC-4BE3-ACE0-708A431A7894}
|
||||
EndGlobalSection
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertIfStatementToSwitchStatement/@EntryIndexedValue">DO_NOT_SHOW</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=InconsistentNaming/@EntryIndexedValue">SUGGESTION</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UncatchableException/@EntryIndexedValue">DO_NOT_SHOW</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UnthrowableException/@EntryIndexedValue">DO_NOT_SHOW</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_FOR/@EntryValue">Required</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_FOREACH/@EntryValue">Required</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_IFELSE/@EntryValue">Required</s:String>
|
||||
@@ -230,16 +234,19 @@
|
||||
<s:String x:Key="/Default/CodeStyle/FileHeader/FileHeaderText/@EntryValue">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.</s:String>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EFeature_002EServices_002ECodeCleanup_002EFileHeader_002EFileHeaderSettingsMigrate/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpAttributeForSingleLineMethodUpgrade/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@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_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_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_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=catched/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=CONNACK/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=PINGREQ/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=PINGRESP/@EntryIndexedValue">True</s:Boolean>
|
||||
|
||||
@@ -3,16 +3,18 @@
|
||||
<br/>
|
||||
</p>
|
||||
|
||||
[](https://www.nuget.org/packages/MQTTnet)
|
||||
[](https://www.nuget.org/packages/MQTTnet)
|
||||
[](https://github.com/dotnet/MQTTnet/actions/workflows/ci.yml)
|
||||
[](https://www.myget.org/feed/mqttnet/package/nuget/MQTTnet)
|
||||
[](https://www.myget.org/feed/mqttnet/package/nuget/MQTTnet)
|
||||

|
||||
[](https://gitter.im/MQTTnet/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
[](https://raw.githubusercontent.com/dotnet/MQTTnet/master/LICENSE)
|
||||
|
||||
# MQTTnet
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
## Features
|
||||
|
||||
@@ -28,13 +30,15 @@ MQTTnet is a high performance .NET library for MQTT based communication. It prov
|
||||
* Unit tested (~636 tests)
|
||||
* No external dependencies
|
||||
|
||||
\* 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_.
|
||||
\* 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_.
|
||||
|
||||
### Client
|
||||
|
||||
* Communication via TCP (+TLS) or WS (WebSocket) supported
|
||||
* Included core _LowLevelMqttClient_ with low level functionality
|
||||
* Also included _ManagedMqttClient_ which maintains the connection and subscriptions automatically. Also application messages are queued and re-scheduled for higher QoS levels automatically.
|
||||
* Also included _ManagedMqttClient_ which maintains the connection and subscriptions automatically. Also application
|
||||
messages are queued and re-scheduled for higher QoS levels automatically.
|
||||
* Rx support (via another project)
|
||||
* Compatible with Microsoft Azure IoT Hub
|
||||
|
||||
@@ -52,9 +56,11 @@ MQTTnet is a high performance .NET library for MQTT based communication. It prov
|
||||
|
||||
## Getting Started
|
||||
|
||||
MQTTnet is delivered via <b>NuGet</b> package manager. You can find the packages here: https://www.nuget.org/packages/MQTTnet/
|
||||
MQTTnet is delivered via <b>NuGet</b> package manager. You can find the packages
|
||||
here: https://www.nuget.org/packages/MQTTnet/
|
||||
|
||||
Use these command in the Package Manager console to install MQTTnet manually:
|
||||
|
||||
```
|
||||
Install-Package MQTTnet
|
||||
```
|
||||
@@ -68,7 +74,8 @@ Samples for using MQTTnet are part of this repository. For starters these sample
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
This project has adopted the code of conduct defined by the Contributor Covenant to clarify expected behavior in our community.
|
||||
This project has adopted the code of conduct defined by the Contributor Covenant to clarify expected behavior in our
|
||||
community.
|
||||
For more information see the [.NET Foundation Code of Conduct](https://dotnetfoundation.org/code-of-conduct).
|
||||
|
||||
## .NET Foundation
|
||||
|
||||
@@ -7,38 +7,66 @@
|
||||
// ReSharper disable InconsistentNaming
|
||||
|
||||
using System.Security.Authentication;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Extensions.WebSocket4Net;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Text;
|
||||
using MQTTnet.Formatter;
|
||||
using MQTTnet.Protocol;
|
||||
using MQTTnet.Samples.Helpers;
|
||||
using MQTTnet.Server;
|
||||
using MQTTnet.Server.EnhancedAuthentication;
|
||||
|
||||
namespace MQTTnet.Samples.Client;
|
||||
|
||||
public static class Client_Connection_Samples
|
||||
{
|
||||
const string mosquitto_org = @"
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIEAzCCAuugAwIBAgIUBY1hlCGvdj4NhBXkZ/uLUZNILAwwDQYJKoZIhvcNAQEL
|
||||
BQAwgZAxCzAJBgNVBAYTAkdCMRcwFQYDVQQIDA5Vbml0ZWQgS2luZ2RvbTEOMAwG
|
||||
A1UEBwwFRGVyYnkxEjAQBgNVBAoMCU1vc3F1aXR0bzELMAkGA1UECwwCQ0ExFjAU
|
||||
BgNVBAMMDW1vc3F1aXR0by5vcmcxHzAdBgkqhkiG9w0BCQEWEHJvZ2VyQGF0Y2hv
|
||||
by5vcmcwHhcNMjAwNjA5MTEwNjM5WhcNMzAwNjA3MTEwNjM5WjCBkDELMAkGA1UE
|
||||
BhMCR0IxFzAVBgNVBAgMDlVuaXRlZCBLaW5nZG9tMQ4wDAYDVQQHDAVEZXJieTES
|
||||
MBAGA1UECgwJTW9zcXVpdHRvMQswCQYDVQQLDAJDQTEWMBQGA1UEAwwNbW9zcXVp
|
||||
dHRvLm9yZzEfMB0GCSqGSIb3DQEJARYQcm9nZXJAYXRjaG9vLm9yZzCCASIwDQYJ
|
||||
KoZIhvcNAQEBBQADggEPADCCAQoCggEBAME0HKmIzfTOwkKLT3THHe+ObdizamPg
|
||||
UZmD64Tf3zJdNeYGYn4CEXbyP6fy3tWc8S2boW6dzrH8SdFf9uo320GJA9B7U1FW
|
||||
Te3xda/Lm3JFfaHjkWw7jBwcauQZjpGINHapHRlpiCZsquAthOgxW9SgDgYlGzEA
|
||||
s06pkEFiMw+qDfLo/sxFKB6vQlFekMeCymjLCbNwPJyqyhFmPWwio/PDMruBTzPH
|
||||
3cioBnrJWKXc3OjXdLGFJOfj7pP0j/dr2LH72eSvv3PQQFl90CZPFhrCUcRHSSxo
|
||||
E6yjGOdnz7f6PveLIB574kQORwt8ePn0yidrTC1ictikED3nHYhMUOUCAwEAAaNT
|
||||
MFEwHQYDVR0OBBYEFPVV6xBUFPiGKDyo5V3+Hbh4N9YSMB8GA1UdIwQYMBaAFPVV
|
||||
6xBUFPiGKDyo5V3+Hbh4N9YSMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL
|
||||
BQADggEBAGa9kS21N70ThM6/Hj9D7mbVxKLBjVWe2TPsGfbl3rEDfZ+OKRZ2j6AC
|
||||
6r7jb4TZO3dzF2p6dgbrlU71Y/4K0TdzIjRj3cQ3KSm41JvUQ0hZ/c04iGDg/xWf
|
||||
+pp58nfPAYwuerruPNWmlStWAXf0UTqRtg4hQDWBuUFDJTuWuuBvEXudz74eh/wK
|
||||
sMwfu1HFvjy5Z0iMDU8PUDepjVolOCue9ashlS4EB5IECdSR2TItnAIiIwimx839
|
||||
LdUdRudafMu5T5Xma182OC0/u/xRlEm+tvKGGmfFcN0piqVl8OrSPBgIlb+1IKJE
|
||||
m/XriWr/Cq4h/JfB7NTsezVslgkBaoU=
|
||||
-----END CERTIFICATE-----
|
||||
";
|
||||
|
||||
public static async Task Clean_Disconnect()
|
||||
{
|
||||
/*
|
||||
* This sample disconnects in a clean way. This will send a MQTT DISCONNECT packet
|
||||
* to the server and close the connection afterwards.
|
||||
* to the server and close the connection afterward.
|
||||
*
|
||||
* See sample _Connect_Client_ for more details.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttFactory();
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
{
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
// This will send the DISCONNECT packet. Calling _Dispose_ without DisconnectAsync the
|
||||
// connection is closed in a "not clean" way. See MQTT specification for more details.
|
||||
await mqttClient.DisconnectAsync(new MqttClientDisconnectOptionsBuilder().WithReason(MqttClientDisconnectOptionsReason.NormalDisconnection).Build());
|
||||
}
|
||||
// This will send the DISCONNECT packet. Calling _Dispose_ without DisconnectAsync the
|
||||
// connection is closed in a "not clean" way. See MQTT specification for more details.
|
||||
await mqttClient.DisconnectAsync(new MqttClientDisconnectOptionsBuilder().WithReason(MqttClientDisconnectOptionsReason.NormalDisconnection).Build());
|
||||
}
|
||||
|
||||
public static async Task Connect_Client()
|
||||
public static async Task Connect()
|
||||
{
|
||||
/*
|
||||
* This sample creates a simple MQTT client and connects to a public broker.
|
||||
@@ -47,191 +75,207 @@ public static class Client_Connection_Samples
|
||||
* The default version of MQTT is 3.1.1.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttFactory();
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
{
|
||||
// Use builder classes where possible in this project.
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||
// Use builder classes where possible in this project.
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||
|
||||
// This will throw an exception if the server is not available.
|
||||
// The result from this message returns additional data which was sent
|
||||
// from the server. Please refer to the MQTT protocol specification for details.
|
||||
var response = await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
// This will throw an exception if the server is not available.
|
||||
// The result from this message returns additional data which was sent
|
||||
// from the server. Please refer to the MQTT protocol specification for details.
|
||||
var response = await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
Console.WriteLine("The MQTT client is connected.");
|
||||
Console.WriteLine("The MQTT client is connected.");
|
||||
|
||||
response.DumpToConsole();
|
||||
response.DumpToConsole();
|
||||
|
||||
// Send a clean disconnect to the server by calling _DisconnectAsync_. Without this the TCP connection
|
||||
// gets dropped and the server will handle this as a non clean disconnect (see MQTT spec for details).
|
||||
var mqttClientDisconnectOptions = mqttFactory.CreateClientDisconnectOptionsBuilder().Build();
|
||||
// Send a clean disconnect to the server by calling _DisconnectAsync_. Without this the TCP connection
|
||||
// gets dropped and the server will handle this as a non clean disconnect (see MQTT spec for details).
|
||||
var mqttClientDisconnectOptions = mqttFactory.CreateClientDisconnectOptionsBuilder().Build();
|
||||
|
||||
await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None);
|
||||
}
|
||||
await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None);
|
||||
}
|
||||
|
||||
public static async Task Connect_Client_Timeout()
|
||||
public static async Task Connect_Using_Enhanced_Authentication()
|
||||
{
|
||||
/*
|
||||
* This sample creates a simple MQTT client and connects to an invalid broker using a timeout.
|
||||
*
|
||||
* This is a modified version of the sample _Connect_Client_! See other sample for more details.
|
||||
* This sample uses enhanced authentication (Kerberos) when creating the connection.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttFactory();
|
||||
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
/*
|
||||
* Server part...
|
||||
*/
|
||||
|
||||
var mqttServerFactory = new MqttServerFactory();
|
||||
|
||||
var serverOptions = mqttServerFactory.CreateServerOptionsBuilder().WithDefaultEndpoint().Build();
|
||||
var server = mqttServerFactory.CreateMqttServer(serverOptions);
|
||||
|
||||
server.ValidatingConnectionAsync += async args =>
|
||||
{
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1").Build();
|
||||
if (args.AuthenticationMethod == "GS2-KRB5")
|
||||
{
|
||||
var result = await args.ExchangeEnhancedAuthenticationAsync(new ExchangeEnhancedAuthenticationOptions(), args.CancellationToken);
|
||||
|
||||
try
|
||||
{
|
||||
using (var timeoutToken = new CancellationTokenSource(TimeSpan.FromSeconds(1)))
|
||||
{
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, timeoutToken.Token);
|
||||
}
|
||||
Console.WriteLine($"Received AUTH data from client: {Encoding.UTF8.GetString(result.AuthenticationData)}");
|
||||
|
||||
var authOptions = mqttServerFactory.CreateExchangeExtendedAuthenticationOptionsBuilder().WithAuthenticationData("reply context token").Build();
|
||||
|
||||
result = await args.ExchangeEnhancedAuthenticationAsync(authOptions, args.CancellationToken);
|
||||
|
||||
Console.WriteLine($"Received AUTH data from client: {Encoding.UTF8.GetString(result.AuthenticationData)}");
|
||||
|
||||
args.ResponseAuthenticationData = "outcome of authentication"u8.ToArray();
|
||||
|
||||
// Authentication DONE!
|
||||
args.ReasonCode = MqttConnectReasonCode.Success; // Also the default!
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Timeout while connecting.");
|
||||
args.ReasonCode = MqttConnectReasonCode.BadAuthenticationMethod;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
await server.StartAsync();
|
||||
|
||||
/*
|
||||
* Client part...
|
||||
*/
|
||||
|
||||
var mqttClientFactory = new MqttClientFactory();
|
||||
|
||||
// Use Kerberos sample from the MQTT RFC.
|
||||
var kerberosAuthenticationHandler = new SampleClientKerberosAuthenticationHandler();
|
||||
|
||||
var clientOptions = mqttClientFactory.CreateClientOptionsBuilder()
|
||||
.WithTcpServer("localhost")
|
||||
.WithProtocolVersion(MqttProtocolVersion.V500)
|
||||
.WithEnhancedAuthenticationHandler(kerberosAuthenticationHandler)
|
||||
.WithEnhancedAuthentication("GS2-KRB5")
|
||||
.Build();
|
||||
|
||||
var client = mqttClientFactory.CreateMqttClient();
|
||||
|
||||
var result = await client.ConnectAsync(clientOptions);
|
||||
|
||||
Console.WriteLine($"Client connect result: {result.ResultCode}");
|
||||
}
|
||||
|
||||
public static async Task Connect_Client_Using_MQTTv5()
|
||||
public static async Task Connect_Using_MQTTv5()
|
||||
{
|
||||
/*
|
||||
* This sample creates a simple MQTT client and connects to a public broker using MQTTv5.
|
||||
*
|
||||
*
|
||||
* This is a modified version of the sample _Connect_Client_! See other sample for more details.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttFactory();
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
{
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").WithProtocolVersion(MqttProtocolVersion.V500).Build();
|
||||
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").WithProtocolVersion(MqttProtocolVersion.V500).Build();
|
||||
|
||||
// In MQTTv5 the response contains much more information.
|
||||
var response = await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
// In MQTTv5 the response contains much more information.
|
||||
var response = await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
Console.WriteLine("The MQTT client is connected.");
|
||||
Console.WriteLine("The MQTT client is connected.");
|
||||
|
||||
response.DumpToConsole();
|
||||
}
|
||||
response.DumpToConsole();
|
||||
}
|
||||
|
||||
public static async Task Connect_Client_Using_TLS_1_2()
|
||||
public static async Task Connect_Using_TLS_1_2()
|
||||
{
|
||||
/*
|
||||
* This sample creates a simple MQTT client and connects to a public broker using TLS 1.2 encryption.
|
||||
*
|
||||
*
|
||||
* This is a modified version of the sample _Connect_Client_! See other sample for more details.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttFactory();
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
{
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("mqtt.fluux.io")
|
||||
.WithTlsOptions(
|
||||
o =>
|
||||
{
|
||||
// The used public broker sometimes has invalid certificates. This sample accepts all
|
||||
// certificates. This should not be used in live environments.
|
||||
o.WithCertificateValidationHandler(_ => true);
|
||||
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("mqtt.fluux.io")
|
||||
.WithTlsOptions(
|
||||
o =>
|
||||
{
|
||||
// The used public broker sometimes has invalid certificates. This sample accepts all
|
||||
// certificates. This should not be used in live environments.
|
||||
o.WithCertificateValidationHandler(_ => true);
|
||||
|
||||
// The default value is determined by the OS. Set manually to force version.
|
||||
o.WithSslProtocols(SslProtocols.Tls12);
|
||||
})
|
||||
.Build();
|
||||
// The default value is determined by the OS. Set manually to force version.
|
||||
o.WithSslProtocols(SslProtocols.Tls12);
|
||||
})
|
||||
.Build();
|
||||
|
||||
using (var timeout = new CancellationTokenSource(5000))
|
||||
{
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, timeout.Token);
|
||||
using var timeout = new CancellationTokenSource(5000);
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, timeout.Token);
|
||||
|
||||
Console.WriteLine("The MQTT client is connected.");
|
||||
}
|
||||
}
|
||||
Console.WriteLine("The MQTT client is connected.");
|
||||
}
|
||||
|
||||
public static async Task Connect_Client_Using_WebSocket4Net()
|
||||
{
|
||||
/*
|
||||
* This sample creates a simple MQTT client and connects to a public broker using a WebSocket connection.
|
||||
* Instead of the .NET implementation of WebSockets the implementation from WebSocket4Net is used. It provides more
|
||||
* encryption algorithms and supports more platforms.
|
||||
*
|
||||
* This is a modified version of the sample _Connect_Client_! See other sample for more details.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttFactory().UseWebSocket4Net();
|
||||
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
{
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("broker.hivemq.com:8000/mqtt")).Build();
|
||||
|
||||
var response = await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
Console.WriteLine("The MQTT client is connected.");
|
||||
|
||||
response.DumpToConsole();
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task Connect_Client_Using_WebSockets()
|
||||
{
|
||||
/*
|
||||
* This sample creates a simple MQTT client and connects to a public broker using a WebSocket connection.
|
||||
*
|
||||
* This is a modified version of the sample _Connect_Client_! See other sample for more details.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttFactory();
|
||||
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
{
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("broker.hivemq.com:8000/mqtt")).Build();
|
||||
|
||||
var response = await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
Console.WriteLine("The MQTT client is connected.");
|
||||
|
||||
response.DumpToConsole();
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task Connect_Client_With_TLS_Encryption()
|
||||
public static async Task Connect_Using_TLS_Encryption()
|
||||
{
|
||||
/*
|
||||
* This sample creates a simple MQTT client and connects to a public broker with enabled TLS encryption.
|
||||
*
|
||||
*
|
||||
* This is a modified version of the sample _Connect_Client_! See other sample for more details.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttFactory();
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
{
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883)
|
||||
.WithTlsOptions(
|
||||
o => o.WithCertificateValidationHandler(
|
||||
// The used public broker sometimes has invalid certificates. This sample accepts all
|
||||
// certificates. This should not be used in live environments.
|
||||
_ => true))
|
||||
.Build();
|
||||
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883)
|
||||
.WithTlsOptions(
|
||||
o => o.WithCertificateValidationHandler(
|
||||
// The used public broker sometimes has invalid certificates. This sample accepts all
|
||||
// certificates. This should not be used in live environments.
|
||||
_ => true))
|
||||
.Build();
|
||||
|
||||
// In MQTTv5 the response contains much more information.
|
||||
using (var timeout = new CancellationTokenSource(5000))
|
||||
{
|
||||
var response = await mqttClient.ConnectAsync(mqttClientOptions, timeout.Token);
|
||||
// In MQTTv5 the response contains much more information.
|
||||
using var timeout = new CancellationTokenSource(5000);
|
||||
var response = await mqttClient.ConnectAsync(mqttClientOptions, timeout.Token);
|
||||
|
||||
Console.WriteLine("The MQTT client is connected.");
|
||||
Console.WriteLine("The MQTT client is connected.");
|
||||
|
||||
response.DumpToConsole();
|
||||
}
|
||||
}
|
||||
response.DumpToConsole();
|
||||
}
|
||||
|
||||
public static async Task Connect_Using_TLS_With_CA_File()
|
||||
{
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
var caChain = new X509Certificate2Collection();
|
||||
caChain.ImportFromPem(mosquitto_org); // from https://test.mosquitto.org/ssl/mosquitto.org.crt
|
||||
|
||||
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883)
|
||||
.WithTlsOptions(new MqttClientTlsOptionsBuilder().WithTrustChain(caChain).Build())
|
||||
.Build();
|
||||
|
||||
var connAck = await mqttClient.ConnectAsync(mqttClientOptions);
|
||||
Console.WriteLine("Connected to test.moquitto.org:8883 with CaFile mosquitto.org.crt: " + connAck.ResultCode);
|
||||
}
|
||||
|
||||
public static async Task Connect_Using_WebSockets()
|
||||
{
|
||||
/*
|
||||
* This sample creates a simple MQTT client and connects to a public broker using a WebSocket connection.
|
||||
*
|
||||
* This is a modified version of the sample _Connect_Client_! See other sample for more details.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("broker.hivemq.com:8000/mqtt")).Build();
|
||||
|
||||
var response = await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
Console.WriteLine("The MQTT client is connected.");
|
||||
|
||||
response.DumpToConsole();
|
||||
}
|
||||
|
||||
public static async Task Connect_With_Amazon_AWS()
|
||||
@@ -242,21 +286,19 @@ public static class Client_Connection_Samples
|
||||
* The broker requires special settings which are set here.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttFactory();
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
{
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("amazon.web.services.broker")
|
||||
// Disabling packet fragmentation is very important!
|
||||
.WithoutPacketFragmentation()
|
||||
.Build();
|
||||
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("amazon.web.services.broker")
|
||||
// Disabling packet fragmentation is very important!
|
||||
.WithoutPacketFragmentation()
|
||||
.Build();
|
||||
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
Console.WriteLine("The MQTT client is connected.");
|
||||
Console.WriteLine("The MQTT client is connected.");
|
||||
|
||||
await mqttClient.DisconnectAsync();
|
||||
}
|
||||
await mqttClient.DisconnectAsync();
|
||||
}
|
||||
|
||||
public static async Task Disconnect_Clean()
|
||||
@@ -267,29 +309,27 @@ public static class Client_Connection_Samples
|
||||
* trigger sending the last will etc.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttFactory();
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
{
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
// Calling _DisconnectAsync_ will send a DISCONNECT packet before closing the connection.
|
||||
// Using a reason code requires MQTT version 5.0.0!
|
||||
await mqttClient.DisconnectAsync(MqttClientDisconnectOptionsReason.ImplementationSpecificError);
|
||||
}
|
||||
// Calling _DisconnectAsync_ will send a DISCONNECT packet before closing the connection.
|
||||
// Using a reason code requires MQTT version 5.0.0!
|
||||
await mqttClient.DisconnectAsync(MqttClientDisconnectOptionsReason.ImplementationSpecificError);
|
||||
}
|
||||
|
||||
public static async Task Disconnect_Non_Clean()
|
||||
{
|
||||
/*
|
||||
* This sample disconnects from the server without sending a DISCONNECT packet.
|
||||
* This way of disconnecting is treated as a non clean disconnect which will
|
||||
* This way of disconnecting is treated as a non-clean disconnect which will
|
||||
* trigger sending the last will etc.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttFactory();
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
var mqttClient = mqttFactory.CreateMqttClient();
|
||||
|
||||
@@ -309,33 +349,29 @@ public static class Client_Connection_Samples
|
||||
* including the reason for that status.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttFactory();
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
{
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("mqtt.fluux.io", 8883)
|
||||
.WithTlsOptions(
|
||||
o =>
|
||||
{
|
||||
o.WithCertificateValidationHandler(
|
||||
eventArgs =>
|
||||
{
|
||||
eventArgs.Certificate.Subject.DumpToConsole();
|
||||
eventArgs.Certificate.GetExpirationDateString().DumpToConsole();
|
||||
eventArgs.Chain.ChainPolicy.RevocationMode.DumpToConsole();
|
||||
eventArgs.Chain.ChainStatus.DumpToConsole();
|
||||
eventArgs.SslPolicyErrors.DumpToConsole();
|
||||
return true;
|
||||
});
|
||||
})
|
||||
.Build();
|
||||
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("mqtt.fluux.io", 8883)
|
||||
.WithTlsOptions(
|
||||
o =>
|
||||
{
|
||||
o.WithCertificateValidationHandler(
|
||||
eventArgs =>
|
||||
{
|
||||
eventArgs.Certificate.Subject.DumpToConsole();
|
||||
eventArgs.Certificate.GetExpirationDateString().DumpToConsole();
|
||||
eventArgs.Chain.ChainPolicy.RevocationMode.DumpToConsole();
|
||||
eventArgs.Chain.ChainStatus.DumpToConsole();
|
||||
eventArgs.SslPolicyErrors.DumpToConsole();
|
||||
return true;
|
||||
});
|
||||
})
|
||||
.Build();
|
||||
|
||||
// In MQTTv5 the response contains much more information.
|
||||
using (var timeout = new CancellationTokenSource(5000))
|
||||
{
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, timeout.Token);
|
||||
}
|
||||
}
|
||||
// In MQTTv5 the response contains much more information.
|
||||
using var timeout = new CancellationTokenSource(5000);
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, timeout.Token);
|
||||
}
|
||||
|
||||
public static async Task Ping_Server()
|
||||
@@ -346,19 +382,17 @@ public static class Client_Connection_Samples
|
||||
* This is only supported in MQTTv5.0.0+.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttFactory();
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
{
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
// This will throw an exception if the server does not reply.
|
||||
await mqttClient.PingAsync(CancellationToken.None);
|
||||
// This will throw an exception if the server does not reply.
|
||||
await mqttClient.PingAsync(CancellationToken.None);
|
||||
|
||||
Console.WriteLine("The MQTT server replied to the ping request.");
|
||||
}
|
||||
Console.WriteLine("The MQTT server replied to the ping request.");
|
||||
}
|
||||
|
||||
public static async Task Reconnect_Using_Event()
|
||||
@@ -366,26 +400,24 @@ public static class Client_Connection_Samples
|
||||
/*
|
||||
* This sample shows how to reconnect when the connection was dropped.
|
||||
* This approach uses one of the events from the client.
|
||||
* This approach has a risk of dead locks! Consider using the timer approach (see sample).
|
||||
* This approach has a risk of deadlocks! Consider using the timer approach (see sample).
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttFactory();
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||
|
||||
mqttClient.DisconnectedAsync += async e =>
|
||||
{
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||
|
||||
mqttClient.DisconnectedAsync += async e =>
|
||||
if (e.ClientWasConnected)
|
||||
{
|
||||
if (e.ClientWasConnected)
|
||||
{
|
||||
// Use the current options as the new options.
|
||||
await mqttClient.ConnectAsync(mqttClient.Options);
|
||||
}
|
||||
};
|
||||
// Use the current options as the new options.
|
||||
await mqttClient.ConnectAsync(mqttClientOptions);
|
||||
}
|
||||
};
|
||||
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
}
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
}
|
||||
|
||||
public static void Reconnect_Using_Timer()
|
||||
@@ -396,40 +428,95 @@ public static class Client_Connection_Samples
|
||||
* This is the recommended way but requires more custom code!
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttFactory();
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
{
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||
|
||||
_ = Task.Run(
|
||||
async () =>
|
||||
_ = Task.Run(
|
||||
async () =>
|
||||
{
|
||||
// User proper cancellation and no while(true).
|
||||
while (true)
|
||||
{
|
||||
// User proper cancellation and no while(true).
|
||||
while (true)
|
||||
try
|
||||
{
|
||||
try
|
||||
// This code will also do the very first connect! So no call to _ConnectAsync_ is required in the first place.
|
||||
if (!await mqttClient.TryPingAsync())
|
||||
{
|
||||
// This code will also do the very first connect! So no call to _ConnectAsync_ is required in the first place.
|
||||
if (!await mqttClient.TryPingAsync())
|
||||
{
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
// Subscribe to topics when session is clean etc.
|
||||
Console.WriteLine("The MQTT client is connected.");
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Handle the exception properly (logging etc.).
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Check the connection state every 5 seconds and perform a reconnect if required.
|
||||
await Task.Delay(TimeSpan.FromSeconds(5));
|
||||
// Subscribe to topics when session is clean etc.
|
||||
Console.WriteLine("The MQTT client is connected.");
|
||||
}
|
||||
}
|
||||
});
|
||||
catch
|
||||
{
|
||||
// Handle the exception properly (logging etc.).
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Check the connection state every 5 seconds and perform a reconnect if required.
|
||||
await Task.Delay(TimeSpan.FromSeconds(5));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Console.WriteLine("Press <Enter> to exit");
|
||||
Console.ReadLine();
|
||||
}
|
||||
|
||||
public static async Task Timeout()
|
||||
{
|
||||
/*
|
||||
* This sample creates a simple MQTT client and connects to an invalid broker using a timeout.
|
||||
*
|
||||
* This is a modified version of the sample _Connect_Client_! See other sample for more details.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1").Build();
|
||||
|
||||
try
|
||||
{
|
||||
using var timeoutToken = new CancellationTokenSource(TimeSpan.FromSeconds(1));
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, timeoutToken.Token);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
Console.WriteLine("Timeout while connecting.");
|
||||
}
|
||||
}
|
||||
|
||||
sealed class SampleClientKerberosAuthenticationHandler : IMqttEnhancedAuthenticationHandler
|
||||
{
|
||||
public async Task HandleEnhancedAuthenticationAsync(MqttEnhancedAuthenticationEventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs.AuthenticationMethod != "GS2-KRB5")
|
||||
{
|
||||
throw new InvalidOperationException("Wrong authentication method");
|
||||
}
|
||||
|
||||
var sendOptions = new SendMqttEnhancedAuthenticationDataOptions
|
||||
{
|
||||
Data = "initial context token"u8.ToArray()
|
||||
};
|
||||
|
||||
await eventArgs.SendAsync(sendOptions);
|
||||
|
||||
var response = await eventArgs.ReceiveAsync(CancellationToken.None);
|
||||
|
||||
Console.WriteLine($"Received AUTH data from server: {Encoding.UTF8.GetString(response.AuthenticationData)}");
|
||||
|
||||
// No further data is required, but we have to fulfil the exchange.
|
||||
sendOptions = new SendMqttEnhancedAuthenticationDataOptions
|
||||
{
|
||||
Data = []
|
||||
};
|
||||
|
||||
await eventArgs.SendAsync(sendOptions, CancellationToken.None);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,8 +6,6 @@
|
||||
// ReSharper disable UnusedMember.Global
|
||||
// ReSharper disable InconsistentNaming
|
||||
|
||||
using MQTTnet.Client;
|
||||
|
||||
namespace MQTTnet.Samples.Client;
|
||||
|
||||
public static class Client_Publish_Samples
|
||||
@@ -23,29 +21,27 @@ public static class Client_Publish_Samples
|
||||
* or at least provides backward compatibility where possible.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttFactory();
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
{
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder()
|
||||
.WithTcpServer("broker.hivemq.com")
|
||||
.Build();
|
||||
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder()
|
||||
.WithTcpServer("broker.hivemq.com")
|
||||
.Build();
|
||||
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
var applicationMessage = new MqttApplicationMessageBuilder()
|
||||
.WithTopic("samples/temperature/living_room")
|
||||
.WithPayload("19.5")
|
||||
.Build();
|
||||
var applicationMessage = new MqttApplicationMessageBuilder()
|
||||
.WithTopic("samples/temperature/living_room")
|
||||
.WithPayload("19.5")
|
||||
.Build();
|
||||
|
||||
await mqttClient.PublishAsync(applicationMessage, CancellationToken.None);
|
||||
await mqttClient.PublishAsync(applicationMessage, CancellationToken.None);
|
||||
|
||||
await mqttClient.DisconnectAsync();
|
||||
|
||||
Console.WriteLine("MQTT application message is published.");
|
||||
}
|
||||
await mqttClient.DisconnectAsync();
|
||||
|
||||
Console.WriteLine("MQTT application message is published.");
|
||||
}
|
||||
|
||||
|
||||
public static async Task Publish_Multiple_Application_Messages()
|
||||
{
|
||||
/*
|
||||
@@ -54,40 +50,38 @@ public static class Client_Publish_Samples
|
||||
* See sample _Publish_Application_Message_ for more details.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttFactory();
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
{
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder()
|
||||
.WithTcpServer("broker.hivemq.com")
|
||||
.Build();
|
||||
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder()
|
||||
.WithTcpServer("broker.hivemq.com")
|
||||
.Build();
|
||||
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
var applicationMessage = new MqttApplicationMessageBuilder()
|
||||
.WithTopic("samples/temperature/living_room")
|
||||
.WithPayload("19.5")
|
||||
.Build();
|
||||
var applicationMessage = new MqttApplicationMessageBuilder()
|
||||
.WithTopic("samples/temperature/living_room")
|
||||
.WithPayload("19.5")
|
||||
.Build();
|
||||
|
||||
await mqttClient.PublishAsync(applicationMessage, CancellationToken.None);
|
||||
|
||||
applicationMessage = new MqttApplicationMessageBuilder()
|
||||
.WithTopic("samples/temperature/living_room")
|
||||
.WithPayload("20.0")
|
||||
.Build();
|
||||
|
||||
await mqttClient.PublishAsync(applicationMessage, CancellationToken.None);
|
||||
await mqttClient.PublishAsync(applicationMessage, CancellationToken.None);
|
||||
|
||||
applicationMessage = new MqttApplicationMessageBuilder()
|
||||
.WithTopic("samples/temperature/living_room")
|
||||
.WithPayload("21.0")
|
||||
.Build();
|
||||
|
||||
await mqttClient.PublishAsync(applicationMessage, CancellationToken.None);
|
||||
applicationMessage = new MqttApplicationMessageBuilder()
|
||||
.WithTopic("samples/temperature/living_room")
|
||||
.WithPayload("20.0")
|
||||
.Build();
|
||||
|
||||
await mqttClient.DisconnectAsync();
|
||||
|
||||
Console.WriteLine("MQTT application message is published.");
|
||||
}
|
||||
await mqttClient.PublishAsync(applicationMessage, CancellationToken.None);
|
||||
|
||||
applicationMessage = new MqttApplicationMessageBuilder()
|
||||
.WithTopic("samples/temperature/living_room")
|
||||
.WithPayload("21.0")
|
||||
.Build();
|
||||
|
||||
await mqttClient.PublishAsync(applicationMessage, CancellationToken.None);
|
||||
|
||||
await mqttClient.DisconnectAsync();
|
||||
|
||||
Console.WriteLine("MQTT application message is published.");
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,6 @@
|
||||
// ReSharper disable InconsistentNaming
|
||||
// ReSharper disable UnusedMember.Local
|
||||
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Packets;
|
||||
using MQTTnet.Protocol;
|
||||
using MQTTnet.Samples.Helpers;
|
||||
@@ -22,86 +21,70 @@ public static class Client_Subscribe_Samples
|
||||
* This sample subscribes to a topic and processes the received message.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttFactory();
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||
|
||||
// Setup message handling before connecting so that queued messages
|
||||
// are also handled properly. When there is no event handler attached all
|
||||
// received messages get lost.
|
||||
mqttClient.ApplicationMessageReceivedAsync += e =>
|
||||
{
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||
Console.WriteLine("Received application message.");
|
||||
e.DumpToConsole();
|
||||
|
||||
// Setup message handling before connecting so that queued messages
|
||||
// are also handled properly. When there is no event handler attached all
|
||||
// received messages get lost.
|
||||
mqttClient.ApplicationMessageReceivedAsync += e =>
|
||||
{
|
||||
Console.WriteLine("Received application message.");
|
||||
e.DumpToConsole();
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder().WithTopicFilter("mqttnet/samples/topic/2").Build();
|
||||
|
||||
var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder()
|
||||
.WithTopicFilter(
|
||||
f =>
|
||||
{
|
||||
f.WithTopic("mqttnet/samples/topic/2");
|
||||
})
|
||||
.Build();
|
||||
await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None);
|
||||
|
||||
await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None);
|
||||
Console.WriteLine("MQTT client subscribed to topic.");
|
||||
|
||||
Console.WriteLine("MQTT client subscribed to topic.");
|
||||
|
||||
Console.WriteLine("Press enter to exit.");
|
||||
Console.ReadLine();
|
||||
}
|
||||
Console.WriteLine("Press enter to exit.");
|
||||
Console.ReadLine();
|
||||
}
|
||||
|
||||
public static async Task Send_Responses()
|
||||
{
|
||||
/*
|
||||
* 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 detailed response to the client. This requires at least QoS level 1 to work!
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttFactory();
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||
mqttClient.ApplicationMessageReceivedAsync += delegate(MqttApplicationMessageReceivedEventArgs args)
|
||||
{
|
||||
mqttClient.ApplicationMessageReceivedAsync += delegate(MqttApplicationMessageReceivedEventArgs args)
|
||||
{
|
||||
// Do some work with the message...
|
||||
// Do some work with the message...
|
||||
|
||||
// Now respond to the broker with a reason code other than success.
|
||||
args.ReasonCode = MqttApplicationMessageReceivedReasonCode.ImplementationSpecificError;
|
||||
args.ResponseReasonString = "That did not work!";
|
||||
// Now respond to the broker with a reason code other than success.
|
||||
args.ReasonCode = MqttApplicationMessageReceivedReasonCode.ImplementationSpecificError;
|
||||
args.ResponseReasonString = "That did not work!";
|
||||
|
||||
// User properties require MQTT v5!
|
||||
args.ResponseUserProperties.Add(new MqttUserProperty("My", "Data"));
|
||||
// User properties require MQTT v5!
|
||||
args.ResponseUserProperties.Add(new MqttUserProperty("My", "Data"));
|
||||
|
||||
// Now the broker will resend the message again.
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
// Now the broker will resend the message again.
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder()
|
||||
.WithTopicFilter(
|
||||
f =>
|
||||
{
|
||||
f.WithTopic("mqttnet/samples/topic/1");
|
||||
})
|
||||
.Build();
|
||||
var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder().WithTopicFilter("topic/test").Build();
|
||||
|
||||
var response = await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None);
|
||||
var response = await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None);
|
||||
|
||||
Console.WriteLine("MQTT client subscribed to topic.");
|
||||
Console.WriteLine("MQTT client subscribed to topic.");
|
||||
|
||||
// The response contains additional data sent by the server after subscribing.
|
||||
response.DumpToConsole();
|
||||
}
|
||||
// The response contains additional data sent by the server after subscribing.
|
||||
response.DumpToConsole();
|
||||
}
|
||||
|
||||
public static async Task Subscribe_Multiple_Topics()
|
||||
@@ -110,41 +93,28 @@ public static class Client_Subscribe_Samples
|
||||
* This sample subscribes to several topics in a single request.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttFactory();
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
{
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
// Create the subscribe options including several topics with different options.
|
||||
// It is also possible to all of these topics using a dedicated call of _SubscribeAsync_ per topic.
|
||||
var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder()
|
||||
.WithTopicFilter(
|
||||
f =>
|
||||
{
|
||||
f.WithTopic("mqttnet/samples/topic/1");
|
||||
})
|
||||
.WithTopicFilter(
|
||||
f =>
|
||||
{
|
||||
f.WithTopic("mqttnet/samples/topic/2").WithNoLocal();
|
||||
})
|
||||
.WithTopicFilter(
|
||||
f =>
|
||||
{
|
||||
f.WithTopic("mqttnet/samples/topic/3").WithRetainHandling(MqttRetainHandling.SendAtSubscribe);
|
||||
})
|
||||
.Build();
|
||||
// Create the subscribe options including several topics with different options.
|
||||
// It is also possible to all of these topics using a dedicated call of _SubscribeAsync_ per topic.
|
||||
var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder()
|
||||
.WithTopicFilter(t => t.WithTopic("topic/1").WithAtLeastOnceQoS())
|
||||
.WithTopicFilter(t => t.WithTopic("topic/2").WithAtMostOnceQoS())
|
||||
.WithTopicFilter(t => t.WithTopic("topic/3").WithNoLocal())
|
||||
.WithTopicFilter(t => t.WithRetainHandling(MqttRetainHandling.SendAtSubscribe))
|
||||
.Build();
|
||||
|
||||
var response = await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None);
|
||||
var response = await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None);
|
||||
|
||||
Console.WriteLine("MQTT client subscribed to topics.");
|
||||
Console.WriteLine("MQTT client subscribed to topics.");
|
||||
|
||||
// The response contains additional data sent by the server after subscribing.
|
||||
response.DumpToConsole();
|
||||
}
|
||||
// The response contains additional data sent by the server after subscribing.
|
||||
response.DumpToConsole();
|
||||
}
|
||||
|
||||
public static async Task Subscribe_Topic()
|
||||
@@ -153,38 +123,32 @@ public static class Client_Subscribe_Samples
|
||||
* This sample subscribes to a topic.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttFactory();
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
{
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder()
|
||||
.WithTopicFilter(
|
||||
f =>
|
||||
{
|
||||
f.WithTopic("mqttnet/samples/topic/1");
|
||||
})
|
||||
.Build();
|
||||
var topicFilter = mqttFactory.CreateTopicFilterBuilder().WithTopic("mqttnet/samples/topic/2").WithAtLeastOnceQoS();
|
||||
|
||||
var response = await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None);
|
||||
var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder().WithTopicFilter(topicFilter).Build();
|
||||
|
||||
Console.WriteLine("MQTT client subscribed to topic.");
|
||||
var response = await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None);
|
||||
|
||||
// The response contains additional data sent by the server after subscribing.
|
||||
response.DumpToConsole();
|
||||
}
|
||||
Console.WriteLine("MQTT client subscribed to topic.");
|
||||
|
||||
// The response contains additional data sent by the server after subscribing.
|
||||
response.DumpToConsole();
|
||||
}
|
||||
|
||||
static void ConcurrentProcessingDisableAutoAcknowledge(CancellationToken shutdownToken, IMqttClient mqttClient)
|
||||
static void ConcurrentProcessingDisableAutoAcknowledge(IMqttClient mqttClient, CancellationToken shutdownToken)
|
||||
{
|
||||
/*
|
||||
* This sample shows how to achieve concurrent processing and not have message AutoAcknowledged
|
||||
* This to have a proper QoS1 (at-least-once) experience for what at least MQTT specification can provide
|
||||
*/
|
||||
mqttClient.ApplicationMessageReceivedAsync += async ea =>
|
||||
mqttClient.ApplicationMessageReceivedAsync += ea =>
|
||||
{
|
||||
ea.AutoAcknowledge = false;
|
||||
|
||||
@@ -201,10 +165,12 @@ public static class Client_Subscribe_Samples
|
||||
}
|
||||
|
||||
_ = Task.Run(ProcessAsync, shutdownToken);
|
||||
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
}
|
||||
|
||||
static void ConcurrentProcessingWithLimit(CancellationToken shutdownToken, IMqttClient mqttClient)
|
||||
static void ConcurrentProcessingWithLimit(IMqttClient mqttClient, CancellationToken shutdownToken)
|
||||
{
|
||||
/*
|
||||
* This sample shows how to achieve concurrent processing, with:
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using MQTTnet.Extensions.TopicTemplate;
|
||||
using MQTTnet.Protocol;
|
||||
using MQTTnet.Samples.Helpers;
|
||||
|
||||
// ReSharper disable UnusedType.Global
|
||||
// ReSharper disable UnusedMember.Global
|
||||
// ReSharper disable InconsistentNaming
|
||||
// ReSharper disable UnusedMember.Local
|
||||
|
||||
namespace MQTTnet.Samples.Client;
|
||||
|
||||
public static class Client_Subscribe_TopicTemplate_Samples
|
||||
{
|
||||
static readonly MqttTopicTemplate SampleTemplate = new("mqttnet/samples/topic/{id}");
|
||||
|
||||
public static async Task Subscribe_Multiple_Topics_With_Template()
|
||||
{
|
||||
/*
|
||||
* This sample subscribes to several topics in a single request.
|
||||
*
|
||||
* This sample requires the nuget 'MQTTnet.Extensions.TopicTemplate'.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
// Create the subscribe options including several topics with different options.
|
||||
// It is also possible to all of these topics using a dedicated call of _SubscribeAsync_ per topic.
|
||||
var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder()
|
||||
.WithTopicTemplate(SampleTemplate.WithParameter("id", "1"))
|
||||
.WithTopicTemplate(SampleTemplate.WithParameter("id", "2"), noLocal: true)
|
||||
.WithTopicTemplate(SampleTemplate.WithParameter("id", "3"), retainHandling: MqttRetainHandling.SendAtSubscribe)
|
||||
.Build();
|
||||
|
||||
var response = await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None);
|
||||
|
||||
Console.WriteLine("MQTT client subscribed to topics.");
|
||||
|
||||
// The response contains additional data sent by the server after subscribing.
|
||||
response.DumpToConsole();
|
||||
}
|
||||
|
||||
public static async Task Subscribe_Topic_With_Template()
|
||||
{
|
||||
/*
|
||||
* This sample subscribes to a topic.
|
||||
*
|
||||
* This sample requires the nuget 'MQTTnet.Extensions.TopicTemplate'.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder().WithTopicTemplate(SampleTemplate.WithParameter("id", "1")).Build();
|
||||
|
||||
var response = await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None);
|
||||
|
||||
Console.WriteLine("MQTT client subscribed to topic.");
|
||||
|
||||
// The response contains additional data sent by the server after subscribing.
|
||||
response.DumpToConsole();
|
||||
}
|
||||
}
|
||||
@@ -6,8 +6,9 @@
|
||||
// ReSharper disable UnusedMember.Global
|
||||
// ReSharper disable InconsistentNaming
|
||||
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Diagnostics.Logger;
|
||||
|
||||
namespace MQTTnet.Samples.Diagnostics;
|
||||
|
||||
@@ -20,23 +21,21 @@ public static class Logger_Samples
|
||||
* to other loggers like Microsoft logger or Serilog or log4net etc.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttFactory(new MyLogger());
|
||||
var mqttFactory = new MqttClientFactory(new MyLogger());
|
||||
|
||||
var mqttClientOptions = mqttFactory.CreateClientOptionsBuilder()
|
||||
.WithTcpServer("broker.hivemq.com")
|
||||
.Build();
|
||||
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
{
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
Console.WriteLine("MQTT client is connected.");
|
||||
Console.WriteLine("MQTT client is connected.");
|
||||
|
||||
var mqttClientDisconnectOptions = mqttFactory.CreateClientDisconnectOptionsBuilder()
|
||||
.Build();
|
||||
var mqttClientDisconnectOptions = mqttFactory.CreateClientDisconnectOptionsBuilder()
|
||||
.Build();
|
||||
|
||||
await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None);
|
||||
}
|
||||
await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None);
|
||||
}
|
||||
|
||||
public static async Task Use_Event_Logger()
|
||||
@@ -50,10 +49,10 @@ public static class Logger_Samples
|
||||
// The logger ID is optional but can be set do distinguish different logger instances.
|
||||
var mqttEventLogger = new MqttNetEventLogger("MyCustomLogger");
|
||||
|
||||
mqttEventLogger.LogMessagePublished += (sender, args) =>
|
||||
mqttEventLogger.LogMessagePublished += (_, args) =>
|
||||
{
|
||||
var output = new StringBuilder();
|
||||
output.AppendLine($">> [{args.LogMessage.Timestamp:O}] [{args.LogMessage.ThreadId}] [{args.LogMessage.Source}] [{args.LogMessage.Level}]: {args.LogMessage.Message}");
|
||||
output.AppendLine(CultureInfo.InvariantCulture, $">> [{args.LogMessage.Timestamp:O}] [{args.LogMessage.ThreadId}] [{args.LogMessage.Source}] [{args.LogMessage.Level}]: {args.LogMessage.Message}");
|
||||
if (args.LogMessage.Exception != null)
|
||||
{
|
||||
output.AppendLine(args.LogMessage.Exception.ToString());
|
||||
@@ -62,23 +61,21 @@ public static class Logger_Samples
|
||||
Console.Write(output);
|
||||
};
|
||||
|
||||
var mqttFactory = new MqttFactory(mqttEventLogger);
|
||||
var mqttFactory = new MqttClientFactory(mqttEventLogger);
|
||||
|
||||
var mqttClientOptions = mqttFactory.CreateClientOptionsBuilder()
|
||||
.WithTcpServer("broker.hivemq.com")
|
||||
.Build();
|
||||
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
{
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
Console.WriteLine("MQTT client is connected.");
|
||||
Console.WriteLine("MQTT client is connected.");
|
||||
|
||||
var mqttClientDisconnectOptions = mqttFactory.CreateClientDisconnectOptionsBuilder()
|
||||
.Build();
|
||||
var mqttClientDisconnectOptions = mqttFactory.CreateClientDisconnectOptionsBuilder()
|
||||
.Build();
|
||||
|
||||
await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None);
|
||||
}
|
||||
await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None);
|
||||
}
|
||||
|
||||
sealed class MyLogger : IMqttNetLogger
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
// ReSharper disable UnusedMember.Global
|
||||
// ReSharper disable InconsistentNaming
|
||||
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Diagnostics.PacketInspection;
|
||||
|
||||
namespace MQTTnet.Samples.Diagnostics;
|
||||
|
||||
@@ -17,26 +17,24 @@ public static class PackageInspection_Samples
|
||||
/*
|
||||
* This sample covers the inspection of outgoing packages from the client.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttFactory();
|
||||
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
{
|
||||
var mqttClientOptions = mqttFactory.CreateClientOptionsBuilder()
|
||||
.WithTcpServer("broker.hivemq.com")
|
||||
.Build();
|
||||
|
||||
mqttClient.InspectPacketAsync += OnInspectPacket;
|
||||
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
Console.WriteLine("MQTT client is connected.");
|
||||
|
||||
var mqttClientDisconnectOptions = mqttFactory.CreateClientDisconnectOptionsBuilder()
|
||||
.Build();
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None);
|
||||
}
|
||||
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||
var mqttClientOptions = mqttFactory.CreateClientOptionsBuilder()
|
||||
.WithTcpServer("broker.hivemq.com")
|
||||
.Build();
|
||||
|
||||
mqttClient.InspectPacketAsync += OnInspectPacket;
|
||||
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
Console.WriteLine("MQTT client is connected.");
|
||||
|
||||
var mqttClientDisconnectOptions = mqttFactory.CreateClientDisconnectOptionsBuilder()
|
||||
.Build();
|
||||
|
||||
await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None);
|
||||
}
|
||||
|
||||
static Task OnInspectPacket(InspectMqttPacketEventArgs eventArgs)
|
||||
|
||||
@@ -8,17 +8,19 @@ namespace MQTTnet.Samples.Helpers;
|
||||
|
||||
internal static class ObjectExtensions
|
||||
{
|
||||
static readonly JsonSerializerOptions SerializerOptions = new()
|
||||
{
|
||||
WriteIndented = true
|
||||
};
|
||||
|
||||
public static TObject DumpToConsole<TObject>(this TObject @object)
|
||||
{
|
||||
var output = "NULL";
|
||||
if (@object != null)
|
||||
{
|
||||
output = JsonSerializer.Serialize(@object, new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true
|
||||
});
|
||||
output = JsonSerializer.Serialize(@object, SerializerOptions);
|
||||
}
|
||||
|
||||
|
||||
Console.WriteLine($"[{@object?.GetType().Name}]:\r\n{output}");
|
||||
return @object;
|
||||
}
|
||||
|
||||
@@ -2,23 +2,19 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
||||
<EnableNETAnalyzers>false</EnableNETAnalyzers>
|
||||
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
|
||||
<NoWarn>1591;NETSDK1138</NoWarn>
|
||||
<NoWarn>$(NoWarn);CA1707</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
<ProjectReference Include="..\Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.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.WebSocket4Net\MQTTnet.Extensions.WebSocket4Net.csproj" />
|
||||
<ProjectReference Include="..\Source\MQTTnet\MQTTnet.csproj" />
|
||||
<ProjectReference Include="..\Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.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.Server\MQTTnet.Server.csproj"/>
|
||||
<ProjectReference Include="..\Source\MQTTnet\MQTTnet.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,52 +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.
|
||||
|
||||
// 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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
-3
@@ -2,6 +2,7 @@
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
|
||||
Console.BackgroundColor = ConsoleColor.White;
|
||||
@@ -28,7 +29,7 @@ Console.Write("Please choose sample class (press Enter to continue): ");
|
||||
Console.ResetColor();
|
||||
|
||||
var input = Console.ReadLine();
|
||||
var selectedIndex = int.Parse(input ?? "0");
|
||||
var selectedIndex = int.Parse(input ?? "0", CultureInfo.InvariantCulture);
|
||||
var selectedSampleClass = sampleClasses[selectedIndex];
|
||||
var sampleMethods = selectedSampleClass.GetMethods(BindingFlags.Static | BindingFlags.Public).OrderBy(m => m.Name).ToList();
|
||||
|
||||
@@ -45,7 +46,7 @@ Console.Write("Please choose sample (press Enter to continue): ");
|
||||
Console.ResetColor();
|
||||
|
||||
input = Console.ReadLine();
|
||||
selectedIndex = int.Parse(input ?? "0");
|
||||
selectedIndex = int.Parse(input ?? "0", CultureInfo.InvariantCulture);
|
||||
var selectedSampleMethod = sampleMethods[selectedIndex];
|
||||
|
||||
Console.WriteLine();
|
||||
@@ -58,7 +59,7 @@ try
|
||||
{
|
||||
if (selectedSampleMethod.Invoke(null, null) is Task task)
|
||||
{
|
||||
await task;
|
||||
await task;
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
// ReSharper disable UnusedMember.Global
|
||||
// ReSharper disable InconsistentNaming
|
||||
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Extensions.Rpc;
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
@@ -19,38 +18,36 @@ public static class RpcClient_Samples
|
||||
* 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.
|
||||
*/
|
||||
|
||||
|
||||
public static async Task Send_Request()
|
||||
{
|
||||
var mqttFactory = new MqttFactory();
|
||||
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
// The RPC client is an addon for the existing client. So we need a regular client
|
||||
// which is wrapped later.
|
||||
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
{
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder()
|
||||
.WithTcpServer("broker.hivemq.com")
|
||||
.Build();
|
||||
|
||||
await mqttClient.ConnectAsync(mqttClientOptions);
|
||||
|
||||
using (var mqttRpcClient = mqttFactory.CreateMqttRpcClient(mqttClient))
|
||||
{
|
||||
// Access to a fully featured application message is not supported for RPC calls!
|
||||
// The method will throw an exception when the response was not received in time.
|
||||
await mqttRpcClient.ExecuteAsync(TimeSpan.FromSeconds(2), "ping", "", MqttQualityOfServiceLevel.AtMostOnce);
|
||||
}
|
||||
|
||||
Console.WriteLine("The RPC call was successful.");
|
||||
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder()
|
||||
.WithTcpServer("broker.hivemq.com")
|
||||
.Build();
|
||||
|
||||
await mqttClient.ConnectAsync(mqttClientOptions);
|
||||
|
||||
using (var mqttRpcClient = mqttFactory.CreateMqttRpcClient(mqttClient))
|
||||
{
|
||||
// Access to a fully featured application message is not supported for RPC calls!
|
||||
// The method will throw an exception when the response was not received in time.
|
||||
await mqttRpcClient.ExecuteAsync(TimeSpan.FromSeconds(2), "ping", "", MqttQualityOfServiceLevel.AtMostOnce);
|
||||
}
|
||||
|
||||
Console.WriteLine("The RPC call was successful.");
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
// 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.
|
||||
mqttClient.subscribe("MQTTnet.RPC/+/ping");
|
||||
mqttClient.subscribe("MQTTnet.RPC/+/do_something");
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
// ReSharper disable EmptyConstructor
|
||||
// ReSharper disable MemberCanBeMadeStatic.Local
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
@@ -51,15 +52,15 @@ public static class Server_ASP_NET_Samples
|
||||
{
|
||||
// Inject other services via constructor.
|
||||
}
|
||||
|
||||
public Task OnClientConnected(ClientConnectedEventArgs eventArgs)
|
||||
|
||||
public static Task OnClientConnected(ClientConnectedEventArgs eventArgs)
|
||||
{
|
||||
Console.WriteLine($"Client '{eventArgs.ClientId}' connected.");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
|
||||
public Task ValidateConnection(ValidatingConnectionEventArgs eventArgs)
|
||||
public static Task ValidateConnection(ValidatingConnectionEventArgs eventArgs)
|
||||
{
|
||||
Console.WriteLine($"Client '{eventArgs.ClientId}' wants to connect. Accepting!");
|
||||
return Task.CompletedTask;
|
||||
@@ -68,6 +69,7 @@ public static class Server_ASP_NET_Samples
|
||||
|
||||
sealed class Startup
|
||||
{
|
||||
[SuppressMessage("Performance", "CA1822:Mark members as static")]
|
||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment environment, MqttController mqttController)
|
||||
{
|
||||
app.UseRouting();
|
||||
@@ -88,11 +90,12 @@ public static class Server_ASP_NET_Samples
|
||||
* Attach event handlers etc. if required.
|
||||
*/
|
||||
|
||||
server.ValidatingConnectionAsync += mqttController.ValidateConnection;
|
||||
server.ClientConnectedAsync += mqttController.OnClientConnected;
|
||||
server.ValidatingConnectionAsync += MqttController.ValidateConnection;
|
||||
server.ClientConnectedAsync += MqttController.OnClientConnected;
|
||||
});
|
||||
}
|
||||
|
||||
[SuppressMessage("Performance", "CA1822:Mark members as static")]
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddHostedMqttServer(
|
||||
|
||||
@@ -13,7 +13,6 @@ namespace MQTTnet.Samples.Server;
|
||||
// ReSharper disable InconsistentNaming
|
||||
// ReSharper disable EmptyConstructor
|
||||
// ReSharper disable MemberCanBeMadeStatic.Local
|
||||
|
||||
public static class Server_Diagnostics_Samples
|
||||
{
|
||||
public static async Task Get_Notified_When_Client_Received_Message()
|
||||
@@ -22,32 +21,30 @@ public static class Server_Diagnostics_Samples
|
||||
* This sample starts a MQTT server and attaches an event which gets fired whenever
|
||||
* a client received and acknowledged a PUBLISH packet.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttFactory();
|
||||
|
||||
var mqttServerFactory = new MqttServerFactory();
|
||||
var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
|
||||
|
||||
using (var mqttServer = mqttFactory.CreateMqttServer(mqttServerOptions))
|
||||
|
||||
using var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions);
|
||||
// Attach the event handler.
|
||||
mqttServer.ClientAcknowledgedPublishPacketAsync += e =>
|
||||
{
|
||||
// Attach the event handler.
|
||||
mqttServer.ClientAcknowledgedPublishPacketAsync += e =>
|
||||
{
|
||||
Console.WriteLine($"Client '{e.ClientId}' acknowledged packet {e.PublishPacket.PacketIdentifier} with topic '{e.PublishPacket.Topic}'");
|
||||
|
||||
// It is also possible to read additional data from the client response. This requires casting the response packet.
|
||||
var qos1AcknowledgePacket = e.AcknowledgePacket as MqttPubAckPacket;
|
||||
Console.WriteLine($"QoS 1 reason code: {qos1AcknowledgePacket?.ReasonCode}");
|
||||
|
||||
var qos2AcknowledgePacket = e.AcknowledgePacket as MqttPubCompPacket;
|
||||
Console.WriteLine($"QoS 2 reason code: {qos1AcknowledgePacket?.ReasonCode}");
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
await mqttServer.StartAsync();
|
||||
Console.WriteLine($"Client '{e.ClientId}' acknowledged packet {e.PublishPacket.PacketIdentifier} with topic '{e.PublishPacket.Topic}'");
|
||||
|
||||
Console.WriteLine("Press Enter to exit.");
|
||||
Console.ReadLine();
|
||||
// It is also possible to read additional data from the client response. This requires casting the response packet.
|
||||
var qos1AcknowledgePacket = e.AcknowledgePacket as MqttPubAckPacket;
|
||||
Console.WriteLine($"QoS 1 reason code: {qos1AcknowledgePacket?.ReasonCode}");
|
||||
|
||||
await mqttServer.StopAsync();
|
||||
}
|
||||
var qos2AcknowledgePacket = e.AcknowledgePacket as MqttPubCompPacket;
|
||||
Console.WriteLine($"QoS 2 reason code: {qos2AcknowledgePacket?.ReasonCode}");
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
await mqttServer.StartAsync();
|
||||
|
||||
Console.WriteLine("Press Enter to exit.");
|
||||
Console.ReadLine();
|
||||
|
||||
await mqttServer.StopAsync();
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@
|
||||
// ReSharper disable MemberCanBeMadeStatic.Local
|
||||
|
||||
using MQTTnet.Internal;
|
||||
using MQTTnet.Protocol;
|
||||
using MQTTnet.Server;
|
||||
|
||||
namespace MQTTnet.Samples.Server;
|
||||
@@ -22,26 +23,52 @@ public static class Server_Intercepting_Samples
|
||||
* Please see _Server_Simple_Samples_ for more details on how to start a server.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttFactory();
|
||||
var mqttServerFactory = new MqttServerFactory();
|
||||
var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
|
||||
|
||||
using (var mqttServer = mqttFactory.CreateMqttServer(mqttServerOptions))
|
||||
using var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions);
|
||||
mqttServer.InterceptingPublishAsync += args =>
|
||||
{
|
||||
mqttServer.InterceptingPublishAsync += args =>
|
||||
// Here we only change the topic of the received application message.
|
||||
// but also changing the payload etc. is required. Changing the QoS after
|
||||
// transmitting is not supported and makes no sense at all.
|
||||
args.ApplicationMessage.Topic += "/manipulated";
|
||||
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
await mqttServer.StartAsync();
|
||||
|
||||
Console.WriteLine("Press Enter to exit.");
|
||||
Console.ReadLine();
|
||||
await mqttServer.StopAsync();
|
||||
}
|
||||
|
||||
public static async Task Intercept_Subscription()
|
||||
{
|
||||
/*
|
||||
* This sample starts a simple MQTT server which does not allow to subscribe to '#'
|
||||
* Start the server and try to subscribe to '#' with a client of choice.
|
||||
*/
|
||||
|
||||
var mqttServerFactory = new MqttServerFactory();
|
||||
var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
|
||||
|
||||
using var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions);
|
||||
mqttServer.InterceptingSubscriptionAsync += args =>
|
||||
{
|
||||
if (args.TopicFilter.Topic.Equals("#", StringComparison.Ordinal))
|
||||
{
|
||||
// Here we only change the topic of the received application message.
|
||||
// but also changing the payload etc. is required. Changing the QoS after
|
||||
// transmitting is not supported and makes no sense at all.
|
||||
args.ApplicationMessage.Topic += "/manipulated";
|
||||
args.Response.ReasonCode = MqttSubscribeReasonCode.NotAuthorized;
|
||||
}
|
||||
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
await mqttServer.StartAsync();
|
||||
await mqttServer.StartAsync();
|
||||
|
||||
Console.WriteLine("Press Enter to exit.");
|
||||
Console.ReadLine();
|
||||
await mqttServer.StopAsync();
|
||||
}
|
||||
Console.WriteLine("Press Enter to exit.");
|
||||
Console.ReadLine();
|
||||
await mqttServer.StopAsync();
|
||||
}
|
||||
}
|
||||
@@ -6,9 +6,11 @@
|
||||
// ReSharper disable UnusedMember.Global
|
||||
// ReSharper disable InconsistentNaming
|
||||
|
||||
using System.Buffers;
|
||||
using System.Text.Json;
|
||||
using MQTTnet.Packets;
|
||||
using MQTTnet.Protocol;
|
||||
using MQTTnet.Server;
|
||||
|
||||
namespace MQTTnet.Samples.Server;
|
||||
|
||||
@@ -22,69 +24,67 @@ public static class Server_Retained_Messages_Samples
|
||||
|
||||
var storePath = Path.Combine(Path.GetTempPath(), "RetainedMessages.json");
|
||||
|
||||
var mqttFactory = new MqttFactory();
|
||||
var mqttServerFactory = new MqttServerFactory();
|
||||
|
||||
// Due to security reasons the "default" endpoint (which is unencrypted) is not enabled by default!
|
||||
var mqttServerOptions = mqttFactory.CreateServerOptionsBuilder().WithDefaultEndpoint().Build();
|
||||
var mqttServerOptions = mqttServerFactory.CreateServerOptionsBuilder().WithDefaultEndpoint().Build();
|
||||
|
||||
using (var server = mqttFactory.CreateMqttServer(mqttServerOptions))
|
||||
using var server = mqttServerFactory.CreateMqttServer(mqttServerOptions);
|
||||
// Make sure that the server will load the retained messages.
|
||||
server.LoadingRetainedMessageAsync += async eventArgs =>
|
||||
{
|
||||
// Make sure that the server will load the retained messages.
|
||||
server.LoadingRetainedMessageAsync += async eventArgs =>
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
var models = await JsonSerializer.DeserializeAsync<List<MqttRetainedMessageModel>>(File.OpenRead(storePath)) ?? new List<MqttRetainedMessageModel>();
|
||||
var retainedMessages = models.Select(m => m.ToApplicationMessage()).ToList();
|
||||
var models = await JsonSerializer.DeserializeAsync<List<MqttRetainedMessageModel>>(File.OpenRead(storePath)) ?? new List<MqttRetainedMessageModel>();
|
||||
var retainedMessages = models.Select(m => m.ToApplicationMessage()).ToList();
|
||||
|
||||
eventArgs.LoadedRetainedMessages = retainedMessages;
|
||||
Console.WriteLine("Retained messages loaded.");
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
// Ignore because nothing is stored yet.
|
||||
Console.WriteLine("No retained messages stored yet.");
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Console.WriteLine(exception);
|
||||
}
|
||||
};
|
||||
|
||||
// Make sure to persist the changed retained messages.
|
||||
server.RetainedMessageChangedAsync += async eventArgs =>
|
||||
eventArgs.LoadedRetainedMessages = retainedMessages;
|
||||
Console.WriteLine("Retained messages loaded.");
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
try
|
||||
{
|
||||
// This sample uses the property _StoredRetainedMessages_ which will contain all(!) retained messages.
|
||||
// The event args also contain the affected retained message (property ChangedRetainedMessage). This can be
|
||||
// used to write all retained messages to dedicated files etc. Then all files must be loaded and a full list
|
||||
// of retained messages must be provided in the loaded event.
|
||||
|
||||
var models = eventArgs.StoredRetainedMessages.Select(MqttRetainedMessageModel.Create);
|
||||
|
||||
var buffer = JsonSerializer.SerializeToUtf8Bytes(models);
|
||||
await File.WriteAllBytesAsync(storePath, buffer);
|
||||
Console.WriteLine("Retained messages saved.");
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Console.WriteLine(exception);
|
||||
}
|
||||
};
|
||||
|
||||
// Make sure to clear the retained messages when they are all deleted via API.
|
||||
server.RetainedMessagesClearedAsync += _ =>
|
||||
// Ignore because nothing is stored yet.
|
||||
Console.WriteLine("No retained messages stored yet.");
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
File.Delete(storePath);
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
Console.WriteLine(exception);
|
||||
}
|
||||
};
|
||||
|
||||
await server.StartAsync();
|
||||
// Make sure to persist the changed retained messages.
|
||||
server.RetainedMessageChangedAsync += async eventArgs =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// This sample uses the property _StoredRetainedMessages_ which will contain all(!) retained messages.
|
||||
// The event args also contain the affected retained message (property ChangedRetainedMessage). This can be
|
||||
// used to write all retained messages to dedicated files etc. Then all files must be loaded and a full list
|
||||
// of retained messages must be provided in the loaded event.
|
||||
|
||||
Console.WriteLine("Press Enter to exit.");
|
||||
Console.ReadLine();
|
||||
}
|
||||
var models = eventArgs.StoredRetainedMessages.Select(MqttRetainedMessageModel.Create);
|
||||
|
||||
var buffer = JsonSerializer.SerializeToUtf8Bytes(models);
|
||||
await File.WriteAllBytesAsync(storePath, buffer);
|
||||
Console.WriteLine("Retained messages saved.");
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Console.WriteLine(exception);
|
||||
}
|
||||
};
|
||||
|
||||
// Make sure to clear the retained messages when they are all deleted via API.
|
||||
server.RetainedMessagesClearedAsync += _ =>
|
||||
{
|
||||
File.Delete(storePath);
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
|
||||
await server.StartAsync();
|
||||
|
||||
Console.WriteLine("Press Enter to exit.");
|
||||
Console.ReadLine();
|
||||
}
|
||||
|
||||
sealed class MqttRetainedMessageModel
|
||||
@@ -100,18 +100,15 @@ public static class Server_Retained_Messages_Samples
|
||||
|
||||
public static MqttRetainedMessageModel Create(MqttApplicationMessage message)
|
||||
{
|
||||
if (message == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(message));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(message);
|
||||
|
||||
return new MqttRetainedMessageModel
|
||||
{
|
||||
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.
|
||||
Payload = message.PayloadSegment.ToArray(),
|
||||
Payload = message.Payload.ToArray(),
|
||||
UserProperties = message.UserProperties,
|
||||
ResponseTopic = message.ResponseTopic,
|
||||
CorrelationData = message.CorrelationData,
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
// ReSharper disable UnusedMember.Global
|
||||
// ReSharper disable InconsistentNaming
|
||||
|
||||
using MQTTnet.Diagnostics;
|
||||
using System.Globalization;
|
||||
using MQTTnet.Diagnostics.Logger;
|
||||
using MQTTnet.Protocol;
|
||||
using MQTTnet.Server;
|
||||
|
||||
@@ -22,17 +23,15 @@ public static class Server_Simple_Samples
|
||||
* See _Run_Minimal_Server_ for more information.
|
||||
*/
|
||||
|
||||
using (var mqttServer = await StartMqttServer())
|
||||
{
|
||||
// Let the client connect.
|
||||
await Task.Delay(TimeSpan.FromSeconds(5));
|
||||
using var mqttServer = await StartMqttServer();
|
||||
// Let the client connect.
|
||||
await Task.Delay(TimeSpan.FromSeconds(5));
|
||||
|
||||
// Now disconnect the client (if connected).
|
||||
var affectedClient = (await mqttServer.GetClientsAsync()).FirstOrDefault(c => c.Id == "MyClient");
|
||||
if (affectedClient != null)
|
||||
{
|
||||
await affectedClient.DisconnectAsync();
|
||||
}
|
||||
// Now disconnect the client (if connected).
|
||||
var affectedClient = (await mqttServer.GetClientsAsync()).FirstOrDefault(c => c.Id == "MyClient");
|
||||
if (affectedClient != null)
|
||||
{
|
||||
await affectedClient.DisconnectAsync();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,18 +43,16 @@ public static class Server_Simple_Samples
|
||||
* See _Run_Minimal_Server_ for more information.
|
||||
*/
|
||||
|
||||
using (var mqttServer = await StartMqttServer())
|
||||
{
|
||||
// Create a new message using the builder as usual.
|
||||
var message = new MqttApplicationMessageBuilder().WithTopic("HelloWorld").WithPayload("Test").Build();
|
||||
using var mqttServer = await StartMqttServer();
|
||||
// Create a new message using the builder as usual.
|
||||
var message = new MqttApplicationMessageBuilder().WithTopic("HelloWorld").WithPayload("Test").Build();
|
||||
|
||||
// Now inject the new message at the broker.
|
||||
await mqttServer.InjectApplicationMessage(
|
||||
new InjectedMqttApplicationMessage(message)
|
||||
{
|
||||
SenderClientId = "SenderClientId"
|
||||
});
|
||||
}
|
||||
// Now inject the new message at the broker.
|
||||
await mqttServer.InjectApplicationMessage(
|
||||
new InjectedMqttApplicationMessage(message)
|
||||
{
|
||||
SenderClientId = "SenderClientId"
|
||||
});
|
||||
}
|
||||
|
||||
public static async Task Run_Minimal_Server()
|
||||
@@ -64,7 +61,7 @@ public static class Server_Simple_Samples
|
||||
* This sample starts a simple MQTT server which will accept any TCP connection.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttFactory();
|
||||
var mqttServerFactory = new MqttServerFactory();
|
||||
|
||||
// The port for the default endpoint is 1883.
|
||||
// The default endpoint is NOT encrypted!
|
||||
@@ -77,16 +74,14 @@ public static class Server_Simple_Samples
|
||||
// .WithDefaultEndpointPort(1234)
|
||||
// .Build();
|
||||
|
||||
using (var mqttServer = mqttFactory.CreateMqttServer(mqttServerOptions))
|
||||
{
|
||||
await mqttServer.StartAsync();
|
||||
using var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions);
|
||||
await mqttServer.StartAsync();
|
||||
|
||||
Console.WriteLine("Press Enter to exit.");
|
||||
Console.ReadLine();
|
||||
Console.WriteLine("Press Enter to exit.");
|
||||
Console.ReadLine();
|
||||
|
||||
// Stop and dispose the MQTT server if it is no longer needed!
|
||||
await mqttServer.StopAsync();
|
||||
}
|
||||
// Stop and dispose the MQTT server if it is no longer needed!
|
||||
await mqttServer.StopAsync();
|
||||
}
|
||||
|
||||
public static async Task Run_Server_With_Logging()
|
||||
@@ -99,20 +94,18 @@ public static class Server_Simple_Samples
|
||||
* See sample "Run_Minimal_Server" for more details.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttFactory(new ConsoleLogger());
|
||||
var mqttServerFactory = new MqttServerFactory(new ConsoleLogger());
|
||||
|
||||
var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
|
||||
|
||||
using (var mqttServer = mqttFactory.CreateMqttServer(mqttServerOptions))
|
||||
{
|
||||
await mqttServer.StartAsync();
|
||||
using var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions);
|
||||
await mqttServer.StartAsync();
|
||||
|
||||
Console.WriteLine("Press Enter to exit.");
|
||||
Console.ReadLine();
|
||||
Console.WriteLine("Press Enter to exit.");
|
||||
Console.ReadLine();
|
||||
|
||||
// Stop and dispose the MQTT server if it is no longer needed!
|
||||
await mqttServer.StopAsync();
|
||||
}
|
||||
// Stop and dispose the MQTT server if it is no longer needed!
|
||||
await mqttServer.StopAsync();
|
||||
}
|
||||
|
||||
public static async Task Validating_Connections()
|
||||
@@ -123,55 +116,53 @@ public static class Server_Simple_Samples
|
||||
* See _Run_Minimal_Server_ for more information.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttFactory();
|
||||
var mqttServerFactory = new MqttServerFactory();
|
||||
|
||||
var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
|
||||
|
||||
using (var mqttServer = mqttFactory.CreateMqttServer(mqttServerOptions))
|
||||
using var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions);
|
||||
// Setup connection validation before starting the server so that there is
|
||||
// no change to connect without valid credentials.
|
||||
mqttServer.ValidatingConnectionAsync += e =>
|
||||
{
|
||||
// Setup connection validation before starting the server so that there is
|
||||
// no change to connect without valid credentials.
|
||||
mqttServer.ValidatingConnectionAsync += e =>
|
||||
if (e.ClientId != "ValidClientId")
|
||||
{
|
||||
if (e.ClientId != "ValidClientId")
|
||||
{
|
||||
e.ReasonCode = MqttConnectReasonCode.ClientIdentifierNotValid;
|
||||
}
|
||||
e.ReasonCode = MqttConnectReasonCode.ClientIdentifierNotValid;
|
||||
}
|
||||
|
||||
if (e.UserName != "ValidUser")
|
||||
{
|
||||
e.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword;
|
||||
}
|
||||
if (e.UserName != "ValidUser")
|
||||
{
|
||||
e.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword;
|
||||
}
|
||||
|
||||
if (e.Password != "SecretPassword")
|
||||
{
|
||||
e.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword;
|
||||
}
|
||||
if (e.Password != "SecretPassword")
|
||||
{
|
||||
e.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword;
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
|
||||
await mqttServer.StartAsync();
|
||||
await mqttServer.StartAsync();
|
||||
|
||||
Console.WriteLine("Press Enter to exit.");
|
||||
Console.ReadLine();
|
||||
Console.WriteLine("Press Enter to exit.");
|
||||
Console.ReadLine();
|
||||
|
||||
await mqttServer.StopAsync();
|
||||
}
|
||||
await mqttServer.StopAsync();
|
||||
}
|
||||
|
||||
static async Task<MqttServer> StartMqttServer()
|
||||
{
|
||||
var mqttFactory = new MqttFactory();
|
||||
var mqttServerFactory = new MqttServerFactory();
|
||||
|
||||
// Due to security reasons the "default" endpoint (which is unencrypted) is not enabled by default!
|
||||
var mqttServerOptions = mqttFactory.CreateServerOptionsBuilder().WithDefaultEndpoint().Build();
|
||||
var server = mqttFactory.CreateMqttServer(mqttServerOptions);
|
||||
var mqttServerOptions = mqttServerFactory.CreateServerOptionsBuilder().WithDefaultEndpoint().Build();
|
||||
var server = mqttServerFactory.CreateMqttServer(mqttServerOptions);
|
||||
await server.StartAsync();
|
||||
return server;
|
||||
}
|
||||
|
||||
class ConsoleLogger : IMqttNetLogger
|
||||
sealed class ConsoleLogger : IMqttNetLogger
|
||||
{
|
||||
readonly object _consoleSyncRoot = new();
|
||||
|
||||
@@ -201,7 +192,7 @@ public static class Server_Simple_Samples
|
||||
|
||||
if (parameters?.Length > 0)
|
||||
{
|
||||
message = string.Format(message, parameters);
|
||||
message = string.Format(CultureInfo.InvariantCulture, message, parameters);
|
||||
}
|
||||
|
||||
lock (_consoleSyncRoot)
|
||||
@@ -216,4 +207,4 @@ public static class Server_Simple_Samples
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,23 +24,21 @@ public static class Server_TLS_Samples
|
||||
* See sample "Run_Minimal_Server" for more details.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttFactory();
|
||||
var mqttServerFactory = new MqttServerFactory();
|
||||
|
||||
// This certificate is self signed so that
|
||||
var certificate = CreateSelfSignedCertificate("1.3.6.1.5.5.7.3.1");
|
||||
|
||||
|
||||
var mqttServerOptions = new MqttServerOptionsBuilder().WithEncryptionCertificate(certificate).WithEncryptedEndpoint().Build();
|
||||
|
||||
using (var mqttServer = mqttFactory.CreateMqttServer(mqttServerOptions))
|
||||
{
|
||||
await mqttServer.StartAsync();
|
||||
using var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions);
|
||||
await mqttServer.StartAsync();
|
||||
|
||||
Console.WriteLine("Press Enter to exit.");
|
||||
Console.ReadLine();
|
||||
Console.WriteLine("Press Enter to exit.");
|
||||
Console.ReadLine();
|
||||
|
||||
// Stop and dispose the MQTT server if it is no longer needed!
|
||||
await mqttServer.StopAsync();
|
||||
}
|
||||
// Stop and dispose the MQTT server if it is no longer needed!
|
||||
await mqttServer.StopAsync();
|
||||
}
|
||||
|
||||
static X509Certificate2 CreateSelfSignedCertificate(string oid)
|
||||
@@ -50,26 +48,22 @@ public static class Server_TLS_Samples
|
||||
sanBuilder.AddIpAddress(IPAddress.IPv6Loopback);
|
||||
sanBuilder.AddDnsName("localhost");
|
||||
|
||||
using (var rsa = RSA.Create())
|
||||
{
|
||||
var certRequest = new CertificateRequest("CN=localhost", rsa, HashAlgorithmName.SHA512, RSASignaturePadding.Pkcs1);
|
||||
using var rsa = RSA.Create();
|
||||
var certRequest = new CertificateRequest("CN=localhost", rsa, HashAlgorithmName.SHA512, RSASignaturePadding.Pkcs1);
|
||||
|
||||
certRequest.CertificateExtensions.Add(
|
||||
new X509KeyUsageExtension(X509KeyUsageFlags.DataEncipherment | X509KeyUsageFlags.KeyEncipherment | X509KeyUsageFlags.DigitalSignature, false));
|
||||
certRequest.CertificateExtensions.Add(
|
||||
new X509KeyUsageExtension(X509KeyUsageFlags.DataEncipherment | X509KeyUsageFlags.KeyEncipherment | X509KeyUsageFlags.DigitalSignature, false));
|
||||
|
||||
certRequest.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(new OidCollection { new(oid) }, false));
|
||||
certRequest.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(new OidCollection { new(oid) }, false));
|
||||
|
||||
certRequest.CertificateExtensions.Add(sanBuilder.Build());
|
||||
certRequest.CertificateExtensions.Add(sanBuilder.Build());
|
||||
|
||||
using (var certificate = certRequest.CreateSelfSigned(DateTimeOffset.Now.AddMinutes(-10), DateTimeOffset.Now.AddMinutes(10)))
|
||||
{
|
||||
var pfxCertificate = new X509Certificate2(
|
||||
certificate.Export(X509ContentType.Pfx),
|
||||
(string)null!,
|
||||
X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable);
|
||||
using var certificate = certRequest.CreateSelfSigned(DateTimeOffset.Now.AddMinutes(-10), DateTimeOffset.Now.AddMinutes(10));
|
||||
var pfxCertificate = new X509Certificate2(
|
||||
certificate.Export(X509ContentType.Pfx),
|
||||
(string)null!,
|
||||
X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable);
|
||||
|
||||
return pfxCertificate;
|
||||
}
|
||||
}
|
||||
return pfxCertificate;
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
<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>
|
||||
@@ -1,50 +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.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,27 +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.Tests.Mockups
|
||||
{
|
||||
public sealed class DuplexPipeMockup : IDuplexPipe
|
||||
{
|
||||
public DuplexPipeMockup()
|
||||
{
|
||||
var pool = new LimitedMemoryPool();
|
||||
var pipeOptions = new PipeOptions(pool);
|
||||
Receive = new Pipe(pipeOptions);
|
||||
Send = new Pipe(pipeOptions);
|
||||
}
|
||||
|
||||
PipeReader IDuplexPipe.Input => Receive.Reader;
|
||||
|
||||
PipeWriter IDuplexPipe.Output => Send.Writer;
|
||||
|
||||
public Pipe Receive { get; }
|
||||
|
||||
public Pipe Send { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,22 +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.Buffers;
|
||||
|
||||
namespace MQTTnet.AspNetCore.Tests.Mockups
|
||||
{
|
||||
public sealed class LimitedMemoryPool : MemoryPool<byte>
|
||||
{
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
}
|
||||
|
||||
public override IMemoryOwner<byte> Rent(int minBufferSize = -1)
|
||||
{
|
||||
return new MemoryOwner(minBufferSize);
|
||||
}
|
||||
|
||||
public override int MaxBufferSize { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,37 +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;
|
||||
|
||||
namespace MQTTnet.AspNetCore.Tests.Mockups
|
||||
{
|
||||
public sealed class MemoryOwner : IMemoryOwner<byte>
|
||||
{
|
||||
readonly byte[] _raw;
|
||||
|
||||
public MemoryOwner(int size)
|
||||
{
|
||||
if (size <= 0)
|
||||
{
|
||||
size = 1024;
|
||||
}
|
||||
|
||||
if (size > 4096)
|
||||
{
|
||||
size = 4096;
|
||||
}
|
||||
|
||||
_raw = ArrayPool<byte>.Shared.Rent(size);
|
||||
Memory = _raw;
|
||||
}
|
||||
|
||||
public Memory<byte> Memory { get; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(_raw);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,133 +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.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.AspNetCore.Tests.Mockups;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Exceptions;
|
||||
using MQTTnet.Formatter;
|
||||
using MQTTnet.Packets;
|
||||
using MQTTnet.Protocol;
|
||||
using MQTTnet.Tests.Helpers;
|
||||
|
||||
namespace MQTTnet.AspNetCore.Tests
|
||||
{
|
||||
[TestClass]
|
||||
public class MqttConnectionContextTest
|
||||
{
|
||||
[TestMethod]
|
||||
public async Task TestCorruptedConnectPacket()
|
||||
{
|
||||
var writer = new MqttBufferWriter(4096, 65535);
|
||||
var serializer = new MqttPacketFormatterAdapter(writer);
|
||||
var pipe = new DuplexPipeMockup();
|
||||
var connection = new DefaultConnectionContext();
|
||||
connection.Transport = pipe;
|
||||
var ctx = new MqttConnectionContext(serializer, connection);
|
||||
|
||||
await pipe.Receive.Writer.WriteAsync(writer.AddMqttHeader(MqttControlPacketType.Connect, Array.Empty<byte>()));
|
||||
|
||||
await Assert.ThrowsExceptionAsync<MqttProtocolViolationException>(() => ctx.ReceivePacketAsync(CancellationToken.None));
|
||||
|
||||
// the first exception should complete the pipes so if someone tries to use the connection after that it should throw immidiatly
|
||||
await Assert.ThrowsExceptionAsync<InvalidOperationException>(() => ctx.ReceivePacketAsync(CancellationToken.None));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task TestEndpoint()
|
||||
{
|
||||
var mockup = new ConnectionHandlerMockup();
|
||||
|
||||
|
||||
using (var host = new WebHostBuilder().UseKestrel(kestrel => kestrel.ListenLocalhost(1883, listener => listener.Use((ctx, next) => mockup.OnConnectedAsync(ctx))))
|
||||
.UseStartup<Startup>()
|
||||
.ConfigureServices(
|
||||
(hostContext, services) =>
|
||||
{
|
||||
services.AddHostedMqttServer(o => o.WithoutDefaultEndpoint());
|
||||
services.AddSingleton<IMqttServerAdapter>(mockup);
|
||||
})
|
||||
.Build())
|
||||
|
||||
using (var client = new MqttFactory().CreateMqttClient())
|
||||
{
|
||||
host.Start();
|
||||
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("localhost").Build(), CancellationToken.None);
|
||||
|
||||
var ctx = await mockup.Context.Task;
|
||||
#if NETCOREAPP3_1
|
||||
var ep = IPEndPoint.Parse(ctx.Endpoint);
|
||||
Assert.IsNotNull(ep);
|
||||
#endif
|
||||
Assert.IsNotNull(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
// COMMENTED OUT DUE TO DEAD LOCK? OR VERY VERY SLOW PERFORMANCE ON LOCAL DEV MACHINE. TEST WAS STILL RUNNING AFTER SEVERAL MINUTES!
|
||||
//[TestMethod]
|
||||
//public async Task TestParallelWrites()
|
||||
//{
|
||||
// var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311);
|
||||
// var pipe = new DuplexPipeMockup();
|
||||
// var connection = new DefaultConnectionContext();
|
||||
// connection.Transport = pipe;
|
||||
// var ctx = new MqttConnectionContext(serializer, connection);
|
||||
|
||||
// var tasks = Enumerable.Range(1, 100).Select(_ => Task.Run(async () =>
|
||||
// {
|
||||
// for (int i = 0; i < 100; i++)
|
||||
// {
|
||||
// await ctx.SendPacketAsync(new MqttPublishPacket(), TimeSpan.Zero, CancellationToken.None).ConfigureAwait(false);
|
||||
// }
|
||||
// }));
|
||||
|
||||
// await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
//}
|
||||
|
||||
[TestMethod]
|
||||
public async Task TestLargePacket()
|
||||
{
|
||||
var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535));
|
||||
var pipe = new DuplexPipeMockup();
|
||||
var connection = new DefaultConnectionContext();
|
||||
connection.Transport = pipe;
|
||||
var ctx = new MqttConnectionContext(serializer, connection);
|
||||
|
||||
await ctx.SendPacketAsync(new MqttPublishPacket { PayloadSegment = new ArraySegment<byte>(new byte[20_000]) }, CancellationToken.None).ConfigureAwait(false);
|
||||
|
||||
var readResult = await pipe.Send.Reader.ReadAsync();
|
||||
Assert.IsTrue(readResult.Buffer.Length > 20000);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task TestReceivePacketAsyncThrowsWhenReaderCompleted()
|
||||
{
|
||||
var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535));
|
||||
var pipe = new DuplexPipeMockup();
|
||||
var connection = new DefaultConnectionContext();
|
||||
connection.Transport = pipe;
|
||||
var ctx = new MqttConnectionContext(serializer, connection);
|
||||
|
||||
pipe.Receive.Writer.Complete();
|
||||
|
||||
await Assert.ThrowsExceptionAsync<MqttCommunicationException>(() => ctx.ReceivePacketAsync(CancellationToken.None));
|
||||
}
|
||||
|
||||
class Startup
|
||||
{
|
||||
public void Configure(IApplicationBuilder app)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,51 +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.
|
||||
|
||||
#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,22 +1,20 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<IsPackable>false</IsPackable>
|
||||
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
||||
<EnableNETAnalyzers>false</EnableNETAnalyzers>
|
||||
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
|
||||
<NoWarn>1591;NETSDK1138</NoWarn>
|
||||
<NoWarn>$(NoWarn);CA1707</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<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>
|
||||
<Folder Include="wwwroot\" />
|
||||
<Folder Include="wwwroot\"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
clientId: 'mqttnet_web_client'
|
||||
}
|
||||
|
||||
const client = mqtt.connect('ws://localhost:5049/mqtt')
|
||||
const client = mqtt.connect('ws://localhost:5000/mqtt')
|
||||
|
||||
client.on('connect', function () {
|
||||
console.log('Connected')
|
||||
|
||||
@@ -4,20 +4,22 @@
|
||||
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
|
||||
namespace MQTTnet.AspTestApp.Pages
|
||||
namespace MQTTnet.AspTestApp.Pages;
|
||||
|
||||
public partial class IndexModel : PageModel
|
||||
{
|
||||
public class IndexModel : PageModel
|
||||
readonly ILogger<IndexModel> _logger;
|
||||
|
||||
public IndexModel(ILogger<IndexModel> logger)
|
||||
{
|
||||
private readonly ILogger<IndexModel> _logger;
|
||||
|
||||
public IndexModel(ILogger<IndexModel> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void OnGet()
|
||||
{
|
||||
|
||||
}
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void OnGet()
|
||||
{
|
||||
LogOnGet();
|
||||
}
|
||||
|
||||
[LoggerMessage(Level = LogLevel.Information, Message = "OnGet")]
|
||||
private partial void LogOnGet();
|
||||
}
|
||||
@@ -30,10 +30,7 @@ app.UseAuthorization();
|
||||
app.MapRazorPages();
|
||||
|
||||
// Setup MQTT stuff.
|
||||
app.UseEndpoints(endpoints =>
|
||||
{
|
||||
endpoints.MapMqtt("/mqtt");
|
||||
});
|
||||
app.MapMqtt("/mqtt");
|
||||
|
||||
app.UseMqttServer(server =>
|
||||
{
|
||||
@@ -42,7 +39,7 @@ app.UseMqttServer(server =>
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
var mqttApplicationMessage = new MqttApplicationMessageBuilder()
|
||||
.WithPayload($"Test application message from MQTTnet server.")
|
||||
.WithPayload("Test application message from MQTTnet server.")
|
||||
.WithTopic("message")
|
||||
.Build();
|
||||
|
||||
|
||||
@@ -2,20 +2,21 @@
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using MQTTnet.Server;
|
||||
|
||||
namespace MQTTnet.AspNetCore
|
||||
namespace MQTTnet.AspNetCore;
|
||||
|
||||
public static class ApplicationBuilderExtensions
|
||||
{
|
||||
public static class ApplicationBuilderExtensions
|
||||
[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(...).")]
|
||||
public static IApplicationBuilder UseMqttEndpoint(this IApplicationBuilder app, string path = "/mqtt")
|
||||
{
|
||||
[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(...).")]
|
||||
public static IApplicationBuilder UseMqttEndpoint(this IApplicationBuilder app, string path = "/mqtt")
|
||||
{
|
||||
app.UseWebSockets();
|
||||
app.Use(async (context, next) =>
|
||||
app.UseWebSockets();
|
||||
app.Use(
|
||||
async (context, next) =>
|
||||
{
|
||||
if (!context.WebSockets.IsWebSocketRequest || context.Request.Path != path)
|
||||
{
|
||||
@@ -31,22 +32,19 @@ namespace MQTTnet.AspNetCore
|
||||
}
|
||||
|
||||
var adapter = app.ApplicationServices.GetRequiredService<MqttWebSocketServerAdapter>();
|
||||
using (var webSocket = await context.WebSockets.AcceptWebSocketAsync(subProtocol).ConfigureAwait(false))
|
||||
{
|
||||
await adapter.RunWebSocketConnectionAsync(webSocket, context);
|
||||
}
|
||||
using var webSocket = await context.WebSockets.AcceptWebSocketAsync(subProtocol).ConfigureAwait(false);
|
||||
await adapter.RunWebSocketConnectionAsync(webSocket, context);
|
||||
});
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
public static IApplicationBuilder UseMqttServer(this IApplicationBuilder app, Action<MqttServer> configure)
|
||||
{
|
||||
var server = app.ApplicationServices.GetRequiredService<MqttServer>();
|
||||
|
||||
configure(server);
|
||||
|
||||
return app;
|
||||
}
|
||||
return app;
|
||||
}
|
||||
}
|
||||
|
||||
public static IApplicationBuilder UseMqttServer(this IApplicationBuilder app, Action<MqttServer> configure)
|
||||
{
|
||||
var server = app.ApplicationServices.GetRequiredService<MqttServer>();
|
||||
|
||||
configure(server);
|
||||
|
||||
return app;
|
||||
}
|
||||
}
|
||||
@@ -3,17 +3,15 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using MQTTnet.Server;
|
||||
using System;
|
||||
|
||||
namespace MQTTnet.AspNetCore
|
||||
namespace MQTTnet.AspNetCore;
|
||||
|
||||
public sealed class AspNetMqttServerOptionsBuilder : MqttServerOptionsBuilder
|
||||
{
|
||||
public sealed class AspNetMqttServerOptionsBuilder : MqttServerOptionsBuilder
|
||||
public AspNetMqttServerOptionsBuilder(IServiceProvider serviceProvider)
|
||||
{
|
||||
public AspNetMqttServerOptionsBuilder(IServiceProvider serviceProvider)
|
||||
{
|
||||
ServiceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
|
||||
}
|
||||
|
||||
public IServiceProvider ServiceProvider { get; }
|
||||
ServiceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
|
||||
}
|
||||
}
|
||||
|
||||
public IServiceProvider ServiceProvider { get; }
|
||||
}
|
||||
@@ -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.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;
|
||||
}
|
||||
}
|
||||
@@ -1,38 +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 MQTTnet.Adapter;
|
||||
using MQTTnet.AspNetCore.Client.Tcp;
|
||||
using MQTTnet.Formatter;
|
||||
using System;
|
||||
using System.Net;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Diagnostics;
|
||||
|
||||
namespace MQTTnet.AspNetCore.Client
|
||||
{
|
||||
public sealed class MqttClientConnectionContextFactory : IMqttClientAdapterFactory
|
||||
{
|
||||
public IMqttChannelAdapter CreateClientAdapter(MqttClientOptions options, MqttPacketInspector packetInspector, IMqttNetLogger logger)
|
||||
{
|
||||
if (options == null) throw new ArgumentNullException(nameof(options));
|
||||
|
||||
switch (options.ChannelOptions)
|
||||
{
|
||||
case MqttClientTcpOptions tcpOptions:
|
||||
{
|
||||
var endpoint = new DnsEndPoint(tcpOptions.Server, tcpOptions.GetPort());
|
||||
var tcpConnection = new TcpConnection(endpoint);
|
||||
|
||||
var formatter = new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(4096, 65535));
|
||||
return new MqttConnectionContext(formatter, tcpConnection);
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,26 +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.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,45 +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.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,74 +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.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,41 +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.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,105 +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;
|
||||
|
||||
#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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,272 +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 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 class TcpConnection : ConnectionContext
|
||||
{
|
||||
private volatile bool _aborted;
|
||||
private readonly EndPoint _endPoint;
|
||||
private SocketSender _sender;
|
||||
private SocketReceiver _receiver;
|
||||
|
||||
private Socket _socket;
|
||||
private 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 TcpConnection(EndPoint endPoint)
|
||||
{
|
||||
_endPoint = endPoint;
|
||||
}
|
||||
|
||||
public TcpConnection(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;
|
||||
}
|
||||
|
||||
private 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(TcpConnection)}.{nameof(StartAsync)}: " + ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Complete the output after disposing the socket
|
||||
_application.Input.Complete(sendError);
|
||||
}
|
||||
}
|
||||
private 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);
|
||||
}
|
||||
}
|
||||
|
||||
private 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Exception ConnectionAborted()
|
||||
{
|
||||
return new MqttCommunicationException("Connection Aborted");
|
||||
}
|
||||
|
||||
private 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;
|
||||
}
|
||||
|
||||
private 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,13 +4,12 @@
|
||||
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
|
||||
namespace MQTTnet.AspNetCore
|
||||
namespace MQTTnet.AspNetCore;
|
||||
|
||||
public static class ConnectionBuilderExtensions
|
||||
{
|
||||
public static class ConnectionBuilderExtensions
|
||||
public static IConnectionBuilder UseMqtt(this IConnectionBuilder builder)
|
||||
{
|
||||
public static IConnectionBuilder UseMqtt(this IConnectionBuilder builder)
|
||||
{
|
||||
return builder.UseConnectionHandler<MqttConnectionHandler>();
|
||||
}
|
||||
return builder.UseConnectionHandler<MqttConnectionHandler>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,29 +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 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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
// 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,24 +2,20 @@
|
||||
// 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_OR_GREATER
|
||||
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
|
||||
namespace MQTTnet.AspNetCore
|
||||
{
|
||||
public static class EndpointRouterExtensions
|
||||
{
|
||||
public static void MapMqtt(this IEndpointRouteBuilder endpoints, string pattern)
|
||||
{
|
||||
endpoints.MapConnectionHandler<MqttConnectionHandler>(pattern, options =>
|
||||
{
|
||||
options.WebSockets.SubProtocolSelector = MqttSubProtocolSelector.SelectSubProtocol;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
namespace MQTTnet.AspNetCore;
|
||||
|
||||
#endif
|
||||
public static class EndpointRouterExtensions
|
||||
{
|
||||
public static void MapMqtt(this IEndpointRouteBuilder endpoints, string pattern)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(endpoints);
|
||||
|
||||
endpoints.MapConnectionHandler<MqttConnectionHandler>(pattern, options =>
|
||||
{
|
||||
options.WebSockets.SubProtocolSelector = MqttSubProtocolSelector.SelectSubProtocol;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,66 +1,26 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;netcoreapp2.1;netcoreapp3.1;net5.0;net6.0;net7.0</TargetFrameworks>
|
||||
|
||||
<AssemblyName>MQTTnet.AspNetCore</AssemblyName>
|
||||
<RootNamespace>MQTTnet.AspNetCore</RootNamespace>
|
||||
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
||||
<Company>The contributors of MQTTnet</Company>
|
||||
<Product>MQTTnet</Product>
|
||||
<IsAotCompatible>true</IsAotCompatible>
|
||||
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<Description>MQTTnet is a high performance .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker) and supports v3.1.0, v3.1.1 and v5.0.0 of the MQTT protocol.</Description>
|
||||
<Authors>The contributors of MQTTnet</Authors>
|
||||
<PackageId>MQTTnet.AspNetCore</PackageId>
|
||||
<SignAssembly>false</SignAssembly>
|
||||
<DelaySign>false</DelaySign>
|
||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||
<IncludeSymbols>true</IncludeSymbols>
|
||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||
<Copyright>Copyright (c) .NET Foundation and Contributors</Copyright>
|
||||
<PackageProjectUrl>https://github.com/dotnet/MQTTnet</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/dotnet/MQTTnet.git</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<PackageTags>MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin Blazor</PackageTags>
|
||||
<NeutralLanguage>en-US</NeutralLanguage>
|
||||
<EnableNETAnalyzers>false</EnableNETAnalyzers>
|
||||
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
|
||||
<PackageIcon>nuget.png</PackageIcon>
|
||||
<EmbedUntrackedSources>true</EmbedUntrackedSources>
|
||||
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<PackageReleaseNotes>For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).</PackageReleaseNotes>
|
||||
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<NoWarn>1591;NETSDK1138</NoWarn>
|
||||
<LangVersion>7.3</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="..\..\Images\nuget.png">
|
||||
<Pack>True</Pack>
|
||||
<PackagePath>\</PackagePath>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<DefineConstants>RELEASE;NETSTANDARD2_0</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp3.1' or '$(TargetFramework)' == 'net5.0' or '$(TargetFramework)' == 'net6.0' or '$(TargetFramework)' == 'net7.0' ">
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' != 'netcoreapp3.1' and '$(TargetFramework)' != 'net5.0' and '$(TargetFramework)' != 'net6.0' and '$(TargetFramework)' != 'net7.0' ">
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http.Connections" Version="1.1.0" />
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MQTTnet\MQTTnet.csproj" />
|
||||
<ProjectReference Include="..\MQTTnet.Server\MQTTnet.Server.csproj"/>
|
||||
<ProjectReference Include="..\MQTTnet\MQTTnet.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.Diagnostics.Logger;
|
||||
using MQTTnet.Formatter;
|
||||
|
||||
namespace MQTTnet.AspNetCore;
|
||||
|
||||
public sealed class MqttClientConnectionContextFactory : IMqttClientAdapterFactory
|
||||
{
|
||||
public IMqttChannelAdapter CreateClientAdapter(MqttClientOptions options, MqttPacketInspector packetInspector, IMqttNetLogger logger)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(options);
|
||||
|
||||
switch (options.ChannelOptions)
|
||||
{
|
||||
case MqttClientTcpOptions tcpOptions:
|
||||
{
|
||||
var tcpConnection = new SocketConnection(tcpOptions.RemoteEndpoint);
|
||||
|
||||
var formatter = new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(4096, 65535));
|
||||
return new MqttConnectionContext(formatter, tcpConnection);
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,250 +2,256 @@
|
||||
// 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.IO.Pipelines;
|
||||
using System.Net;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
using Microsoft.AspNetCore.Http.Connections.Features;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.AspNetCore.Client.Tcp;
|
||||
using MQTTnet.Exceptions;
|
||||
using MQTTnet.Formatter;
|
||||
using MQTTnet.Internal;
|
||||
using MQTTnet.Packets;
|
||||
|
||||
namespace MQTTnet.AspNetCore
|
||||
namespace MQTTnet.AspNetCore;
|
||||
|
||||
public sealed class MqttConnectionContext : IMqttChannelAdapter
|
||||
{
|
||||
public sealed class MqttConnectionContext : IMqttChannelAdapter
|
||||
readonly ConnectionContext _connection;
|
||||
readonly AsyncLock _writerLock = new();
|
||||
|
||||
PipeReader _input;
|
||||
PipeWriter _output;
|
||||
|
||||
public MqttConnectionContext(MqttPacketFormatterAdapter packetFormatterAdapter, ConnectionContext connection)
|
||||
{
|
||||
readonly ConnectionContext _connection;
|
||||
readonly AsyncLock _writerLock = new AsyncLock();
|
||||
PacketFormatterAdapter = packetFormatterAdapter ?? throw new ArgumentNullException(nameof(packetFormatterAdapter));
|
||||
_connection = connection ?? throw new ArgumentNullException(nameof(connection));
|
||||
|
||||
PipeReader _input;
|
||||
PipeWriter _output;
|
||||
|
||||
public MqttConnectionContext(MqttPacketFormatterAdapter packetFormatterAdapter, ConnectionContext connection)
|
||||
if (!(_connection is SocketConnection tcp) || tcp.IsConnected)
|
||||
{
|
||||
PacketFormatterAdapter = packetFormatterAdapter ?? throw new ArgumentNullException(nameof(packetFormatterAdapter));
|
||||
_connection = connection ?? throw new ArgumentNullException(nameof(connection));
|
||||
|
||||
if (!(_connection is TcpConnection tcp) || tcp.IsConnected)
|
||||
{
|
||||
_input = connection.Transport.Input;
|
||||
_output = connection.Transport.Output;
|
||||
}
|
||||
_input = connection.Transport.Input;
|
||||
_output = connection.Transport.Output;
|
||||
}
|
||||
}
|
||||
|
||||
public long BytesReceived { get; private set; }
|
||||
public long BytesReceived { get; private set; }
|
||||
|
||||
public long BytesSent { get; private set; }
|
||||
public long BytesSent { get; private set; }
|
||||
|
||||
public X509Certificate2 ClientCertificate
|
||||
public X509Certificate2 ClientCertificate
|
||||
{
|
||||
get
|
||||
{
|
||||
get
|
||||
// mqtt over tcp
|
||||
var tlsFeature = _connection.Features.Get<ITlsConnectionFeature>();
|
||||
if (tlsFeature != null)
|
||||
{
|
||||
// mqtt over tcp
|
||||
var tlsFeature = _connection.Features.Get<ITlsConnectionFeature>();
|
||||
if (tlsFeature != null)
|
||||
{
|
||||
return tlsFeature.ClientCertificate;
|
||||
}
|
||||
|
||||
// mqtt over websocket
|
||||
var httpFeature = _connection.Features.Get<IHttpContextFeature>();
|
||||
return httpFeature?.HttpContext?.Connection.ClientCertificate;
|
||||
return tlsFeature.ClientCertificate;
|
||||
}
|
||||
|
||||
// mqtt over websocket
|
||||
var httpFeature = _connection.Features.Get<IHttpContextFeature>();
|
||||
return httpFeature?.HttpContext?.Connection.ClientCertificate;
|
||||
}
|
||||
}
|
||||
|
||||
public string Endpoint
|
||||
public bool IsSecureConnection
|
||||
{
|
||||
get
|
||||
{
|
||||
get
|
||||
// mqtt over tcp
|
||||
var tlsFeature = _connection.Features.Get<ITlsConnectionFeature>();
|
||||
if (tlsFeature != null)
|
||||
{
|
||||
// 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;
|
||||
return true;
|
||||
}
|
||||
|
||||
// mqtt over websocket
|
||||
var httpFeature = _connection.Features.Get<IHttpContextFeature>();
|
||||
if (httpFeature?.HttpContext != null)
|
||||
{
|
||||
return httpFeature.HttpContext.Request.IsHttps;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsReadingPacket { get; private set; }
|
||||
|
||||
public bool IsSecureConnection
|
||||
public EndPoint LocalEndPoint
|
||||
{
|
||||
get
|
||||
{
|
||||
get
|
||||
// mqtt over tcp
|
||||
if (_connection.LocalEndPoint != null)
|
||||
{
|
||||
// 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 TcpConnection tcp && !tcp.IsConnected)
|
||||
{
|
||||
await tcp.StartAsync().ConfigureAwait(false);
|
||||
return _connection.LocalEndPoint;
|
||||
}
|
||||
|
||||
_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
|
||||
// mqtt over websocket
|
||||
var httpFeature = _connection.Features.Get<IHttpConnectionFeature>();
|
||||
if (httpFeature?.LocalIpAddress != null)
|
||||
{
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
ReadResult readResult;
|
||||
var readTask = _input.ReadAsync(cancellationToken);
|
||||
if (readTask.IsCompleted)
|
||||
{
|
||||
readResult = readTask.Result;
|
||||
}
|
||||
else
|
||||
{
|
||||
readResult = await readTask.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
var buffer = readResult.Buffer;
|
||||
|
||||
var consumed = buffer.Start;
|
||||
var observed = buffer.Start;
|
||||
|
||||
try
|
||||
{
|
||||
if (!buffer.IsEmpty)
|
||||
{
|
||||
if (PacketFormatterAdapter.TryDecode(buffer, out var packet, out consumed, out observed, out var received))
|
||||
{
|
||||
BytesReceived += received;
|
||||
return packet;
|
||||
}
|
||||
else
|
||||
{
|
||||
// we did receive something but the message is not yet complete
|
||||
IsReadingPacket = true;
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsReadingPacket = false;
|
||||
return new IPEndPoint(httpFeature.LocalIpAddress, httpFeature.LocalPort);
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void ResetStatistics()
|
||||
public MqttPacketFormatterAdapter PacketFormatterAdapter { get; }
|
||||
|
||||
public EndPoint RemoteEndPoint
|
||||
{
|
||||
get
|
||||
{
|
||||
BytesReceived = 0;
|
||||
BytesSent = 0;
|
||||
// mqtt over tcp
|
||||
if (_connection.RemoteEndPoint != null)
|
||||
{
|
||||
return _connection.RemoteEndPoint;
|
||||
}
|
||||
|
||||
// mqtt over websocket
|
||||
var httpFeature = _connection.Features.Get<IHttpConnectionFeature>();
|
||||
if (httpFeature?.RemoteIpAddress != null)
|
||||
{
|
||||
return new IPEndPoint(httpFeature.RemoteIpAddress, httpFeature.RemotePort);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ConnectAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (_connection is SocketConnection tcp && !tcp.IsConnected)
|
||||
{
|
||||
await tcp.StartAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task SendPacketAsync(MqttPacket packet, CancellationToken cancellationToken)
|
||||
_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
|
||||
{
|
||||
using (await _writerLock.EnterAsync(cancellationToken).ConfigureAwait(false))
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
ReadResult readResult;
|
||||
var readTask = _input.ReadAsync(cancellationToken);
|
||||
if (readTask.IsCompleted)
|
||||
{
|
||||
readResult = readTask.Result;
|
||||
}
|
||||
else
|
||||
{
|
||||
readResult = await readTask.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
var buffer = readResult.Buffer;
|
||||
|
||||
var consumed = buffer.Start;
|
||||
var observed = buffer.Start;
|
||||
|
||||
try
|
||||
{
|
||||
var buffer = PacketFormatterAdapter.Encode(packet);
|
||||
|
||||
if (buffer.Payload.Count == 0)
|
||||
if (!buffer.IsEmpty)
|
||||
{
|
||||
// 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);
|
||||
if (PacketFormatterAdapter.TryDecode(buffer, out var packet, out consumed, out observed, out var received))
|
||||
{
|
||||
BytesReceived += received;
|
||||
return packet;
|
||||
}
|
||||
}
|
||||
else
|
||||
else if (readResult.IsCompleted)
|
||||
{
|
||||
WritePacketBuffer(_output, buffer);
|
||||
await _output.FlushAsync(cancellationToken).ConfigureAwait(false);
|
||||
throw new MqttCommunicationException("Connection Aborted");
|
||||
}
|
||||
|
||||
BytesSent += buffer.Length;
|
||||
}
|
||||
finally
|
||||
{
|
||||
PacketFormatterAdapter.Cleanup();
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void WritePacketBuffer(PipeWriter output, MqttPacketBuffer buffer)
|
||||
catch (Exception exception)
|
||||
{
|
||||
// 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
|
||||
// completing the channel makes sure that there is no more data read after a protocol error
|
||||
_input?.Complete(exception);
|
||||
_output?.Complete(exception);
|
||||
|
||||
var span = output.GetSpan(buffer.Length);
|
||||
throw;
|
||||
}
|
||||
|
||||
buffer.Packet.AsSpan().CopyTo(span);
|
||||
buffer.Payload.AsSpan().CopyTo(span.Slice(buffer.Packet.Count));
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
return null;
|
||||
}
|
||||
|
||||
output.Advance(buffer.Length);
|
||||
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.Length == 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)
|
||||
{
|
||||
// copy MqttPacketBuffer's Packet and Payload to the same buffer block of PipeWriter
|
||||
// MqttPacket will be transmitted within the bounds of a WebSocket frame after PipeWriter.FlushAsync
|
||||
|
||||
var span = output.GetSpan(buffer.Length);
|
||||
|
||||
buffer.Packet.AsSpan().CopyTo(span);
|
||||
var offset = buffer.Packet.Count;
|
||||
buffer.Payload.CopyTo(span.Slice(offset));
|
||||
output.Advance(buffer.Length);
|
||||
}
|
||||
}
|
||||
@@ -5,54 +5,49 @@
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
using Microsoft.AspNetCore.Connections.Features;
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.Server;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Diagnostics.Logger;
|
||||
using MQTTnet.Formatter;
|
||||
using MQTTnet.Server;
|
||||
|
||||
namespace MQTTnet.AspNetCore
|
||||
namespace MQTTnet.AspNetCore;
|
||||
|
||||
public sealed class MqttConnectionHandler : ConnectionHandler, IMqttServerAdapter
|
||||
{
|
||||
public sealed class MqttConnectionHandler : ConnectionHandler, IMqttServerAdapter
|
||||
MqttServerOptions _serverOptions;
|
||||
|
||||
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)
|
||||
{
|
||||
// required for websocket transport to work
|
||||
var transferFormatFeature = connection.Features.Get<ITransferFormatFeature>();
|
||||
if (transferFormatFeature != null)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
transferFormatFeature.ActiveFormat = TransferFormat.Binary;
|
||||
}
|
||||
|
||||
public Task StartAsync(MqttServerOptions options, IMqttNetLogger logger)
|
||||
{
|
||||
_serverOptions = options;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task StopAsync()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
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)
|
||||
{
|
||||
_serverOptions = options;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task StopAsync()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -2,32 +2,43 @@
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Diagnostics.Logger;
|
||||
using MQTTnet.Server;
|
||||
|
||||
namespace MQTTnet.AspNetCore
|
||||
namespace MQTTnet.AspNetCore;
|
||||
|
||||
public sealed class MqttHostedServer : MqttServer, IHostedService
|
||||
{
|
||||
public sealed class MqttHostedServer : MqttServer, IHostedService
|
||||
readonly IHostApplicationLifetime _hostApplicationLifetime;
|
||||
readonly MqttServerFactory _mqttFactory;
|
||||
|
||||
public MqttHostedServer(
|
||||
IHostApplicationLifetime hostApplicationLifetime,
|
||||
MqttServerFactory mqttFactory,
|
||||
MqttServerOptions options,
|
||||
IEnumerable<IMqttServerAdapter> adapters,
|
||||
IMqttNetLogger logger) : base(options, adapters, logger)
|
||||
{
|
||||
public MqttHostedServer(MqttServerOptions options, IEnumerable<IMqttServerAdapter> adapters, IMqttNetLogger logger)
|
||||
: base(options, adapters, logger)
|
||||
{
|
||||
}
|
||||
_mqttFactory = mqttFactory ?? throw new ArgumentNullException(nameof(mqttFactory));
|
||||
_hostApplicationLifetime = hostApplicationLifetime;
|
||||
}
|
||||
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_ = StartAsync();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
public async Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
// The yield makes sure that the hosted service is considered up and running.
|
||||
await Task.Yield();
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return StopAsync();
|
||||
}
|
||||
_hostApplicationLifetime.ApplicationStarted.Register(OnStarted);
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return StopAsync(_mqttFactory.CreateMqttServerStopOptionsBuilder().Build());
|
||||
}
|
||||
|
||||
void OnStarted()
|
||||
{
|
||||
_ = StartAsync();
|
||||
}
|
||||
}
|
||||
@@ -2,36 +2,30 @@
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace MQTTnet.AspNetCore
|
||||
namespace MQTTnet.AspNetCore;
|
||||
|
||||
public static class MqttSubProtocolSelector
|
||||
{
|
||||
public static class MqttSubProtocolSelector
|
||||
public static string SelectSubProtocol(HttpRequest request)
|
||||
{
|
||||
public static string SelectSubProtocol(HttpRequest request)
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
|
||||
string subProtocol = null;
|
||||
if (request.Headers.TryGetValue("Sec-WebSocket-Protocol", out var 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;
|
||||
subProtocol = SelectSubProtocol(requestedSubProtocolValues);
|
||||
}
|
||||
|
||||
public static string SelectSubProtocol(IList<string> requestedSubProtocolValues)
|
||||
{
|
||||
if (requestedSubProtocolValues == null) throw new ArgumentNullException(nameof(requestedSubProtocolValues));
|
||||
|
||||
// Order the protocols to also match "mqtt", "mqttv-3.1", "mqttv-3.11" etc.
|
||||
return requestedSubProtocolValues
|
||||
.OrderByDescending(p => p.Length)
|
||||
.FirstOrDefault(p => p.ToLower().StartsWith("mqtt"));
|
||||
}
|
||||
return subProtocol;
|
||||
}
|
||||
}
|
||||
|
||||
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.ToLowerInvariant().StartsWith("mqtt", StringComparison.InvariantCulture));
|
||||
}
|
||||
}
|
||||
@@ -2,69 +2,63 @@
|
||||
// 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.Net;
|
||||
using System.Net.WebSockets;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Diagnostics.Logger;
|
||||
using MQTTnet.Formatter;
|
||||
using MQTTnet.Implementations;
|
||||
using MQTTnet.Server;
|
||||
|
||||
namespace MQTTnet.AspNetCore
|
||||
namespace MQTTnet.AspNetCore;
|
||||
|
||||
public sealed class MqttWebSocketServerAdapter : IMqttServerAdapter
|
||||
{
|
||||
public sealed class MqttWebSocketServerAdapter : IMqttServerAdapter
|
||||
IMqttNetLogger _logger = MqttNetNullLogger.Instance;
|
||||
|
||||
public Func<IMqttChannelAdapter, Task> ClientHandler { get; set; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
IMqttNetLogger _logger = MqttNetNullLogger.Instance;
|
||||
}
|
||||
|
||||
public Func<IMqttChannelAdapter, Task> ClientHandler { get; set; }
|
||||
public async Task RunWebSocketConnectionAsync(WebSocket webSocket, HttpContext httpContext)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(webSocket);
|
||||
|
||||
public void Dispose()
|
||||
var remoteAddress = httpContext.Connection.RemoteIpAddress;
|
||||
var remoteEndPoint = remoteAddress == null ? null : new IPEndPoint(remoteAddress, httpContext.Connection.RemotePort);
|
||||
|
||||
var clientCertificate = await httpContext.Connection.GetClientCertificateAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
}
|
||||
var isSecureConnection = clientCertificate != null;
|
||||
|
||||
public async Task RunWebSocketConnectionAsync(WebSocket webSocket, HttpContext httpContext)
|
||||
{
|
||||
if (webSocket == null)
|
||||
var clientHandler = ClientHandler;
|
||||
if (clientHandler != null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(webSocket));
|
||||
}
|
||||
var formatter = new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535));
|
||||
var channel = new MqttWebSocketChannel(webSocket, remoteEndPoint, isSecureConnection, clientCertificate);
|
||||
|
||||
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)
|
||||
{
|
||||
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();
|
||||
using var channelAdapter = new MqttChannelAdapter(channel, formatter, _logger);
|
||||
await clientHandler(channelAdapter).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
public Task StartAsync(MqttServerOptions options, IMqttNetLogger logger)
|
||||
finally
|
||||
{
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task StopAsync()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
clientCertificate?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public Task StartAsync(MqttServerOptions options, IMqttNetLogger logger)
|
||||
{
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task StopAsync()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -2,139 +2,133 @@
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Buffers;
|
||||
using System.Runtime.InteropServices;
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.Exceptions;
|
||||
using MQTTnet.Formatter;
|
||||
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 class ReaderExtensions
|
||||
public static bool TryDecode(
|
||||
this MqttPacketFormatterAdapter formatter,
|
||||
in ReadOnlySequence<byte> input,
|
||||
out MqttPacket packet,
|
||||
out SequencePosition consumed,
|
||||
out SequencePosition observed,
|
||||
out int bytesRead)
|
||||
{
|
||||
public static bool TryDecode(this MqttPacketFormatterAdapter formatter,
|
||||
in ReadOnlySequence<byte> input,
|
||||
out MqttPacket packet,
|
||||
out SequencePosition consumed,
|
||||
out SequencePosition observed,
|
||||
out int bytesRead)
|
||||
ArgumentNullException.ThrowIfNull(formatter);
|
||||
|
||||
packet = null;
|
||||
consumed = input.Start;
|
||||
observed = input.End;
|
||||
bytesRead = 0;
|
||||
var copy = input;
|
||||
|
||||
if (copy.Length < 2)
|
||||
{
|
||||
if (formatter == null) throw new ArgumentNullException(nameof(formatter));
|
||||
|
||||
packet = null;
|
||||
consumed = input.Start;
|
||||
observed = input.End;
|
||||
bytesRead = 0;
|
||||
var copy = input;
|
||||
|
||||
if (copy.Length < 2)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TryReadBodyLength(ref copy, out var headerLength, out var bodyLength))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var fixedHeader = copy.First.Span[0];
|
||||
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;
|
||||
return false;
|
||||
}
|
||||
|
||||
static ArraySegment<byte> GetArraySegment(ref ReadOnlySequence<byte> input)
|
||||
if (!TryReadBodyLength(ref copy, out var headerLength, out var bodyLength))
|
||||
{
|
||||
if (input.IsSingleSegment && MemoryMarshal.TryGetArray(input.First, out var segment))
|
||||
{
|
||||
return segment;
|
||||
}
|
||||
|
||||
// Should be rare
|
||||
var array = input.ToArray();
|
||||
return new ArraySegment<byte>(array);
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool TryReadBodyLength(ref ReadOnlySequence<byte> input, out int headerLength, out int bodyLength)
|
||||
var fixedHeader = copy.First.Span[0];
|
||||
copy = copy.Slice(headerLength);
|
||||
if (copy.Length < 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);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool TryReadBodyLength(ReadOnlySpan<byte> span, out int headerLength, out int bodyLength)
|
||||
var bodySlice = copy.Slice(0, bodyLength);
|
||||
var bodySegment = GetArraySegment(ref bodySlice);
|
||||
|
||||
var receivedMqttPacket = new ReceivedMqttPacket(fixedHeader, bodySegment, headerLength + bodyLength);
|
||||
if (formatter.ProtocolVersion == MqttProtocolVersion.Unknown)
|
||||
{
|
||||
// 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;
|
||||
formatter.DetectProtocolVersion(receivedMqttPacket);
|
||||
}
|
||||
|
||||
packet = formatter.Decode(receivedMqttPacket);
|
||||
consumed = bodySlice.End;
|
||||
observed = bodySlice.End;
|
||||
bytesRead = headerLength + bodyLength;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void ThrowProtocolViolationException(ReadOnlySpan<byte> valueSpan, int index)
|
||||
static ArraySegment<byte> GetArraySegment(ref ReadOnlySequence<byte> input)
|
||||
{
|
||||
if (input.IsSingleSegment && MemoryMarshal.TryGetArray(input.First, out var segment))
|
||||
{
|
||||
throw new MqttProtocolViolationException($"Remaining length is invalid (Data={string.Join(",", valueSpan.Slice(1, index).ToArray())}).");
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -2,104 +2,107 @@
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Implementations;
|
||||
using MQTTnet.Diagnostics.Logger;
|
||||
using MQTTnet.Server;
|
||||
using MQTTnet.Server.Internal.Adapter;
|
||||
|
||||
namespace MQTTnet.AspNetCore
|
||||
namespace MQTTnet.AspNetCore;
|
||||
|
||||
public static class ServiceCollectionExtensions
|
||||
{
|
||||
public static class ServiceCollectionExtensions
|
||||
public static IServiceCollection AddHostedMqttServer(this IServiceCollection services, MqttServerOptions options)
|
||||
{
|
||||
public static IServiceCollection AddMqttServer(this IServiceCollection serviceCollection, Action<MqttServerOptionsBuilder> configure = null)
|
||||
{
|
||||
if (serviceCollection is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(serviceCollection));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(services);
|
||||
ArgumentNullException.ThrowIfNull(options);
|
||||
|
||||
serviceCollection.AddMqttConnectionHandler();
|
||||
serviceCollection.AddHostedMqttServer(configure);
|
||||
|
||||
return serviceCollection;
|
||||
}
|
||||
services.AddSingleton(options);
|
||||
services.AddHostedMqttServer();
|
||||
|
||||
public static IServiceCollection AddHostedMqttServer(this IServiceCollection services, MqttServerOptions options)
|
||||
{
|
||||
if (options == null) throw new ArgumentNullException(nameof(options));
|
||||
return services;
|
||||
}
|
||||
|
||||
services.AddSingleton(options);
|
||||
public static IServiceCollection AddHostedMqttServer(this IServiceCollection services, Action<MqttServerOptionsBuilder> configure)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(services);
|
||||
|
||||
services.AddHostedMqttServer();
|
||||
var serverOptionsBuilder = new MqttServerOptionsBuilder();
|
||||
|
||||
return services;
|
||||
}
|
||||
configure?.Invoke(serverOptionsBuilder);
|
||||
|
||||
public static IServiceCollection AddHostedMqttServer(this IServiceCollection services, Action<MqttServerOptionsBuilder> configure = null)
|
||||
{
|
||||
services.AddSingleton(s =>
|
||||
{
|
||||
var serverOptionsBuilder = new MqttServerOptionsBuilder();
|
||||
configure?.Invoke(serverOptionsBuilder);
|
||||
return serverOptionsBuilder.Build();
|
||||
});
|
||||
var options = serverOptionsBuilder.Build();
|
||||
|
||||
services.AddHostedMqttServer();
|
||||
return AddHostedMqttServer(services, options);
|
||||
}
|
||||
|
||||
return services;
|
||||
}
|
||||
public static void AddHostedMqttServer(this IServiceCollection services)
|
||||
{
|
||||
// The user may have these services already registered.
|
||||
services.TryAddSingleton<IMqttNetLogger>(MqttNetNullLogger.Instance);
|
||||
services.TryAddSingleton(new MqttServerFactory());
|
||||
|
||||
public static IServiceCollection AddHostedMqttServerWithServices(this IServiceCollection services, Action<AspNetMqttServerOptionsBuilder> configure)
|
||||
{
|
||||
services.AddSingleton(s =>
|
||||
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);
|
||||
configure(builder);
|
||||
return builder.Build();
|
||||
});
|
||||
|
||||
services.AddHostedMqttServer();
|
||||
services.AddHostedMqttServer();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
static IServiceCollection AddHostedMqttServer(this IServiceCollection services)
|
||||
{
|
||||
var logger = new MqttNetEventLogger();
|
||||
|
||||
services.AddSingleton<IMqttNetLogger>(logger);
|
||||
services.AddSingleton<MqttHostedServer>();
|
||||
services.AddSingleton<IHostedService>(s => s.GetService<MqttHostedServer>());
|
||||
services.AddSingleton<MqttServer>(s => s.GetService<MqttHostedServer>());
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddMqttWebSocketServerAdapter(this IServiceCollection services)
|
||||
{
|
||||
services.AddSingleton<MqttWebSocketServerAdapter>();
|
||||
services.AddSingleton<IMqttServerAdapter>(s => s.GetService<MqttWebSocketServerAdapter>());
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddMqttTcpServerAdapter(this IServiceCollection services)
|
||||
{
|
||||
services.AddSingleton<MqttTcpServerAdapter>();
|
||||
services.AddSingleton<IMqttServerAdapter>(s => s.GetService<MqttTcpServerAdapter>());
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddMqttConnectionHandler(this IServiceCollection services)
|
||||
{
|
||||
services.AddSingleton<MqttConnectionHandler>();
|
||||
services.AddSingleton<IMqttServerAdapter>(s => s.GetService<MqttConnectionHandler>());
|
||||
|
||||
return services;
|
||||
}
|
||||
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)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(services);
|
||||
|
||||
services.AddSingleton(logger);
|
||||
}
|
||||
|
||||
public static IServiceCollection AddMqttServer(this IServiceCollection serviceCollection, Action<MqttServerOptionsBuilder> configure = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
// 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.Diagnostics;
|
||||
using System.IO.Pipelines;
|
||||
using System.Net.Sockets;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace MQTTnet.AspNetCore;
|
||||
|
||||
public class SocketAwaitable(PipeScheduler ioScheduler) : ICriticalNotifyCompletion
|
||||
{
|
||||
static readonly Action CallbackCompleted = () =>
|
||||
{
|
||||
};
|
||||
|
||||
int _bytesTransferred;
|
||||
|
||||
Action _callback;
|
||||
SocketError _error;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,263 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.IO.Pipelines;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using 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;
|
||||
}
|
||||
|
||||
static MqttCommunicationException 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 is SocketError.OperationAborted or SocketError.ConnectionAborted or SocketError.Interrupted or 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 ??= ConnectionAborted();
|
||||
}
|
||||
|
||||
await _application.Output.CompleteAsync(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();
|
||||
|
||||
FlushResult result;
|
||||
if (!flushTask.IsCompleted)
|
||||
{
|
||||
result = await flushTask;
|
||||
}
|
||||
else
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.IO.Pipelines;
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace MQTTnet.AspNetCore;
|
||||
|
||||
public sealed class SocketReceiver : IDisposable
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_eventArgs.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Buffers;
|
||||
using System.Diagnostics;
|
||||
using System.IO.Pipelines;
|
||||
using System.Net.Sockets;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace MQTTnet.AspNetCore;
|
||||
|
||||
public sealed class SocketSender : IDisposable
|
||||
{
|
||||
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 void Dispose()
|
||||
{
|
||||
_eventArgs.Dispose();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
[assembly: DoNotParallelize]
|
||||
@@ -2,64 +2,58 @@
|
||||
// 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;
|
||||
using System.Threading.Tasks;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using MQTTnet.Internal;
|
||||
|
||||
namespace MQTTnet.Benchmarks
|
||||
namespace MQTTnet.Benchmarks;
|
||||
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[MemoryDiagnoser]
|
||||
public class AsyncLockBenchmark : BaseBenchmark
|
||||
{
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[MemoryDiagnoser]
|
||||
public class AsyncLockBenchmark : BaseBenchmark
|
||||
[Benchmark]
|
||||
public async Task Synchronize_100_Tasks()
|
||||
{
|
||||
[Benchmark]
|
||||
public async Task Synchronize_100_Tasks()
|
||||
const int tasksCount = 100;
|
||||
|
||||
var tasks = new Task[tasksCount];
|
||||
var asyncLock = new AsyncLock();
|
||||
var globalI = 0;
|
||||
|
||||
for (var i = 0; i < tasksCount; i++)
|
||||
{
|
||||
const int tasksCount = 100;
|
||||
|
||||
var tasks = new Task[tasksCount];
|
||||
var asyncLock = new AsyncLock();
|
||||
var globalI = 0;
|
||||
|
||||
for (var i = 0; i < tasksCount; i++)
|
||||
{
|
||||
tasks[i] = Task.Run(
|
||||
async () =>
|
||||
{
|
||||
using (await asyncLock.EnterAsync().ConfigureAwait(false))
|
||||
{
|
||||
var localI = globalI;
|
||||
await Task.Delay(5).ConfigureAwait(false); // Increase the chance for wrong data.
|
||||
localI++;
|
||||
globalI = localI;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
|
||||
if (globalI != tasksCount)
|
||||
{
|
||||
throw new Exception($"Code is broken ({globalI})!");
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public async Task Wait_100_000_Times()
|
||||
{
|
||||
var asyncLock = new AsyncLock();
|
||||
|
||||
using (var cancellationToken = new CancellationTokenSource())
|
||||
{
|
||||
for (var i = 0; i < 100000; i++)
|
||||
tasks[i] = Task.Run(
|
||||
async () =>
|
||||
{
|
||||
using (await asyncLock.EnterAsync(cancellationToken.Token).ConfigureAwait(false))
|
||||
using (await asyncLock.EnterAsync().ConfigureAwait(false))
|
||||
{
|
||||
var localI = globalI;
|
||||
await Task.Delay(5).ConfigureAwait(false); // Increase the chance for wrong data.
|
||||
localI++;
|
||||
globalI = localI;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
|
||||
if (globalI != tasksCount)
|
||||
{
|
||||
throw new InvalidOperationException($"Code is broken ({globalI})!");
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public async Task Wait_100_000_Times()
|
||||
{
|
||||
var asyncLock = new AsyncLock();
|
||||
|
||||
using var cancellationToken = new CancellationTokenSource();
|
||||
for (var i = 0; i < 100000; i++)
|
||||
{
|
||||
using (await asyncLock.EnterAsync(cancellationToken.Token).ConfigureAwait(false))
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,8 @@
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace MQTTnet.Benchmarks
|
||||
namespace MQTTnet.Benchmarks;
|
||||
|
||||
public abstract class BaseBenchmark
|
||||
{
|
||||
public abstract class BaseBenchmark
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -2,89 +2,91 @@
|
||||
// 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;
|
||||
using System.Threading;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Diagnostics.Logger;
|
||||
using MQTTnet.Formatter;
|
||||
using MQTTnet.Packets;
|
||||
using MQTTnet.Tests.Mockups;
|
||||
|
||||
namespace MQTTnet.Benchmarks
|
||||
namespace MQTTnet.Benchmarks;
|
||||
|
||||
[MemoryDiagnoser]
|
||||
public sealed class ChannelAdapterBenchmark : BaseBenchmark, IDisposable
|
||||
{
|
||||
[MemoryDiagnoser]
|
||||
public sealed class ChannelAdapterBenchmark : BaseBenchmark
|
||||
MqttChannelAdapter _channelAdapter;
|
||||
int _iterations;
|
||||
MqttPublishPacket _packet;
|
||||
MemoryStream _stream;
|
||||
|
||||
[Benchmark]
|
||||
public void Receive_10000_Messages()
|
||||
{
|
||||
MqttChannelAdapter _channelAdapter;
|
||||
int _iterations;
|
||||
MqttPublishPacket _packet;
|
||||
MemoryStream _stream;
|
||||
_stream.Position = 0;
|
||||
|
||||
[Benchmark]
|
||||
public void Receive_10000_Messages()
|
||||
for (var i = 0; i < 10000; i++)
|
||||
{
|
||||
_stream.Position = 0;
|
||||
|
||||
for (var i = 0; i < 10000; i++)
|
||||
{
|
||||
_channelAdapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
_stream.Position = 0;
|
||||
_channelAdapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Send_10000_Messages()
|
||||
_stream.Position = 0;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Send_10000_Messages()
|
||||
{
|
||||
_stream.Position = 0;
|
||||
|
||||
for (var i = 0; i < 10000; i++)
|
||||
{
|
||||
_stream.Position = 0;
|
||||
|
||||
for (var i = 0; i < 10000; i++)
|
||||
{
|
||||
_channelAdapter.SendPacketAsync(_packet, CancellationToken.None).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
_stream.Position = 0;
|
||||
_channelAdapter.SendPacketAsync(_packet, CancellationToken.None).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
_stream.Position = 0;
|
||||
}
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
_packet = new MqttPublishPacket
|
||||
{
|
||||
_packet = new MqttPublishPacket
|
||||
{
|
||||
Topic = "A"
|
||||
};
|
||||
Topic = "A"
|
||||
};
|
||||
|
||||
var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535));
|
||||
var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535));
|
||||
|
||||
var serializedPacket = Join(serializer.Encode(_packet).Join());
|
||||
var serializedPacket = Join(serializer.Encode(_packet).Join());
|
||||
|
||||
_iterations = 10000;
|
||||
_iterations = 10000;
|
||||
|
||||
_stream = new MemoryStream(_iterations * serializedPacket.Length);
|
||||
_stream = new MemoryStream(_iterations * serializedPacket.Length);
|
||||
|
||||
for (var i = 0; i < _iterations; i++)
|
||||
{
|
||||
_stream.Write(serializedPacket, 0, serializedPacket.Length);
|
||||
}
|
||||
|
||||
_stream.Position = 0;
|
||||
|
||||
var channel = new MemoryMqttChannel(_stream);
|
||||
|
||||
_channelAdapter = new MqttChannelAdapter(channel, serializer, new MqttNetEventLogger());
|
||||
for (var i = 0; i < _iterations; i++)
|
||||
{
|
||||
_stream.Write(serializedPacket, 0, serializedPacket.Length);
|
||||
}
|
||||
|
||||
static byte[] Join(params ArraySegment<byte>[] chunks)
|
||||
{
|
||||
var buffer = new MemoryStream();
|
||||
foreach (var chunk in chunks)
|
||||
{
|
||||
buffer.Write(chunk.Array, chunk.Offset, chunk.Count);
|
||||
}
|
||||
_stream.Position = 0;
|
||||
|
||||
return buffer.ToArray();
|
||||
var channel = new MemoryMqttChannel(_stream);
|
||||
|
||||
_channelAdapter = new MqttChannelAdapter(channel, serializer, new MqttNetEventLogger());
|
||||
}
|
||||
|
||||
static byte[] Join(params ArraySegment<byte>[] chunks)
|
||||
{
|
||||
var buffer = new MemoryStream();
|
||||
foreach (var chunk in chunks)
|
||||
{
|
||||
buffer.Write(chunk.Array, chunk.Offset, chunk.Count);
|
||||
}
|
||||
|
||||
return buffer.ToArray();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_channelAdapter?.Dispose();
|
||||
_stream?.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -2,90 +2,90 @@
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Diagnostics;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Diagnostics.Logger;
|
||||
|
||||
namespace MQTTnet.Benchmarks
|
||||
namespace MQTTnet.Benchmarks;
|
||||
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[RPlotExporter]
|
||||
[MemoryDiagnoser]
|
||||
public class LoggerBenchmark : BaseBenchmark
|
||||
{
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[RPlotExporter]
|
||||
[MemoryDiagnoser]
|
||||
public class LoggerBenchmark : BaseBenchmark
|
||||
MqttNetNullLogger _nullLogger;
|
||||
MqttNetSourceLogger _sourceNullLogger;
|
||||
|
||||
MqttNetEventLogger _eventLogger;
|
||||
MqttNetSourceLogger _sourceEventLogger;
|
||||
|
||||
MqttNetEventLogger _eventLoggerNoListener;
|
||||
MqttNetSourceLogger _sourceEventLoggerNoListener;
|
||||
|
||||
bool _useHandler;
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
MqttNetNullLogger _nullLogger;
|
||||
MqttNetSourceLogger _sourceNullLogger;
|
||||
|
||||
MqttNetEventLogger _eventLogger;
|
||||
MqttNetSourceLogger _sourceEventLogger;
|
||||
|
||||
MqttNetEventLogger _eventLoggerNoListener;
|
||||
MqttNetSourceLogger _sourceEventLoggerNoListener;
|
||||
|
||||
bool _useHandler;
|
||||
_nullLogger = new MqttNetNullLogger();
|
||||
_sourceNullLogger = _nullLogger.WithSource("Source");
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
_nullLogger = new MqttNetNullLogger();
|
||||
_sourceNullLogger = _nullLogger.WithSource("Source");
|
||||
|
||||
_eventLogger = new MqttNetEventLogger();
|
||||
_eventLogger.LogMessagePublished += OnLogMessagePublished;
|
||||
_sourceEventLogger = _eventLogger.WithSource("Source");
|
||||
|
||||
_eventLoggerNoListener = new MqttNetEventLogger();
|
||||
_sourceEventLoggerNoListener = _eventLoggerNoListener.WithSource("Source");
|
||||
}
|
||||
_eventLogger = new MqttNetEventLogger();
|
||||
_eventLogger.LogMessagePublished += OnLogMessagePublished;
|
||||
_sourceEventLogger = _eventLogger.WithSource("Source");
|
||||
|
||||
void OnLogMessagePublished(object sender, MqttNetLogMessagePublishedEventArgs eventArgs)
|
||||
{
|
||||
if (_useHandler)
|
||||
{
|
||||
eventArgs.LogMessage.ToString();
|
||||
}
|
||||
}
|
||||
_eventLoggerNoListener = new MqttNetEventLogger();
|
||||
_sourceEventLoggerNoListener = _eventLoggerNoListener.WithSource("Source");
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Log_10000_Messages_No_Listener()
|
||||
void OnLogMessagePublished(object sender, MqttNetLogMessagePublishedEventArgs eventArgs)
|
||||
{
|
||||
if (_useHandler)
|
||||
{
|
||||
_useHandler = false;
|
||||
|
||||
for (var i = 0; i < 10000; i++)
|
||||
{
|
||||
_sourceEventLoggerNoListener.Verbose("test log message {0}", "parameter");
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Log_10000_Messages_With_To_String()
|
||||
{
|
||||
_useHandler = true;
|
||||
|
||||
for (var i = 0; i < 10000; i++)
|
||||
{
|
||||
_sourceEventLogger.Verbose("test log message {0}", "parameter");
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Log_10000_Messages_Without_To_String()
|
||||
{
|
||||
_useHandler = false;
|
||||
|
||||
for (var i = 0; i < 10000; i++)
|
||||
{
|
||||
_sourceEventLogger.Verbose("test log message {0}", "parameter");
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Log_10000_Messages_With_NullLogger()
|
||||
{
|
||||
for (var i = 0; i < 10000; i++)
|
||||
{
|
||||
_sourceNullLogger.Verbose("test log message {0}", "parameter");
|
||||
}
|
||||
Debug.WriteLine(eventArgs.LogMessage.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Log_10000_Messages_No_Listener()
|
||||
{
|
||||
_useHandler = false;
|
||||
|
||||
for (var i = 0; i < 10000; i++)
|
||||
{
|
||||
_sourceEventLoggerNoListener.Verbose("test log message {0}", "parameter");
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Log_10000_Messages_With_To_String()
|
||||
{
|
||||
_useHandler = true;
|
||||
|
||||
for (var i = 0; i < 10000; i++)
|
||||
{
|
||||
_sourceEventLogger.Verbose("test log message {0}", "parameter");
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Log_10000_Messages_Without_To_String()
|
||||
{
|
||||
_useHandler = false;
|
||||
|
||||
for (var i = 0; i < 10000; i++)
|
||||
{
|
||||
_sourceEventLogger.Verbose("test log message {0}", "parameter");
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Log_10000_Messages_With_NullLogger()
|
||||
{
|
||||
for (var i = 0; i < 10000; i++)
|
||||
{
|
||||
_sourceNullLogger.Verbose("test log message {0}", "parameter");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,38 +1,22 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<OutputType>Exe</OutputType>
|
||||
<DebugType>Full</DebugType>
|
||||
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
|
||||
<LangVersion>7.2</LangVersion>
|
||||
<IsPackable>false</IsPackable>
|
||||
<EnableNETAnalyzers>false</EnableNETAnalyzers>
|
||||
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
|
||||
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
||||
<NoWarn>1591;NETSDK1138</NoWarn>
|
||||
<NoWarn>$(NoWarn);CS8892;CA1707;CA1051;CA1822</NoWarn>
|
||||
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<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" />
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.15.4" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj" />
|
||||
<ProjectReference Include="..\MQTTnet.Tests\MQTTnet.Tests.csproj" />
|
||||
<ProjectReference Include="..\..\Source\MQTTnet.Server\MQTTnet.Server.csproj"/>
|
||||
<ProjectReference Include="..\..\Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj"/>
|
||||
<ProjectReference Include="..\MQTTnet.Tests\MQTTnet.Tests.csproj"/>
|
||||
<ProjectReference Include="..\MQTTnet\MQTTnet.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,40 +1,39 @@
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using System;
|
||||
|
||||
|
||||
namespace MQTTnet.Benchmarks
|
||||
namespace MQTTnet.Benchmarks;
|
||||
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[RPlotExporter, RankColumn]
|
||||
[MemoryDiagnoser]
|
||||
public class MemoryCopyBenchmark
|
||||
{
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[RPlotExporter, RankColumn]
|
||||
[MemoryDiagnoser]
|
||||
public class MemoryCopyBenchmark
|
||||
const int MaxLength = 1024 * 8;
|
||||
|
||||
byte[] _source;
|
||||
byte[] _target;
|
||||
|
||||
[Params(64 - 1, 128 - 1, 256 - 1, 512 - 1, 1024 - 1, 2048 - 1, 5096 - 1)]
|
||||
public int Length { get; set; }
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
const int max_length = 1024 * 8;
|
||||
private byte[] source;
|
||||
private byte[] target;
|
||||
|
||||
[Params(64 - 1, 128 - 1, 256 - 1, 512 - 1, 1024 - 1, 2048 - 1, 5096 - 1)]
|
||||
public int Length { get; set; }
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
source = new byte[max_length];
|
||||
target = new byte[max_length];
|
||||
}
|
||||
|
||||
[Benchmark(Baseline = true)]
|
||||
public void Array_Copy()
|
||||
{
|
||||
Array.Copy(source, 0, target, 0, Length);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Memory_Copy()
|
||||
{
|
||||
MQTTnet.Internal.MqttMemoryHelper.Copy(source, 0, target, 0, Length);
|
||||
}
|
||||
|
||||
_source = new byte[MaxLength];
|
||||
_target = new byte[MaxLength];
|
||||
}
|
||||
|
||||
[Benchmark(Baseline = true)]
|
||||
public void Array_Copy()
|
||||
{
|
||||
Array.Copy(_source, 0, _target, 0, Length);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Memory_Copy()
|
||||
{
|
||||
Internal.MqttMemoryHelper.Copy(_source, 0, _target, 0, Length);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,231 +3,194 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Packets;
|
||||
using MQTTnet.Server;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MQTTnet.Benchmarks
|
||||
namespace MQTTnet.Benchmarks;
|
||||
|
||||
[MemoryDiagnoser]
|
||||
public sealed class MessageDeliveryBenchmark : BaseBenchmark, IDisposable
|
||||
{
|
||||
[MemoryDiagnoser]
|
||||
public class MessageDeliveryBenchmark : BaseBenchmark
|
||||
List<string> _allSubscribedTopics; // Keep track of the subset of topics that are subscribed
|
||||
CancellationTokenSource _cancellationTokenSource;
|
||||
|
||||
object _lockMsgCount;
|
||||
int _messagesExpectedCount;
|
||||
int _messagesReceivedCount;
|
||||
Dictionary<string, IMqttClient> _mqttPublisherClientsByPublisherName;
|
||||
MqttServer _mqttServer;
|
||||
List<IMqttClient> _mqttSubscriberClients;
|
||||
|
||||
[Params(1000, 10000)] public int _numPublishers;
|
||||
|
||||
[Params(5, 10, 20, 50)] public int _numSubscribedTopicsPerSubscriber;
|
||||
|
||||
[Params(10)] public int _numSubscribers;
|
||||
|
||||
[Params(1, 5)] public int _numTopicsPerPublisher;
|
||||
Dictionary<string, string> _publisherByTopic;
|
||||
Dictionary<string, List<string>> _topicsByPublisher;
|
||||
|
||||
[GlobalCleanup]
|
||||
public void Cleanup()
|
||||
{
|
||||
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()
|
||||
foreach (var mp in _mqttPublisherClientsByPublisherName)
|
||||
{
|
||||
_lockMsgCount = new object();
|
||||
|
||||
Dictionary<string, List<string>> singleWildcardTopicsByPublisher;
|
||||
Dictionary<string, List<string>> multiWildcardTopicsByPublisher;
|
||||
|
||||
TopicGenerator.Generate(NumPublishers, NumTopicsPerPublisher, out _topicsByPublisher, out singleWildcardTopicsByPublisher, out multiWildcardTopicsByPublisher);
|
||||
|
||||
var topics = _topicsByPublisher.First().Value;
|
||||
_topicPublishMessages = new List<MqttApplicationMessage>();
|
||||
// Prepare messages, same for each publisher
|
||||
foreach (var topic in topics)
|
||||
{
|
||||
var message = new MqttApplicationMessageBuilder()
|
||||
.WithTopic(topic)
|
||||
.WithPayload(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 })
|
||||
.Build();
|
||||
_topicPublishMessages.Add(message);
|
||||
}
|
||||
|
||||
// Create server
|
||||
var factory = new MqttFactory();
|
||||
var serverOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
|
||||
_mqttServer = factory.CreateMqttServer(serverOptions);
|
||||
_mqttServer.StartAsync().GetAwaiter().GetResult();
|
||||
|
||||
// Create publisher clients
|
||||
_mqttPublisherClientsByPublisherName = new Dictionary<string, 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 =>
|
||||
{
|
||||
// count messages and signal cancellation when expected message count is reached
|
||||
lock (_lockMsgCount)
|
||||
{
|
||||
++_messagesReceivedCount;
|
||||
if (_messagesReceivedCount == _messagesExpectedCount)
|
||||
{
|
||||
_cancellationTokenSource.Cancel();
|
||||
}
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
mqttSubscriberClient.ConnectAsync(subscriberOptions).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
|
||||
List<string> allTopics = new List<string>();
|
||||
_publisherByTopic = new Dictionary<string, string>();
|
||||
foreach (var t in _topicsByPublisher)
|
||||
{
|
||||
foreach (var topic in t.Value)
|
||||
{
|
||||
_publisherByTopic.Add(topic, t.Key);
|
||||
allTopics.Add(topic);
|
||||
}
|
||||
}
|
||||
|
||||
// Subscribe to NumSubscribedTopics topics spread across all topics
|
||||
_allSubscribedTopics = new List<string>();
|
||||
|
||||
var totalNumTopics = NumPublishers * NumTopicsPerPublisher;
|
||||
int topicIndexStep = totalNumTopics / (NumSubscribedTopicsPerSubscriber * NumSubscribers);
|
||||
if (topicIndexStep * NumSubscribedTopicsPerSubscriber * NumSubscribers != totalNumTopics)
|
||||
{
|
||||
throw new System.Exception(
|
||||
String.Format("The total number of topics must be divisible by the number of subscribed topics across all subscribers. Total number of topics: {0}, topic step: {1}",
|
||||
totalNumTopics, topicIndexStep
|
||||
));
|
||||
}
|
||||
|
||||
var topicIndex = 0;
|
||||
foreach (var mqttSubscriber in _mqttSubscriberClients)
|
||||
{
|
||||
for (var i = 0; i < NumSubscribedTopicsPerSubscriber; ++i, topicIndex += topicIndexStep)
|
||||
{
|
||||
var topic = allTopics[topicIndex];
|
||||
_allSubscribedTopics.Add(topic);
|
||||
var subOptions = new Client.MqttClientSubscribeOptionsBuilder().WithTopicFilter(
|
||||
new Packets.MqttTopicFilter() { Topic = topic })
|
||||
.Build();
|
||||
mqttSubscriber.SubscribeAsync(subOptions).GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
|
||||
Task.Delay(1000).GetAwaiter().GetResult();
|
||||
var mqttPublisherClient = mp.Value;
|
||||
mqttPublisherClient.DisconnectAsync().GetAwaiter().GetResult();
|
||||
mqttPublisherClient.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Publish messages and wait for messages sent to subscribers
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public void DeliverMessages()
|
||||
_mqttPublisherClientsByPublisherName.Clear();
|
||||
|
||||
foreach (var mqttSubscriber in _mqttSubscriberClients)
|
||||
{
|
||||
// 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));
|
||||
}
|
||||
mqttSubscriber.DisconnectAsync().GetAwaiter().GetResult();
|
||||
mqttSubscriber.Dispose();
|
||||
}
|
||||
|
||||
[GlobalCleanup]
|
||||
public void Cleanup()
|
||||
_mqttSubscriberClients.Clear();
|
||||
|
||||
_mqttServer.StopAsync().GetAwaiter().GetResult();
|
||||
_mqttServer.Dispose();
|
||||
_mqttServer = null;
|
||||
}
|
||||
|
||||
[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 };
|
||||
|
||||
// publish a message for each subscribed topic
|
||||
foreach (var topic in _allSubscribedTopics)
|
||||
{
|
||||
foreach (var mp in _mqttPublisherClientsByPublisherName)
|
||||
{
|
||||
var mqttPublisherClient = mp.Value;
|
||||
mqttPublisherClient.DisconnectAsync().GetAwaiter().GetResult();
|
||||
mqttPublisherClient.Dispose();
|
||||
}
|
||||
_mqttPublisherClientsByPublisherName.Clear();
|
||||
var message = new MqttApplicationMessageBuilder().WithTopic(topic).WithPayload(payload).Build();
|
||||
// pick the correct publisher
|
||||
var publisherName = _publisherByTopic[topic];
|
||||
var publisherClient = _mqttPublisherClientsByPublisherName[publisherName];
|
||||
_ = publisherClient.PublishAsync(message);
|
||||
}
|
||||
|
||||
foreach (var mqttSubscriber in _mqttSubscriberClients)
|
||||
{
|
||||
mqttSubscriber.DisconnectAsync().GetAwaiter().GetResult();
|
||||
mqttSubscriber.Dispose();
|
||||
}
|
||||
_mqttSubscriberClients.Clear();
|
||||
// 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
|
||||
{
|
||||
// Ignore all errors.
|
||||
}
|
||||
|
||||
_mqttServer.StopAsync().GetAwaiter().GetResult();
|
||||
_mqttServer.Dispose();
|
||||
_mqttServer = null;
|
||||
_cancellationTokenSource.Dispose();
|
||||
|
||||
if (_messagesReceivedCount < _messagesExpectedCount)
|
||||
{
|
||||
throw new InvalidOperationException($"Messages Received Count mismatch, expected {_messagesExpectedCount}, received {_messagesReceivedCount}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_cancellationTokenSource?.Dispose();
|
||||
_mqttServer?.Dispose();
|
||||
}
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
_lockMsgCount = new object();
|
||||
|
||||
TopicGenerator.Generate(_numPublishers, _numTopicsPerPublisher, out _topicsByPublisher, out _, out _);
|
||||
|
||||
// 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 = [];
|
||||
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 += _ =>
|
||||
{
|
||||
// count messages and signal cancellation when expected message count is reached
|
||||
lock (_lockMsgCount)
|
||||
{
|
||||
++_messagesReceivedCount;
|
||||
if (_messagesReceivedCount == _messagesExpectedCount)
|
||||
{
|
||||
_cancellationTokenSource.Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
|
||||
mqttSubscriberClient.ConnectAsync(subscriberOptions).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
|
||||
var allTopics = new List<string>();
|
||||
_publisherByTopic = [];
|
||||
foreach (var t in _topicsByPublisher)
|
||||
{
|
||||
foreach (var topic in t.Value)
|
||||
{
|
||||
_publisherByTopic.Add(topic, t.Key);
|
||||
allTopics.Add(topic);
|
||||
}
|
||||
}
|
||||
|
||||
// Subscribe to NumSubscribedTopics topics spread across all topics
|
||||
_allSubscribedTopics = new List<string>();
|
||||
|
||||
var totalNumTopics = _numPublishers * _numTopicsPerPublisher;
|
||||
var topicIndexStep = totalNumTopics / (_numSubscribedTopicsPerSubscriber * _numSubscribers);
|
||||
if (topicIndexStep * _numSubscribedTopicsPerSubscriber * _numSubscribers != totalNumTopics)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"The total number of topics must be divisible by the number of subscribed topics across all subscribers. Total number of topics: {totalNumTopics}, topic step: {topicIndexStep}");
|
||||
}
|
||||
|
||||
var topicIndex = 0;
|
||||
foreach (var mqttSubscriber in _mqttSubscriberClients)
|
||||
{
|
||||
for (var i = 0; i < _numSubscribedTopicsPerSubscriber; ++i, topicIndex += topicIndexStep)
|
||||
{
|
||||
var topic = allTopics[topicIndex];
|
||||
_allSubscribedTopics.Add(topic);
|
||||
var subOptions = new MqttClientSubscribeOptionsBuilder().WithTopicFilter(new MqttTopicFilter { Topic = topic }).Build();
|
||||
mqttSubscriber.SubscribeAsync(subOptions).GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
|
||||
Task.Delay(1000).GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
@@ -4,48 +4,45 @@
|
||||
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Server;
|
||||
|
||||
namespace MQTTnet.Benchmarks
|
||||
namespace MQTTnet.Benchmarks;
|
||||
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[RPlotExporter]
|
||||
[RankColumn]
|
||||
[MemoryDiagnoser]
|
||||
public class MessageProcessingBenchmark : BaseBenchmark
|
||||
{
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[RPlotExporter, RankColumn]
|
||||
[MemoryDiagnoser]
|
||||
public class MessageProcessingBenchmark : BaseBenchmark
|
||||
MqttApplicationMessage _message;
|
||||
IMqttClient _mqttClient;
|
||||
MqttServer _mqttServer;
|
||||
|
||||
[Benchmark]
|
||||
public void Send_10000_Messages()
|
||||
{
|
||||
MqttServer _mqttServer;
|
||||
IMqttClient _mqttClient;
|
||||
MqttApplicationMessage _message;
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
for (var i = 0; i < 10000; i++)
|
||||
{
|
||||
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();
|
||||
}
|
||||
_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,72 +4,69 @@
|
||||
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using MQTTnet.Client;
|
||||
using Microsoft.AspNetCore;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using MQTTnet.AspNetCore.Client;
|
||||
using MQTTnet.AspNetCore;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Diagnostics.Logger;
|
||||
|
||||
namespace MQTTnet.Benchmarks
|
||||
namespace MQTTnet.Benchmarks;
|
||||
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[MemoryDiagnoser]
|
||||
public class MessageProcessingMqttConnectionContextBenchmark : BaseBenchmark
|
||||
{
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[MemoryDiagnoser]
|
||||
public class MessageProcessingMqttConnectionContextBenchmark : BaseBenchmark
|
||||
IWebHost _host;
|
||||
IMqttClient _mqttClient;
|
||||
MqttApplicationMessage _message;
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
IWebHost _host;
|
||||
IMqttClient _mqttClient;
|
||||
MqttApplicationMessage _message;
|
||||
_host = WebHost.CreateDefaultBuilder()
|
||||
.UseKestrel(o => o.ListenAnyIP(1883, l => l.UseMqtt()))
|
||||
.ConfigureServices(services => {
|
||||
services
|
||||
.AddHostedMqttServer(mqttServerOptions => mqttServerOptions.WithoutDefaultEndpoint())
|
||||
.AddMqttConnectionHandler();
|
||||
})
|
||||
.Configure(app => {
|
||||
app.UseMqttServer(_ => {
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
});
|
||||
})
|
||||
.Build();
|
||||
|
||||
var factory = new MqttClientFactory();
|
||||
_mqttClient = factory.CreateMqttClient(new MqttNetEventLogger(), new MqttClientConnectionContextFactory());
|
||||
|
||||
_host.StartAsync().GetAwaiter().GetResult();
|
||||
|
||||
var clientOptions = new MqttClientOptionsBuilder()
|
||||
.WithTcpServer("localhost").Build();
|
||||
|
||||
_mqttClient.ConnectAsync(clientOptions).GetAwaiter().GetResult();
|
||||
|
||||
_message = new MqttApplicationMessageBuilder()
|
||||
.WithTopic("A")
|
||||
.Build();
|
||||
}
|
||||
|
||||
[GlobalCleanup]
|
||||
public void Cleanup()
|
||||
{
|
||||
_mqttClient.DisconnectAsync().GetAwaiter().GetResult();
|
||||
_mqttClient.Dispose();
|
||||
|
||||
_host.StopAsync().GetAwaiter().GetResult();
|
||||
_host.Dispose();
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Send_10000_Messages()
|
||||
{
|
||||
for (var i = 0; i < 10000; i++)
|
||||
{
|
||||
_host = WebHost.CreateDefaultBuilder()
|
||||
.UseKestrel(o => o.ListenAnyIP(1883, l => l.UseMqtt()))
|
||||
.ConfigureServices(services => {
|
||||
services
|
||||
.AddHostedMqttServer(mqttServerOptions => mqttServerOptions.WithoutDefaultEndpoint())
|
||||
.AddMqttConnectionHandler();
|
||||
})
|
||||
.Configure(app => {
|
||||
app.UseMqttServer(s => {
|
||||
|
||||
});
|
||||
})
|
||||
.Build();
|
||||
|
||||
var factory = new MqttFactory();
|
||||
_mqttClient = factory.CreateMqttClient(new MqttNetEventLogger(), new MqttClientConnectionContextFactory());
|
||||
|
||||
_host.StartAsync().GetAwaiter().GetResult();
|
||||
|
||||
var clientOptions = new MqttClientOptionsBuilder()
|
||||
.WithTcpServer("localhost").Build();
|
||||
|
||||
_mqttClient.ConnectAsync(clientOptions).GetAwaiter().GetResult();
|
||||
|
||||
_message = new MqttApplicationMessageBuilder()
|
||||
.WithTopic("A")
|
||||
.Build();
|
||||
}
|
||||
|
||||
[GlobalCleanup]
|
||||
public void Cleanup()
|
||||
{
|
||||
_mqttClient.DisconnectAsync().GetAwaiter().GetResult();
|
||||
_mqttClient.Dispose();
|
||||
|
||||
_host.StopAsync().GetAwaiter().GetResult();
|
||||
_host.Dispose();
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Send_10000_Messages()
|
||||
{
|
||||
for (var i = 0; i < 10000; i++)
|
||||
{
|
||||
_mqttClient.PublishAsync(_message).GetAwaiter().GetResult();
|
||||
}
|
||||
_mqttClient.PublishAsync(_message).GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,42 +2,40 @@
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Text;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using MQTTnet.Formatter;
|
||||
|
||||
namespace MQTTnet.Benchmarks
|
||||
namespace MQTTnet.Benchmarks;
|
||||
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[MemoryDiagnoser]
|
||||
public class MqttBufferReaderBenchmark
|
||||
{
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[MemoryDiagnoser]
|
||||
public class MqttBufferReaderBenchmark
|
||||
byte[] _buffer;
|
||||
int _bufferLength;
|
||||
|
||||
[GlobalSetup]
|
||||
public void GlobalSetup()
|
||||
{
|
||||
byte[] _buffer;
|
||||
int _bufferLength;
|
||||
var writer = new MqttBufferWriter(1024, 1024);
|
||||
writer.WriteString("hgfjkdfkjlghfdjghdfljkdfhgdlkjfshgsldkfjghsdflkjghdsflkjhrstiuoghlkfjbhnfbutghjoiöjhklötnbhtroliöuhbjntluiobkjzbhtdrlskbhtruhjkfthgbkftgjhgfiklhotriuöhbjtrsioöbtrsötrhträhtrühjtriüoätrhjtsrölbktrbnhtrulöbionhströloubinströoliubhnsöotrunbtöroisntröointröioujhgötiohjgötorshjnbgtöorihbnjtröoihbjntröobntröoibntrjhötrohjbtröoihntröoibnrtoiöbtrjnboöitrhjtnriohötrhjtöroihjtroöihjtroösibntsroönbotöirsbntöoihjntröoihntroöbtrboöitrnhoöitrhjntröoishbnjtröosbhtröbntriohjtröoijtöoitbjöotibjnhöotirhbjntroöibhnjrtoöibnhtroöibnhtörsbnhtöoirbnhtöroibntoörhjnbträöbtrbträbtrbtirbätrsibohjntrsöiobthnjiohjsrtoib");
|
||||
|
||||
[GlobalSetup]
|
||||
public void GlobalSetup()
|
||||
{
|
||||
var writer = new MqttBufferWriter(1024, 1024);
|
||||
writer.WriteString("hgfjkdfkjlghfdjghdfljkdfhgdlkjfshgsldkfjghsdflkjghdsflkjhrstiuoghlkfjbhnfbutghjoiöjhklötnbhtroliöuhbjntluiobkjzbhtdrlskbhtruhjkfthgbkftgjhgfiklhotriuöhbjtrsioöbtrsötrhträhtrühjtriüoätrhjtsrölbktrbnhtrulöbionhströloubinströoliubhnsöotrunbtöroisntröointröioujhgötiohjgötorshjnbgtöorihbnjtröoihbjntröobntröoibntrjhötrohjbtröoihntröoibnrtoiöbtrjnboöitrhjtnriohötrhjtöroihjtroöihjtroösibntsroönbotöirsbntöoihjntröoihntroöbtrboöitrnhoöitrhjntröoishbnjtröosbhtröbntriohjtröoijtöoitbjöotibjnhöotirhbjntroöibhnjrtoöibnhtroöibnhtörsbnhtöoirbnhtöroibntoörhjnbträöbtrbträbtrbtirbätrsibohjntrsöiobthnjiohjsrtoib");
|
||||
_buffer = writer.GetBuffer();
|
||||
_bufferLength = writer.Length;
|
||||
}
|
||||
|
||||
_buffer = writer.GetBuffer();
|
||||
_bufferLength = writer.Length;
|
||||
}
|
||||
[Benchmark]
|
||||
public void Use_Span()
|
||||
{
|
||||
var span = _buffer.AsSpan(0, _bufferLength);
|
||||
Encoding.UTF8.GetString(span);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Use_Span()
|
||||
{
|
||||
var span = _buffer.AsSpan(0, _bufferLength);
|
||||
Encoding.UTF8.GetString(span);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Use_Encoding()
|
||||
{
|
||||
Encoding.UTF8.GetString(_buffer, 0, _bufferLength);
|
||||
}
|
||||
[Benchmark]
|
||||
public void Use_Encoding()
|
||||
{
|
||||
Encoding.UTF8.GetString(_buffer, 0, _bufferLength);
|
||||
}
|
||||
}
|
||||
@@ -2,95 +2,93 @@
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using MQTTnet.Formatter;
|
||||
using MQTTnet.Tests.Mockups;
|
||||
|
||||
namespace MQTTnet.Benchmarks
|
||||
namespace MQTTnet.Benchmarks;
|
||||
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[MemoryDiagnoser]
|
||||
public class MqttPacketReaderWriterBenchmark : BaseBenchmark
|
||||
{
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[MemoryDiagnoser]
|
||||
public class MqttPacketReaderWriterBenchmark : BaseBenchmark
|
||||
readonly byte[] _demoPayload = new byte[1024];
|
||||
|
||||
byte[] _readPayload;
|
||||
|
||||
[GlobalCleanup]
|
||||
public void GlobalCleanup()
|
||||
{
|
||||
readonly byte[] _demoPayload = new byte[1024];
|
||||
|
||||
byte[] _readPayload;
|
||||
}
|
||||
|
||||
[GlobalCleanup]
|
||||
public void GlobalCleanup()
|
||||
[GlobalSetup]
|
||||
public void GlobalSetup()
|
||||
{
|
||||
TestEnvironment.EnableLogger = false;
|
||||
|
||||
var writer = new MqttBufferWriter(4096, 65535);
|
||||
writer.WriteString("A relative short string.");
|
||||
writer.WriteBinary(_demoPayload);
|
||||
writer.WriteByte(0x01);
|
||||
writer.WriteByte(0x02);
|
||||
writer.WriteVariableByteInteger(5647382);
|
||||
writer.WriteString("A relative short string.");
|
||||
writer.WriteVariableByteInteger(8574489);
|
||||
writer.WriteBinary(_demoPayload);
|
||||
writer.WriteByte(2);
|
||||
writer.WriteByte(0x02);
|
||||
writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug");
|
||||
writer.WriteBinary(_demoPayload);
|
||||
|
||||
_readPayload = new ArraySegment<byte>(writer.GetBuffer(), 0, writer.Length).ToArray();
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Read_100_000_Messages()
|
||||
{
|
||||
var reader = new MqttBufferReader();
|
||||
reader.SetBuffer(_readPayload, 0, _readPayload.Length);
|
||||
|
||||
for (var i = 0; i < 100000; i++)
|
||||
{
|
||||
reader.Seek(0);
|
||||
|
||||
reader.ReadString();
|
||||
reader.ReadBinaryData();
|
||||
reader.ReadByte();
|
||||
reader.ReadByte();
|
||||
reader.ReadVariableByteInteger();
|
||||
reader.ReadString();
|
||||
reader.ReadVariableByteInteger();
|
||||
reader.ReadBinaryData();
|
||||
reader.ReadByte();
|
||||
reader.ReadByte();
|
||||
reader.ReadString();
|
||||
reader.ReadBinaryData();
|
||||
}
|
||||
}
|
||||
|
||||
[GlobalSetup]
|
||||
public void GlobalSetup()
|
||||
[Benchmark]
|
||||
public void Write_100_000_Messages()
|
||||
{
|
||||
var writer = new MqttBufferWriter(4096, 65535);
|
||||
|
||||
for (var i = 0; i < 100000; i++)
|
||||
{
|
||||
TestEnvironment.EnableLogger = false;
|
||||
|
||||
var writer = new MqttBufferWriter(4096, 65535);
|
||||
writer.WriteString("A relative short string.");
|
||||
writer.WriteBinary(_demoPayload);
|
||||
writer.WriteByte(0x01);
|
||||
writer.WriteByte(0x02);
|
||||
writer.WriteVariableByteInteger(5647382);
|
||||
writer.WriteString("A relative short string.");
|
||||
writer.WriteVariableByteInteger(8574489);
|
||||
writer.WriteVariableByteInteger(8574589);
|
||||
writer.WriteBinary(_demoPayload);
|
||||
writer.WriteByte(2);
|
||||
writer.WriteByte(0x02);
|
||||
writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug");
|
||||
writer.WriteBinary(_demoPayload);
|
||||
|
||||
_readPayload = new ArraySegment<byte>(writer.GetBuffer(), 0, writer.Length).ToArray();
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Read_100_000_Messages()
|
||||
{
|
||||
var reader = new MqttBufferReader();
|
||||
reader.SetBuffer(_readPayload, 0, _readPayload.Length);
|
||||
|
||||
for (var i = 0; i < 100000; i++)
|
||||
{
|
||||
reader.Seek(0);
|
||||
|
||||
reader.ReadString();
|
||||
reader.ReadBinaryData();
|
||||
reader.ReadByte();
|
||||
reader.ReadByte();
|
||||
reader.ReadVariableByteInteger();
|
||||
reader.ReadString();
|
||||
reader.ReadVariableByteInteger();
|
||||
reader.ReadBinaryData();
|
||||
reader.ReadByte();
|
||||
reader.ReadByte();
|
||||
reader.ReadString();
|
||||
reader.ReadBinaryData();
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Write_100_000_Messages()
|
||||
{
|
||||
var writer = new MqttBufferWriter(4096, 65535);
|
||||
|
||||
for (var i = 0; i < 100000; i++)
|
||||
{
|
||||
writer.WriteString("A relative short string.");
|
||||
writer.WriteByte(0x01);
|
||||
writer.WriteByte(0x02);
|
||||
writer.WriteVariableByteInteger(5647382);
|
||||
writer.WriteString("A relative short string.");
|
||||
writer.WriteVariableByteInteger(8574589);
|
||||
writer.WriteBinary(_demoPayload);
|
||||
writer.WriteByte(2);
|
||||
writer.WriteByte(0x02);
|
||||
writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug");
|
||||
writer.WriteBinary(_demoPayload);
|
||||
|
||||
writer.Reset(0);
|
||||
}
|
||||
writer.Reset(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,90 +2,91 @@
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Reflection;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using MQTTnet.Channel;
|
||||
using MQTTnet.Diagnostics.Logger;
|
||||
using MQTTnet.Implementations;
|
||||
using MQTTnet.Server;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Server.Internal.Adapter;
|
||||
|
||||
namespace MQTTnet.Benchmarks
|
||||
namespace MQTTnet.Benchmarks;
|
||||
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[MemoryDiagnoser]
|
||||
public sealed class MqttTcpChannelBenchmark : BaseBenchmark, IDisposable
|
||||
{
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[MemoryDiagnoser]
|
||||
public class MqttTcpChannelBenchmark : BaseBenchmark
|
||||
MqttTcpChannel _clientChannel;
|
||||
MqttServer _mqttServer;
|
||||
IMqttChannel _serverChannel;
|
||||
|
||||
[Benchmark]
|
||||
public async Task Send_10000_Chunks()
|
||||
{
|
||||
MqttServer _mqttServer;
|
||||
IMqttChannel _serverChannel;
|
||||
IMqttChannel _clientChannel;
|
||||
var size = 5;
|
||||
var iterations = 10000;
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
await Task.WhenAll(WriteAsync(iterations, size), ReadAsync(iterations, size));
|
||||
}
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
var serverFactory = new MqttServerFactory();
|
||||
var tcpServer = new MqttTcpServerAdapter();
|
||||
tcpServer.ClientHandler += 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);
|
||||
_serverChannel = (IMqttChannel)args.GetType().GetField("_channel", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(args);
|
||||
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
|
||||
var serverOptions = new MqttServerOptionsBuilder().Build();
|
||||
_mqttServer = factory.CreateMqttServer(serverOptions, new[] { tcpServer }, new MqttNetEventLogger());
|
||||
var serverOptions = new MqttServerOptionsBuilder().Build();
|
||||
_mqttServer = serverFactory.CreateMqttServer(serverOptions, new[] { tcpServer }, new MqttNetEventLogger());
|
||||
|
||||
|
||||
_mqttServer.StartAsync().GetAwaiter().GetResult();
|
||||
|
||||
var clientOptions = new MqttClientOptionsBuilder()
|
||||
.WithTcpServer("localhost").Build();
|
||||
_mqttServer.StartAsync().GetAwaiter().GetResult();
|
||||
|
||||
var tcpOptions = (MqttClientTcpOptions)clientOptions.ChannelOptions;
|
||||
_clientChannel = new MqttTcpChannel(new MqttClientOptions { ChannelOptions = tcpOptions });
|
||||
var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("localhost").Build();
|
||||
|
||||
_clientChannel.ConnectAsync(CancellationToken.None).GetAwaiter().GetResult();
|
||||
}
|
||||
var tcpOptions = (MqttClientTcpOptions)clientOptions.ChannelOptions;
|
||||
_clientChannel = new MqttTcpChannel(new MqttClientOptions { ChannelOptions = tcpOptions });
|
||||
|
||||
[Benchmark]
|
||||
public async Task Send_10000_Chunks()
|
||||
_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 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);
|
||||
}
|
||||
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 ReadOnlySequence<byte>(new byte[size]);
|
||||
|
||||
for (var i = 0; i < iterations; i++)
|
||||
{
|
||||
await _serverChannel.WriteAsync(buffer, true, CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_clientChannel?.Dispose();
|
||||
_mqttServer?.Dispose();
|
||||
_serverChannel?.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -2,141 +2,135 @@
|
||||
// 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 BenchmarkDotNet.Loggers;
|
||||
using BenchmarkDotNet.Running;
|
||||
using MQTTnet.Diagnostics;
|
||||
|
||||
namespace MQTTnet.Benchmarks
|
||||
namespace MQTTnet.Benchmarks;
|
||||
|
||||
public static class Program
|
||||
{
|
||||
public static class Program
|
||||
static int _selectedBenchmarkIndex;
|
||||
static List<Type> _benchmarks;
|
||||
|
||||
public static void Main(string[] arguments)
|
||||
{
|
||||
static int _selectedBenchmarkIndex;
|
||||
static List<Type> _benchmarks;
|
||||
_benchmarks = CollectBenchmarks();
|
||||
HandleArguments(arguments);
|
||||
|
||||
public static void Main(string[] arguments)
|
||||
while (true)
|
||||
{
|
||||
_benchmarks = CollectBenchmarks();
|
||||
HandleArguments(arguments);
|
||||
RenderMenu();
|
||||
|
||||
while (true)
|
||||
var key = Console.ReadKey(true);
|
||||
|
||||
if (key.Key == ConsoleKey.DownArrow)
|
||||
{
|
||||
RenderMenu();
|
||||
|
||||
var key = Console.ReadKey(true);
|
||||
|
||||
if (key.Key == ConsoleKey.DownArrow)
|
||||
_selectedBenchmarkIndex++;
|
||||
if (_selectedBenchmarkIndex > _benchmarks.Count - 1)
|
||||
{
|
||||
_selectedBenchmarkIndex++;
|
||||
if (_selectedBenchmarkIndex > _benchmarks.Count - 1)
|
||||
{
|
||||
_selectedBenchmarkIndex = 0;
|
||||
}
|
||||
}
|
||||
else if (key.Key == ConsoleKey.UpArrow)
|
||||
{
|
||||
_selectedBenchmarkIndex--;
|
||||
if (_selectedBenchmarkIndex < 0)
|
||||
{
|
||||
_selectedBenchmarkIndex = _benchmarks.Count - 1;
|
||||
}
|
||||
}
|
||||
else if (key.Key == ConsoleKey.Escape)
|
||||
{
|
||||
Environment.Exit(0);
|
||||
}
|
||||
else if (key.Key == ConsoleKey.Enter)
|
||||
{
|
||||
break;
|
||||
_selectedBenchmarkIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
BenchmarkRunner.Run(_benchmarks[_selectedBenchmarkIndex]);
|
||||
|
||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
Console.WriteLine("Press any key to exit");
|
||||
Console.ReadLine();
|
||||
}
|
||||
|
||||
static List<Type> CollectBenchmarks()
|
||||
{
|
||||
var benchmarks = new List<Type>();
|
||||
|
||||
var types = typeof(Program).Assembly.GetExportedTypes();
|
||||
foreach (var type in types)
|
||||
else if (key.Key == ConsoleKey.UpArrow)
|
||||
{
|
||||
if (typeof(BaseBenchmark).IsAssignableFrom(type))
|
||||
_selectedBenchmarkIndex--;
|
||||
if (_selectedBenchmarkIndex < 0)
|
||||
{
|
||||
if (type != typeof(BaseBenchmark))
|
||||
{
|
||||
benchmarks.Add(type);
|
||||
}
|
||||
_selectedBenchmarkIndex = _benchmarks.Count - 1;
|
||||
}
|
||||
}
|
||||
|
||||
return benchmarks.OrderBy(b => b.Name).ToList();
|
||||
}
|
||||
|
||||
static void HandleArguments(string[] arguments)
|
||||
{
|
||||
if (arguments.Length == 0)
|
||||
else if (key.Key == ConsoleKey.Escape)
|
||||
{
|
||||
return;
|
||||
Environment.Exit(0);
|
||||
}
|
||||
|
||||
// Allow for preselection to avoid developer frustration.
|
||||
|
||||
if (int.TryParse(arguments[0], out var benchmarkIndex))
|
||||
else if (key.Key == ConsoleKey.Enter)
|
||||
{
|
||||
_selectedBenchmarkIndex = benchmarkIndex;
|
||||
return;
|
||||
}
|
||||
|
||||
_selectedBenchmarkIndex = _benchmarks.FindIndex(b => b.Name.Equals(arguments[0]));
|
||||
|
||||
if (_selectedBenchmarkIndex < 0)
|
||||
{
|
||||
_selectedBenchmarkIndex = 0;
|
||||
}
|
||||
|
||||
if (_selectedBenchmarkIndex > _benchmarks.Count - 1)
|
||||
{
|
||||
_selectedBenchmarkIndex = _benchmarks.Count - 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void RenderMenu()
|
||||
BenchmarkRunner.Run(_benchmarks[_selectedBenchmarkIndex]);
|
||||
|
||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
Console.WriteLine("Press any key to exit");
|
||||
Console.ReadLine();
|
||||
}
|
||||
|
||||
static List<Type> CollectBenchmarks()
|
||||
{
|
||||
var benchmarks = new List<Type>();
|
||||
|
||||
var types = typeof(Program).Assembly.GetExportedTypes();
|
||||
foreach (var type in types)
|
||||
{
|
||||
Console.Clear();
|
||||
|
||||
Console.WriteLine($"MQTTnet - Benchmarks ({TargetFrameworkProvider.TargetFramework})");
|
||||
Console.WriteLine("-----------------------------------------------");
|
||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
Console.WriteLine("Press arrow keys for benchmark selection");
|
||||
Console.WriteLine("Press Enter for benchmark execution");
|
||||
Console.WriteLine("Press Esc for exit");
|
||||
Console.ForegroundColor = ConsoleColor.White;
|
||||
Console.WriteLine();
|
||||
|
||||
for (var index = 0; index < _benchmarks.Count; index++)
|
||||
if (typeof(BaseBenchmark).IsAssignableFrom(type))
|
||||
{
|
||||
var benchmark = _benchmarks[index];
|
||||
|
||||
if (_selectedBenchmarkIndex == index)
|
||||
if (type != typeof(BaseBenchmark))
|
||||
{
|
||||
Console.Write("-> ");
|
||||
benchmarks.Add(type);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.Write(" ");
|
||||
}
|
||||
|
||||
Console.WriteLine(benchmark.Name);
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
return benchmarks.OrderBy(b => b.Name).ToList();
|
||||
}
|
||||
|
||||
static void HandleArguments(string[] arguments)
|
||||
{
|
||||
if (arguments.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Allow for preselection to avoid developer frustration.
|
||||
|
||||
if (int.TryParse(arguments[0], out var benchmarkIndex))
|
||||
{
|
||||
_selectedBenchmarkIndex = benchmarkIndex;
|
||||
return;
|
||||
}
|
||||
|
||||
_selectedBenchmarkIndex = _benchmarks.FindIndex(b => b.Name.Equals(arguments[0], StringComparison.Ordinal));
|
||||
|
||||
if (_selectedBenchmarkIndex < 0)
|
||||
{
|
||||
_selectedBenchmarkIndex = 0;
|
||||
}
|
||||
|
||||
if (_selectedBenchmarkIndex > _benchmarks.Count - 1)
|
||||
{
|
||||
_selectedBenchmarkIndex = _benchmarks.Count - 1;
|
||||
}
|
||||
}
|
||||
|
||||
static void RenderMenu()
|
||||
{
|
||||
Console.Clear();
|
||||
|
||||
Console.WriteLine($"MQTTnet - Benchmarks");
|
||||
Console.WriteLine("-----------------------------------------------");
|
||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
Console.WriteLine("Press arrow keys for benchmark selection");
|
||||
Console.WriteLine("Press Enter for benchmark execution");
|
||||
Console.WriteLine("Press Esc for exit");
|
||||
Console.ForegroundColor = ConsoleColor.White;
|
||||
Console.WriteLine();
|
||||
|
||||
for (var index = 0; index < _benchmarks.Count; index++)
|
||||
{
|
||||
var benchmark = _benchmarks[index];
|
||||
|
||||
if (_selectedBenchmarkIndex == index)
|
||||
{
|
||||
Console.Write("-> ");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.Write(" ");
|
||||
}
|
||||
|
||||
Console.WriteLine(benchmark.Name);
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
}
|
||||
}
|
||||
@@ -1,240 +1,97 @@
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.AspNetCore;
|
||||
using MQTTnet.Exceptions;
|
||||
using MQTTnet.Formatter;
|
||||
using MQTTnet.Packets;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.IO;
|
||||
using System.IO.Pipelines;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MQTTnet.Benchmarks
|
||||
namespace MQTTnet.Benchmarks;
|
||||
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[RPlotExporter, RankColumn]
|
||||
[MemoryDiagnoser]
|
||||
public sealed class ReaderExtensionsBenchmark : IDisposable, IAsyncDisposable
|
||||
{
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[RPlotExporter, RankColumn]
|
||||
[MemoryDiagnoser]
|
||||
public class ReaderExtensionsBenchmark
|
||||
{
|
||||
MqttPacketFormatterAdapter mqttPacketFormatter;
|
||||
MemoryStream stream;
|
||||
MqttPacketFormatterAdapter _mqttPacketFormatter;
|
||||
MemoryStream _stream;
|
||||
|
||||
[GlobalSetup]
|
||||
public void GlobalSetup()
|
||||
{
|
||||
mqttPacketFormatter = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535));
|
||||
var mqttMessage = new MqttApplicationMessageBuilder()
|
||||
[GlobalSetup]
|
||||
public void GlobalSetup()
|
||||
{
|
||||
_mqttPacketFormatter = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535));
|
||||
var mqttMessage = new MqttApplicationMessageBuilder()
|
||||
.WithTopic("topic")
|
||||
.WithPayload(new byte[10 * 1024])
|
||||
.Build();
|
||||
|
||||
var packet = MqttPacketFactories.Publish.Create(mqttMessage);
|
||||
|
||||
var buffer = mqttPacketFormatter.Encode(packet);
|
||||
stream = new MemoryStream();
|
||||
stream.Write(buffer.Packet);
|
||||
stream.Write(buffer.Payload);
|
||||
mqttPacketFormatter.Cleanup();
|
||||
}
|
||||
|
||||
[Benchmark(Baseline = true)]
|
||||
public async Task Before()
|
||||
{
|
||||
stream.Position = 0;
|
||||
var input = PipeReader.Create(stream);
|
||||
|
||||
while (true)
|
||||
{
|
||||
ReadResult readResult;
|
||||
var readTask = input.ReadAsync();
|
||||
if (readTask.IsCompleted)
|
||||
{
|
||||
readResult = readTask.Result;
|
||||
}
|
||||
else
|
||||
{
|
||||
readResult = await readTask.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
var buffer = readResult.Buffer;
|
||||
|
||||
var consumed = buffer.Start;
|
||||
var observed = buffer.Start;
|
||||
|
||||
try
|
||||
{
|
||||
if (!buffer.IsEmpty)
|
||||
{
|
||||
if (ReaderExtensions_Before.TryDecode(mqttPacketFormatter, buffer, out var packet, out consumed, out observed, out var received))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (readResult.IsCompleted)
|
||||
{
|
||||
throw new MqttCommunicationException("Connection Aborted");
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
// The buffer was sliced up to where it was consumed, so we can just advance to the start.
|
||||
// We mark examined as buffer.End so that if we didn't receive a full frame, we'll wait for more data
|
||||
// before yielding the read again.
|
||||
input.AdvanceTo(consumed, observed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public async Task After()
|
||||
{
|
||||
stream.Position = 0;
|
||||
var input = PipeReader.Create(stream);
|
||||
|
||||
while (true)
|
||||
{
|
||||
ReadResult readResult;
|
||||
var readTask = input.ReadAsync();
|
||||
if (readTask.IsCompleted)
|
||||
{
|
||||
readResult = readTask.Result;
|
||||
}
|
||||
else
|
||||
{
|
||||
readResult = await readTask.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
var buffer = readResult.Buffer;
|
||||
|
||||
var consumed = buffer.Start;
|
||||
var observed = buffer.Start;
|
||||
|
||||
try
|
||||
{
|
||||
if (!buffer.IsEmpty)
|
||||
{
|
||||
if (ReaderExtensions.TryDecode(mqttPacketFormatter, buffer, out var packet, out consumed, out observed, out var received))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (readResult.IsCompleted)
|
||||
{
|
||||
throw new MqttCommunicationException("Connection Aborted");
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
// The buffer was sliced up to where it was consumed, so we can just advance to the start.
|
||||
// We mark examined as buffer.End so that if we didn't receive a full frame, we'll wait for more data
|
||||
// before yielding the read again.
|
||||
input.AdvanceTo(consumed, observed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class ReaderExtensions_Before
|
||||
{
|
||||
public static bool TryDecode(MqttPacketFormatterAdapter formatter,
|
||||
in ReadOnlySequence<byte> input,
|
||||
out MqttPacket packet,
|
||||
out SequencePosition consumed,
|
||||
out SequencePosition observed,
|
||||
out int bytesRead)
|
||||
{
|
||||
if (formatter == null) throw new ArgumentNullException(nameof(formatter));
|
||||
|
||||
packet = null;
|
||||
consumed = input.Start;
|
||||
observed = input.End;
|
||||
bytesRead = 0;
|
||||
var copy = input;
|
||||
|
||||
if (copy.Length < 2)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var fixedHeader = copy.First.Span[0];
|
||||
if (!TryReadBodyLength(ref copy, out int headerLength, out var bodyLength))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (copy.Length < bodyLength)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var bodySlice = copy.Slice(0, bodyLength);
|
||||
var buffer = GetMemory(bodySlice).ToArray();
|
||||
|
||||
var receivedMqttPacket = new ReceivedMqttPacket(fixedHeader, new ArraySegment<byte>(buffer, 0, buffer.Length), buffer.Length + 2);
|
||||
|
||||
if (formatter.ProtocolVersion == MqttProtocolVersion.Unknown)
|
||||
{
|
||||
formatter.DetectProtocolVersion(receivedMqttPacket);
|
||||
}
|
||||
|
||||
packet = formatter.Decode(receivedMqttPacket);
|
||||
consumed = bodySlice.End;
|
||||
observed = bodySlice.End;
|
||||
bytesRead = headerLength + bodyLength;
|
||||
return true;
|
||||
}
|
||||
|
||||
static ReadOnlyMemory<byte> GetMemory(in ReadOnlySequence<byte> input)
|
||||
{
|
||||
if (input.IsSingleSegment)
|
||||
{
|
||||
return input.First;
|
||||
}
|
||||
|
||||
// Should be rare
|
||||
return input.ToArray();
|
||||
}
|
||||
|
||||
static bool TryReadBodyLength(ref ReadOnlySequence<byte> input, out int headerLength, out int bodyLength)
|
||||
{
|
||||
// Alorithm taken from https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/errata01/os/mqtt-v3.1.1-errata01-os-complete.html.
|
||||
var multiplier = 1;
|
||||
var value = 0;
|
||||
byte encodedByte;
|
||||
var index = 1;
|
||||
headerLength = 0;
|
||||
bodyLength = 0;
|
||||
|
||||
var temp = GetMemory(input.Slice(0, Math.Min(5, input.Length))).Span;
|
||||
|
||||
do
|
||||
{
|
||||
if (index == temp.Length)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
encodedByte = temp[index];
|
||||
index++;
|
||||
|
||||
value += (byte)(encodedByte & 127) * multiplier;
|
||||
if (multiplier > 128 * 128 * 128)
|
||||
{
|
||||
throw new MqttProtocolViolationException($"Remaining length is invalid (Data={string.Join(",", temp.Slice(1, index).ToArray())}).");
|
||||
}
|
||||
|
||||
multiplier *= 128;
|
||||
} while ((encodedByte & 128) != 0);
|
||||
|
||||
input = input.Slice(index);
|
||||
|
||||
headerLength = index;
|
||||
bodyLength = value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
var packet = MqttPublishPacketFactory.Create(mqttMessage);
|
||||
|
||||
var buffer = _mqttPacketFormatter.Encode(packet);
|
||||
_stream = new MemoryStream();
|
||||
_stream.Write(buffer.Packet);
|
||||
_stream.Write(buffer.Payload.ToArray());
|
||||
_mqttPacketFormatter.Cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public async Task After()
|
||||
{
|
||||
_stream.Position = 0;
|
||||
var input = PipeReader.Create(_stream);
|
||||
|
||||
while (true)
|
||||
{
|
||||
ReadResult readResult;
|
||||
var readTask = input.ReadAsync();
|
||||
if (readTask.IsCompleted)
|
||||
{
|
||||
readResult = readTask.Result;
|
||||
}
|
||||
else
|
||||
{
|
||||
readResult = await readTask.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
var buffer = readResult.Buffer;
|
||||
|
||||
var consumed = buffer.Start;
|
||||
var observed = buffer.Start;
|
||||
|
||||
try
|
||||
{
|
||||
if (!buffer.IsEmpty)
|
||||
{
|
||||
if (_mqttPacketFormatter.TryDecode(buffer, out _, out consumed, out observed, out _))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (readResult.IsCompleted)
|
||||
{
|
||||
throw new MqttCommunicationException("Connection Aborted");
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
// The buffer was sliced up to where it was consumed, so we can just advance to the start.
|
||||
// We mark examined as buffer.End so that if we didn't receive a full frame, we'll wait for more data
|
||||
// before yielding the read again.
|
||||
input.AdvanceTo(consumed, observed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_stream?.Dispose();
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
if (_stream != null)
|
||||
{
|
||||
await _stream.DisposeAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,30 +1,28 @@
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using MQTTnet.Tests.Mockups;
|
||||
using MQTTnet.Tests.Server;
|
||||
|
||||
namespace MQTTnet.Benchmarks
|
||||
namespace MQTTnet.Benchmarks;
|
||||
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[RPlotExporter, RankColumn]
|
||||
[MemoryDiagnoser]
|
||||
public class RoundtripProcessingBenchmark : BaseBenchmark
|
||||
{
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[RPlotExporter, RankColumn]
|
||||
[MemoryDiagnoser]
|
||||
public class RoundtripProcessingBenchmark : BaseBenchmark
|
||||
[GlobalSetup]
|
||||
public void GlobalSetup()
|
||||
{
|
||||
[GlobalSetup]
|
||||
public void GlobalSetup()
|
||||
{
|
||||
TestEnvironment.EnableLogger = false;
|
||||
}
|
||||
|
||||
[GlobalCleanup]
|
||||
public void GlobalCleanup()
|
||||
{
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Handle_100_000_Messages_In_Receiving_Client()
|
||||
{
|
||||
new Load_Tests().Handle_100_000_Messages_In_Receiving_Client().GetAwaiter().GetResult();
|
||||
}
|
||||
TestEnvironment.EnableLogger = false;
|
||||
}
|
||||
|
||||
[GlobalCleanup]
|
||||
public void GlobalCleanup()
|
||||
{
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Handle_100_000_Messages_In_Receiving_Client()
|
||||
{
|
||||
//new Load_Tests().Handle_100_000_Messages_In_Receiving_Client().GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
@@ -1,68 +1,78 @@
|
||||
using System.Buffers;
|
||||
using System.IO.Pipelines;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using MQTTnet.Formatter;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Pipelines;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MQTTnet.Benchmarks
|
||||
namespace MQTTnet.Benchmarks;
|
||||
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[RPlotExporter]
|
||||
[RankColumn]
|
||||
[MemoryDiagnoser]
|
||||
public sealed class SendPacketAsyncBenchmark : BaseBenchmark, IDisposable, IAsyncDisposable
|
||||
{
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[RPlotExporter, RankColumn]
|
||||
[MemoryDiagnoser]
|
||||
public class SendPacketAsyncBenchmark : BaseBenchmark
|
||||
MqttPacketBuffer _buffer;
|
||||
MemoryStream _stream;
|
||||
|
||||
[Benchmark]
|
||||
public async ValueTask After()
|
||||
{
|
||||
MemoryStream stream;
|
||||
MqttPacketBuffer buffer;
|
||||
_stream.Position = 0;
|
||||
var output = PipeWriter.Create(_stream);
|
||||
|
||||
[GlobalSetup]
|
||||
public void GlobalSetup()
|
||||
if (_buffer.Payload.Length == 0)
|
||||
{
|
||||
stream = new MemoryStream(1024);
|
||||
var packet = new ArraySegment<byte>(new byte[10]);
|
||||
buffer = new MqttPacketBuffer(packet);
|
||||
await output.WriteAsync(_buffer.Packet).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Benchmark(Baseline = true)]
|
||||
public async ValueTask Before()
|
||||
else
|
||||
{
|
||||
stream.Position = 0;
|
||||
var output = PipeWriter.Create(stream);
|
||||
|
||||
WritePacketBuffer(output, buffer);
|
||||
await output.FlushAsync();
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public async ValueTask After()
|
||||
{
|
||||
stream.Position = 0;
|
||||
var output = PipeWriter.Create(stream);
|
||||
|
||||
if (buffer.Payload.Count == 0)
|
||||
{
|
||||
await output.WriteAsync(buffer.Packet).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
WritePacketBuffer(output, buffer);
|
||||
await output.FlushAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void WritePacketBuffer(PipeWriter output, MqttPacketBuffer buffer)
|
||||
{
|
||||
// copy MqttPacketBuffer's Packet and Payload to the same buffer block of PipeWriter
|
||||
// MqttPacket will be transmitted within the bounds of a WebSocket frame after PipeWriter.FlushAsync
|
||||
|
||||
var span = output.GetSpan(buffer.Length);
|
||||
|
||||
buffer.Packet.AsSpan().CopyTo(span);
|
||||
buffer.Payload.AsSpan().CopyTo(span.Slice(buffer.Packet.Count));
|
||||
|
||||
output.Advance(buffer.Length);
|
||||
WritePacketBuffer(output, _buffer);
|
||||
await output.FlushAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark(Baseline = true)]
|
||||
public async ValueTask Before()
|
||||
{
|
||||
_stream.Position = 0;
|
||||
var output = PipeWriter.Create(_stream);
|
||||
|
||||
WritePacketBuffer(output, _buffer);
|
||||
await output.FlushAsync();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_stream?.Dispose();
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
if (_stream != null)
|
||||
{
|
||||
await _stream.DisposeAsync();
|
||||
}
|
||||
}
|
||||
|
||||
[GlobalSetup]
|
||||
public void GlobalSetup()
|
||||
{
|
||||
_stream = new MemoryStream(1024);
|
||||
var packet = new ArraySegment<byte>(new byte[10]);
|
||||
_buffer = new MqttPacketBuffer(packet);
|
||||
}
|
||||
|
||||
static void WritePacketBuffer(PipeWriter output, MqttPacketBuffer buffer)
|
||||
{
|
||||
// copy MqttPacketBuffer's Packet and Payload to the same buffer block of PipeWriter
|
||||
// MqttPacket will be transmitted within the bounds of a WebSocket frame after PipeWriter.FlushAsync
|
||||
|
||||
var span = output.GetSpan(buffer.Length);
|
||||
|
||||
buffer.Packet.AsSpan().CopyTo(span);
|
||||
buffer.Payload.CopyTo(span[buffer.Packet.Count..]);
|
||||
|
||||
output.Advance(buffer.Length);
|
||||
}
|
||||
}
|
||||
@@ -4,114 +4,114 @@
|
||||
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using MQTTnet.Packets;
|
||||
using System;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.Channel;
|
||||
using MQTTnet.Formatter;
|
||||
using MQTTnet.Formatter.V3;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Diagnostics.Logger;
|
||||
using System.Buffers;
|
||||
using System.Net;
|
||||
|
||||
namespace MQTTnet.Benchmarks
|
||||
namespace MQTTnet.Benchmarks;
|
||||
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[RPlotExporter]
|
||||
[MemoryDiagnoser]
|
||||
public class SerializerBenchmark : BaseBenchmark
|
||||
{
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[RPlotExporter]
|
||||
[MemoryDiagnoser]
|
||||
public class SerializerBenchmark : BaseBenchmark
|
||||
MqttPacket _packet;
|
||||
ArraySegment<byte> _serializedPacket;
|
||||
MqttV3PacketFormatter _serializer;
|
||||
MqttBufferWriter _bufferWriter;
|
||||
|
||||
[GlobalSetup]
|
||||
public void GlobalSetup()
|
||||
{
|
||||
MqttPacket _packet;
|
||||
ArraySegment<byte> _serializedPacket;
|
||||
IMqttPacketFormatter _serializer;
|
||||
MqttBufferWriter _bufferWriter;
|
||||
|
||||
[GlobalSetup]
|
||||
public void GlobalSetup()
|
||||
_packet = new MqttPublishPacket
|
||||
{
|
||||
_packet = new MqttPublishPacket
|
||||
{
|
||||
Topic = "A"
|
||||
};
|
||||
Topic = "A"
|
||||
};
|
||||
|
||||
_bufferWriter = new MqttBufferWriter(4096, 65535);
|
||||
_serializer = new MqttV3PacketFormatter(_bufferWriter, MqttProtocolVersion.V311);
|
||||
_serializedPacket = _serializer.Encode(_packet).Join();
|
||||
}
|
||||
_bufferWriter = new MqttBufferWriter(4096, 65535);
|
||||
_serializer = new MqttV3PacketFormatter(_bufferWriter, MqttProtocolVersion.V311);
|
||||
_serializedPacket = _serializer.Encode(_packet).Join();
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Serialize_10000_Messages()
|
||||
[Benchmark]
|
||||
public void Serialize10000Messages()
|
||||
{
|
||||
for (var i = 0; i < 10000; i++)
|
||||
{
|
||||
for (var i = 0; i < 10000; i++)
|
||||
{
|
||||
_serializer.Encode(_packet);
|
||||
_bufferWriter.Cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Deserialize_10000_Messages()
|
||||
{
|
||||
var channel = new BenchmarkMqttChannel(_serializedPacket);
|
||||
var reader = new MqttChannelAdapter(channel, new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535)), new MqttNetEventLogger());
|
||||
|
||||
for (var i = 0; i < 10000; i++)
|
||||
{
|
||||
channel.Reset();
|
||||
|
||||
var header = reader.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
|
||||
class BenchmarkMqttChannel : IMqttChannel
|
||||
{
|
||||
readonly ArraySegment<byte> _buffer;
|
||||
int _position;
|
||||
|
||||
public BenchmarkMqttChannel(ArraySegment<byte> buffer)
|
||||
{
|
||||
_buffer = buffer;
|
||||
_position = _buffer.Offset;
|
||||
}
|
||||
|
||||
public string Endpoint { get; } = string.Empty;
|
||||
|
||||
public bool IsSecureConnection { get; } = false;
|
||||
|
||||
public X509Certificate2 ClientCertificate { get; }
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_position = _buffer.Offset;
|
||||
}
|
||||
|
||||
public Task ConnectAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public Task DisconnectAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
Array.Copy(_buffer.Array, _position, buffer, offset, count);
|
||||
_position += count;
|
||||
|
||||
return Task.FromResult(count);
|
||||
}
|
||||
|
||||
public Task WriteAsync(ArraySegment<byte> buffer, bool isEndOfPacket, CancellationToken cancellationToken)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
_serializer.Encode(_packet);
|
||||
_bufferWriter.Cleanup();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Deserialize10000Messages()
|
||||
{
|
||||
var channel = new BenchmarkMqttChannel(_serializedPacket);
|
||||
var reader = new MqttChannelAdapter(channel, new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535)), new MqttNetEventLogger());
|
||||
|
||||
for (var i = 0; i < 10000; i++)
|
||||
{
|
||||
channel.Reset();
|
||||
|
||||
reader.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
|
||||
sealed class BenchmarkMqttChannel : IMqttChannel
|
||||
{
|
||||
readonly ArraySegment<byte> _buffer;
|
||||
int _position;
|
||||
|
||||
public BenchmarkMqttChannel(ArraySegment<byte> buffer)
|
||||
{
|
||||
_buffer = buffer;
|
||||
_position = _buffer.Offset;
|
||||
}
|
||||
|
||||
public EndPoint RemoteEndPoint { get; set; }
|
||||
|
||||
public EndPoint LocalEndPoint { get; set; }
|
||||
|
||||
public bool IsSecureConnection { get; }
|
||||
|
||||
public X509Certificate2 ClientCertificate { get; set; }
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_position = _buffer.Offset;
|
||||
}
|
||||
|
||||
public Task ConnectAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public Task DisconnectAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
Array.Copy(_buffer!.Array!, _position, buffer, offset, count);
|
||||
_position += count;
|
||||
|
||||
return Task.FromResult(count);
|
||||
}
|
||||
|
||||
public Task WriteAsync(ReadOnlySequence<byte> buffer, bool isEndOfPacket, CancellationToken cancellationToken)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,36 +5,34 @@
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using MQTTnet.Tests.Mockups;
|
||||
using MQTTnet.Tests.Server;
|
||||
|
||||
namespace MQTTnet.Benchmarks
|
||||
namespace MQTTnet.Benchmarks;
|
||||
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[RPlotExporter, RankColumn]
|
||||
[MemoryDiagnoser]
|
||||
public class ServerProcessingBenchmark : BaseBenchmark
|
||||
{
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[RPlotExporter, RankColumn]
|
||||
[MemoryDiagnoser]
|
||||
public class ServerProcessingBenchmark : BaseBenchmark
|
||||
[GlobalSetup]
|
||||
public void GlobalSetup()
|
||||
{
|
||||
[GlobalSetup]
|
||||
public void GlobalSetup()
|
||||
{
|
||||
TestEnvironment.EnableLogger = false;
|
||||
}
|
||||
|
||||
[GlobalCleanup]
|
||||
public void GlobalCleanup()
|
||||
{
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Handle_100_000_Messages_In_Server_MqttClient()
|
||||
{
|
||||
new Load_Tests().Handle_100_000_Messages_In_Server().GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
//[Benchmark]
|
||||
public void Handle_100_000_Messages_In_Server_LowLevelMqttClient()
|
||||
{
|
||||
new Load_Tests().Handle_100_000_Messages_In_Low_Level_Client().GetAwaiter().GetResult();
|
||||
}
|
||||
TestEnvironment.EnableLogger = false;
|
||||
}
|
||||
|
||||
[GlobalCleanup]
|
||||
public void GlobalCleanup()
|
||||
{
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Handle_100_000_Messages_In_Server_MqttClient()
|
||||
{
|
||||
//new Load_Tests().Handle_100_000_Messages_In_Server().GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Handle_100_000_Messages_In_Server_LowLevelMqttClient()
|
||||
{
|
||||
//new Load_Tests().Handle_100_000_Messages_In_Low_Level_Client().GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
@@ -3,64 +3,56 @@
|
||||
// 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.Linq;
|
||||
|
||||
namespace MQTTnet.Benchmarks
|
||||
namespace MQTTnet.Benchmarks;
|
||||
|
||||
[MemoryDiagnoser]
|
||||
public class SubscribeBenchmark : BaseBenchmark
|
||||
{
|
||||
[MemoryDiagnoser]
|
||||
public class SubscribeBenchmark : BaseBenchmark
|
||||
const int NumPublishers = 1;
|
||||
const int NumTopicsPerPublisher = 10000;
|
||||
IMqttClient _mqttClient;
|
||||
MqttServer _mqttServer;
|
||||
|
||||
List<string> _topics;
|
||||
|
||||
[GlobalCleanup]
|
||||
public void Cleanup()
|
||||
{
|
||||
MqttServer _mqttServer;
|
||||
IMqttClient _mqttClient;
|
||||
_mqttClient.DisconnectAsync().GetAwaiter().GetResult();
|
||||
_mqttServer.StopAsync().GetAwaiter().GetResult();
|
||||
_mqttServer.Dispose();
|
||||
}
|
||||
|
||||
const int NumPublishers = 1;
|
||||
const int NumTopicsPerPublisher = 10000;
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
TopicGenerator.Generate(NumPublishers, NumTopicsPerPublisher, out var topicsByPublisher, out _, out _);
|
||||
_topics = topicsByPublisher.Values.First();
|
||||
|
||||
List<string> _topics;
|
||||
var serverOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
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();
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Subscribe_10000_Topics()
|
||||
{
|
||||
foreach (var topic in _topics)
|
||||
{
|
||||
TopicGenerator.Generate(NumPublishers, NumTopicsPerPublisher, out var topicsByPublisher, out var singleWildcardTopicsByPublisher, out var multiWildcardTopicsByPublisher);
|
||||
_topics = topicsByPublisher.Values.First();
|
||||
var subscribeOptions = new MqttClientSubscribeOptionsBuilder().WithTopicFilter(topic).Build();
|
||||
|
||||
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();
|
||||
}
|
||||
_mqttClient.SubscribeAsync(subscribeOptions).GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,76 +5,73 @@
|
||||
using System.IO.Pipelines;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using MQTTnet.AspNetCore.Client.Tcp;
|
||||
using MQTTnet.AspNetCore;
|
||||
|
||||
namespace MQTTnet.Benchmarks
|
||||
namespace MQTTnet.Benchmarks;
|
||||
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[MemoryDiagnoser]
|
||||
public class TcpPipesBenchmark : BaseBenchmark
|
||||
{
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[MemoryDiagnoser]
|
||||
public class TcpPipesBenchmark : BaseBenchmark
|
||||
IDuplexPipe _client;
|
||||
IDuplexPipe _server;
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
IDuplexPipe _client;
|
||||
IDuplexPipe _server;
|
||||
var server = new TcpListener(IPAddress.Any, 1883);
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
server.Start(1);
|
||||
|
||||
var task = Task.Run(() => server.AcceptSocket());
|
||||
|
||||
var clientConnection = new SocketConnection(new IPEndPoint(IPAddress.Loopback, 1883));
|
||||
|
||||
clientConnection.StartAsync().GetAwaiter().GetResult();
|
||||
_client = clientConnection.Transport;
|
||||
|
||||
var serverConnection = new SocketConnection(task.GetAwaiter().GetResult());
|
||||
serverConnection.StartAsync().GetAwaiter().GetResult();
|
||||
_server = serverConnection.Transport;
|
||||
}
|
||||
|
||||
|
||||
[Benchmark]
|
||||
public async Task Send_10000_Chunks_Pipe()
|
||||
{
|
||||
var size = 5;
|
||||
var iterations = 10000;
|
||||
|
||||
await Task.WhenAll(WriteAsync(iterations, size), ReadAsync(iterations, size));
|
||||
}
|
||||
|
||||
async Task ReadAsync(int iterations, int size)
|
||||
{
|
||||
await Task.Yield();
|
||||
|
||||
var expected = iterations * size;
|
||||
long read = 0;
|
||||
var input = _client.Input;
|
||||
|
||||
while (read < expected)
|
||||
{
|
||||
var server = new TcpListener(IPAddress.Any, 1883);
|
||||
|
||||
server.Start(1);
|
||||
|
||||
var task = Task.Run(() => server.AcceptSocket());
|
||||
|
||||
var clientConnection = new TcpConnection(new IPEndPoint(IPAddress.Loopback, 1883));
|
||||
|
||||
clientConnection.StartAsync().GetAwaiter().GetResult();
|
||||
_client = clientConnection.Transport;
|
||||
|
||||
var serverConnection = new TcpConnection(task.GetAwaiter().GetResult());
|
||||
serverConnection.StartAsync().GetAwaiter().GetResult();
|
||||
_server = serverConnection.Transport;
|
||||
}
|
||||
|
||||
|
||||
[Benchmark]
|
||||
public async Task Send_10000_Chunks_Pipe()
|
||||
{
|
||||
var size = 5;
|
||||
var iterations = 10000;
|
||||
|
||||
await Task.WhenAll(WriteAsync(iterations, size), ReadAsync(iterations, size));
|
||||
}
|
||||
|
||||
async Task ReadAsync(int iterations, int size)
|
||||
{
|
||||
await Task.Yield();
|
||||
|
||||
var expected = iterations * size;
|
||||
long read = 0;
|
||||
var input = _client.Input;
|
||||
|
||||
while (read < expected)
|
||||
{
|
||||
var readresult = await input.ReadAsync(CancellationToken.None).ConfigureAwait(false);
|
||||
input.AdvanceTo(readresult.Buffer.End);
|
||||
read += readresult.Buffer.Length;
|
||||
}
|
||||
}
|
||||
|
||||
async Task WriteAsync(int iterations, int size)
|
||||
{
|
||||
await Task.Yield();
|
||||
|
||||
var output = _server.Output;
|
||||
|
||||
for (var i = 0; i < iterations; i++)
|
||||
{
|
||||
await output.WriteAsync(new byte[size], CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
var readresult = await input.ReadAsync(CancellationToken.None).ConfigureAwait(false);
|
||||
input.AdvanceTo(readresult.Buffer.End);
|
||||
read += readresult.Buffer.Length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async Task WriteAsync(int iterations, int size)
|
||||
{
|
||||
await Task.Yield();
|
||||
|
||||
var output = _server.Output;
|
||||
|
||||
for (var i = 0; i < iterations; i++)
|
||||
{
|
||||
await output.WriteAsync(new byte[size], CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,276 +2,32 @@
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
|
||||
namespace MQTTnet.Benchmarks
|
||||
namespace MQTTnet.Benchmarks;
|
||||
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[RPlotExporter]
|
||||
[MemoryDiagnoser]
|
||||
public class TopicFilterComparerBenchmark : BaseBenchmark
|
||||
{
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[RPlotExporter]
|
||||
[MemoryDiagnoser]
|
||||
public class TopicFilterComparerBenchmark : BaseBenchmark
|
||||
readonly string _longTopic =
|
||||
"AAAAAAAAAAAAAssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssshhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC";
|
||||
|
||||
[Benchmark]
|
||||
public void MqttTopicFilterComparer_10000_LoopMethod()
|
||||
{
|
||||
static readonly char[] TopicLevelSeparator = { '/' };
|
||||
|
||||
readonly string _longTopic =
|
||||
"AAAAAAAAAAAAAssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssshhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC";
|
||||
|
||||
[Benchmark]
|
||||
public void MqttTopicFilterComparer_10000_LoopMethod()
|
||||
for (var i = 0; i < 100000; i++)
|
||||
{
|
||||
for (var i = 0; i < 100000; i++)
|
||||
{
|
||||
MqttTopicFilterComparer.Compare("sport/tennis/player1", "sport/#");
|
||||
MqttTopicFilterComparer.Compare("sport/tennis/player1/ranking", "sport/#/ranking");
|
||||
MqttTopicFilterComparer.Compare("sport/tennis/player1/score/wimbledon", "sport/+/player1/#");
|
||||
MqttTopicFilterComparer.Compare("sport/tennis/player1", "sport/tennis/+");
|
||||
MqttTopicFilterComparer.Compare("/finance", "+/+");
|
||||
MqttTopicFilterComparer.Compare("/finance", "/+");
|
||||
MqttTopicFilterComparer.Compare("/finance", "+");
|
||||
MqttTopicFilterComparer.Compare(_longTopic, _longTopic);
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void MqttTopicFilterComparer_10000_LoopMethod_Without_Pointer()
|
||||
{
|
||||
for (var i = 0; i < 100000; i++)
|
||||
{
|
||||
MqttTopicFilterComparerWithoutPointer.Compare("sport/tennis/player1", "sport/#");
|
||||
MqttTopicFilterComparerWithoutPointer.Compare("sport/tennis/player1/ranking", "sport/#/ranking");
|
||||
MqttTopicFilterComparerWithoutPointer.Compare("sport/tennis/player1/score/wimbledon", "sport/+/player1/#");
|
||||
MqttTopicFilterComparerWithoutPointer.Compare("sport/tennis/player1", "sport/tennis/+");
|
||||
MqttTopicFilterComparerWithoutPointer.Compare("/finance", "+/+");
|
||||
MqttTopicFilterComparerWithoutPointer.Compare("/finance", "/+");
|
||||
MqttTopicFilterComparerWithoutPointer.Compare("/finance", "+");
|
||||
MqttTopicFilterComparerWithoutPointer.Compare(_longTopic, _longTopic);
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void MqttTopicFilterComparer_10000_StringSplitMethod()
|
||||
{
|
||||
for (var i = 0; i < 100000; i++)
|
||||
{
|
||||
LegacyMethodByStringSplit("sport/tennis/player1", "sport/#");
|
||||
LegacyMethodByStringSplit("sport/tennis/player1/ranking", "sport/#/ranking");
|
||||
LegacyMethodByStringSplit("sport/tennis/player1/score/wimbledon", "sport/+/player1/#");
|
||||
LegacyMethodByStringSplit("sport/tennis/player1", "sport/tennis/+");
|
||||
LegacyMethodByStringSplit("/finance", "+/+");
|
||||
LegacyMethodByStringSplit("/finance", "/+");
|
||||
LegacyMethodByStringSplit("/finance", "+");
|
||||
MqttTopicFilterComparer.Compare(_longTopic, _longTopic);
|
||||
}
|
||||
}
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
}
|
||||
|
||||
static bool LegacyMethodByStringSplit(string topic, string filter)
|
||||
{
|
||||
if (topic == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(topic));
|
||||
}
|
||||
|
||||
if (filter == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(filter));
|
||||
}
|
||||
|
||||
if (string.Equals(topic, filter, StringComparison.Ordinal))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var fragmentsTopic = topic.Split(TopicLevelSeparator, StringSplitOptions.None);
|
||||
var fragmentsFilter = filter.Split(TopicLevelSeparator, StringSplitOptions.None);
|
||||
|
||||
// # > In either case it MUST be the last character specified in the Topic Filter [MQTT-4.7.1-2].
|
||||
for (var i = 0; i < fragmentsFilter.Length; i++)
|
||||
{
|
||||
if (fragmentsFilter[i] == "+")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (fragmentsFilter[i] == "#")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (i >= fragmentsTopic.Length)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!string.Equals(fragmentsFilter[i], fragmentsTopic[i], StringComparison.Ordinal))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return fragmentsTopic.Length == fragmentsFilter.Length;
|
||||
}
|
||||
|
||||
public static class MqttTopicFilterComparerWithoutPointer
|
||||
{
|
||||
public const char LevelSeparator = '/';
|
||||
public const char MultiLevelWildcard = '#';
|
||||
public const char SingleLevelWildcard = '+';
|
||||
public const char ReservedTopicPrefix = '$';
|
||||
|
||||
public static MqttTopicFilterCompareResult Compare(string topic, string filter)
|
||||
{
|
||||
if (string.IsNullOrEmpty(topic))
|
||||
{
|
||||
return MqttTopicFilterCompareResult.TopicInvalid;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(filter))
|
||||
{
|
||||
return MqttTopicFilterCompareResult.FilterInvalid;
|
||||
}
|
||||
|
||||
var filterOffset = 0;
|
||||
var filterLength = filter.Length;
|
||||
|
||||
var topicOffset = 0;
|
||||
var topicLength = topic.Length;
|
||||
|
||||
var topicPointer = topic;
|
||||
var filterPointer = filter;
|
||||
|
||||
var isMultiLevelFilter = filterPointer[filterLength - 1] == MultiLevelWildcard;
|
||||
var isReservedTopic = topicPointer[0] == ReservedTopicPrefix;
|
||||
|
||||
if (isReservedTopic && filterLength == 1 && isMultiLevelFilter)
|
||||
{
|
||||
// It is not allowed to receive i.e. '$foo/bar' with filter '#'.
|
||||
return MqttTopicFilterCompareResult.NoMatch;
|
||||
}
|
||||
|
||||
if (isReservedTopic && filterPointer[0] == SingleLevelWildcard)
|
||||
{
|
||||
// It is not allowed to receive i.e. '$SYS/monitor/Clients' with filter '+/monitor/Clients'.
|
||||
return MqttTopicFilterCompareResult.NoMatch;
|
||||
}
|
||||
|
||||
if (filterLength == 1 && isMultiLevelFilter)
|
||||
{
|
||||
// Filter '#' matches basically everything.
|
||||
return MqttTopicFilterCompareResult.IsMatch;
|
||||
}
|
||||
|
||||
// Go through the filter char by char.
|
||||
while (filterOffset < filterLength && topicOffset < topicLength)
|
||||
{
|
||||
// Check if the current char is a multi level wildcard. The char is only allowed
|
||||
// at the very las position.
|
||||
if (filterPointer[filterOffset] == MultiLevelWildcard && filterOffset != filterLength - 1)
|
||||
{
|
||||
return MqttTopicFilterCompareResult.FilterInvalid;
|
||||
}
|
||||
|
||||
if (filterPointer[filterOffset] == topicPointer[topicOffset])
|
||||
{
|
||||
if (topicOffset == topicLength - 1)
|
||||
{
|
||||
// Check for e.g. "foo" matching "foo/#"
|
||||
if (filterOffset == filterLength - 3 && filterPointer[filterOffset + 1] == LevelSeparator && isMultiLevelFilter)
|
||||
{
|
||||
return MqttTopicFilterCompareResult.IsMatch;
|
||||
}
|
||||
|
||||
// Check for e.g. "foo/" matching "foo/#"
|
||||
if (filterOffset == filterLength - 2 && filterPointer[filterOffset] == LevelSeparator && isMultiLevelFilter)
|
||||
{
|
||||
return MqttTopicFilterCompareResult.IsMatch;
|
||||
}
|
||||
}
|
||||
|
||||
filterOffset++;
|
||||
topicOffset++;
|
||||
|
||||
// Check if the end was reached and i.e. "foo/bar" matches "foo/bar"
|
||||
if (filterOffset == filterLength && topicOffset == topicLength)
|
||||
{
|
||||
return MqttTopicFilterCompareResult.IsMatch;
|
||||
}
|
||||
|
||||
var endOfTopic = topicOffset == topicLength;
|
||||
|
||||
if (endOfTopic && filterOffset == filterLength - 1 && filterPointer[filterOffset] == SingleLevelWildcard)
|
||||
{
|
||||
if (filterOffset > 0 && filterPointer[filterOffset - 1] != LevelSeparator)
|
||||
{
|
||||
return MqttTopicFilterCompareResult.FilterInvalid;
|
||||
}
|
||||
|
||||
return MqttTopicFilterCompareResult.IsMatch;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (filterPointer[filterOffset] == SingleLevelWildcard)
|
||||
{
|
||||
// Check for invalid "+foo" or "a/+foo" subscription
|
||||
if (filterOffset > 0 && filterPointer[filterOffset - 1] != LevelSeparator)
|
||||
{
|
||||
return MqttTopicFilterCompareResult.FilterInvalid;
|
||||
}
|
||||
|
||||
// Check for bad "foo+" or "foo+/a" subscription
|
||||
if (filterOffset < filterLength - 1 && filterPointer[filterOffset + 1] != LevelSeparator)
|
||||
{
|
||||
return MqttTopicFilterCompareResult.FilterInvalid;
|
||||
}
|
||||
|
||||
filterOffset++;
|
||||
while (topicOffset < topicLength && topicPointer[topicOffset] != LevelSeparator)
|
||||
{
|
||||
topicOffset++;
|
||||
}
|
||||
|
||||
if (topicOffset == topicLength && filterOffset == filterLength)
|
||||
{
|
||||
return MqttTopicFilterCompareResult.IsMatch;
|
||||
}
|
||||
}
|
||||
else if (filterPointer[filterOffset] == MultiLevelWildcard)
|
||||
{
|
||||
if (filterOffset > 0 && filterPointer[filterOffset - 1] != LevelSeparator)
|
||||
{
|
||||
return MqttTopicFilterCompareResult.FilterInvalid;
|
||||
}
|
||||
|
||||
if (filterOffset + 1 != filterLength)
|
||||
{
|
||||
return MqttTopicFilterCompareResult.FilterInvalid;
|
||||
}
|
||||
|
||||
return MqttTopicFilterCompareResult.IsMatch;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Check for e.g. "foo/bar" matching "foo/+/#".
|
||||
if (filterOffset > 0 && filterOffset + 2 == filterLength && topicOffset == topicLength && filterPointer[filterOffset - 1] == SingleLevelWildcard &&
|
||||
filterPointer[filterOffset] == LevelSeparator && isMultiLevelFilter)
|
||||
{
|
||||
return MqttTopicFilterCompareResult.IsMatch;
|
||||
}
|
||||
|
||||
return MqttTopicFilterCompareResult.NoMatch;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return MqttTopicFilterCompareResult.NoMatch;
|
||||
}
|
||||
MqttTopicFilterComparer.Compare("sport/tennis/player1", "sport/#");
|
||||
MqttTopicFilterComparer.Compare("sport/tennis/player1/ranking", "sport/#/ranking");
|
||||
MqttTopicFilterComparer.Compare("sport/tennis/player1/score/wimbledon", "sport/+/player1/#");
|
||||
MqttTopicFilterComparer.Compare("sport/tennis/player1", "sport/tennis/+");
|
||||
MqttTopicFilterComparer.Compare("/finance", "+/+");
|
||||
MqttTopicFilterComparer.Compare("/finance", "/+");
|
||||
MqttTopicFilterComparer.Compare("/finance", "+");
|
||||
MqttTopicFilterComparer.Compare(_longTopic, _longTopic);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,84 +1,80 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
namespace MQTTnet.Benchmarks;
|
||||
|
||||
namespace MQTTnet.Benchmarks
|
||||
public class TopicGenerator
|
||||
{
|
||||
public class TopicGenerator
|
||||
public static void Generate(
|
||||
int numPublishers,
|
||||
int numTopicsPerPublisher,
|
||||
out Dictionary<string, List<string>> topicsByPublisher,
|
||||
out Dictionary<string, List<string>> singleWildcardTopicsByPublisher,
|
||||
out Dictionary<string, List<string>> multiWildcardTopicsByPublisher)
|
||||
{
|
||||
public static void Generate(
|
||||
int numPublishers,
|
||||
int numTopicsPerPublisher,
|
||||
out Dictionary<string, List<string>> topicsByPublisher,
|
||||
out Dictionary<string, List<string>> singleWildcardTopicsByPublisher,
|
||||
out Dictionary<string, List<string>> multiWildcardTopicsByPublisher)
|
||||
topicsByPublisher = new Dictionary<string, List<string>>();
|
||||
singleWildcardTopicsByPublisher = new Dictionary<string, List<string>>();
|
||||
multiWildcardTopicsByPublisher = new Dictionary<string, List<string>>();
|
||||
|
||||
// Find some reasonable distribution across three topic levels
|
||||
var topicsPerLevel = (int)Math.Pow(numTopicsPerPublisher, 1.0 / 3.0);
|
||||
if (topicsPerLevel <= 0)
|
||||
{
|
||||
topicsByPublisher = new Dictionary<string, List<string>>();
|
||||
singleWildcardTopicsByPublisher = new Dictionary<string, List<string>>();
|
||||
multiWildcardTopicsByPublisher = new Dictionary<string, List<string>>();
|
||||
topicsPerLevel = 1;
|
||||
}
|
||||
|
||||
// Find some reasonable distribution across three topic levels
|
||||
var topicsPerLevel = (int)Math.Pow(numTopicsPerPublisher, 1.0 / 3.0);
|
||||
if (topicsPerLevel <= 0)
|
||||
var numLevel1Topics = topicsPerLevel;
|
||||
var numLevel2Topics = topicsPerLevel;
|
||||
|
||||
var maxNumLevel3Topics = 1 + (int)((double)numTopicsPerPublisher / numLevel1Topics / numLevel2Topics);
|
||||
if (maxNumLevel3Topics <= 0)
|
||||
{
|
||||
maxNumLevel3Topics = 1;
|
||||
}
|
||||
|
||||
for (var p = 0; p < numPublishers; ++p)
|
||||
{
|
||||
var publisherTopicCount = 0;
|
||||
var publisherName = "pub" + p;
|
||||
for (var l1 = 0; l1 < numLevel1Topics; ++l1)
|
||||
{
|
||||
topicsPerLevel = 1;
|
||||
}
|
||||
|
||||
var numLevel1Topics = topicsPerLevel;
|
||||
var numLevel2Topics = topicsPerLevel;
|
||||
|
||||
var maxNumLevel3Topics = 1 + (int)((double)numTopicsPerPublisher / numLevel1Topics / numLevel2Topics);
|
||||
if (maxNumLevel3Topics <= 0)
|
||||
{
|
||||
maxNumLevel3Topics = 1;
|
||||
}
|
||||
|
||||
for (var p = 0; p < numPublishers; ++p)
|
||||
{
|
||||
var publisherTopicCount = 0;
|
||||
var publisherName = "pub" + p;
|
||||
for (var l1 = 0; l1 < numLevel1Topics; ++l1)
|
||||
for (var l2 = 0; l2 < numLevel2Topics; ++l2)
|
||||
{
|
||||
for (var l2 = 0; l2 < numLevel2Topics; ++l2)
|
||||
for (var l3 = 0; l3 < maxNumLevel3Topics; ++l3)
|
||||
{
|
||||
for (var l3 = 0; l3 < maxNumLevel3Topics; ++l3)
|
||||
if (publisherTopicCount >= numTopicsPerPublisher)
|
||||
{
|
||||
if (publisherTopicCount >= numTopicsPerPublisher)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
var topic = $"{publisherName}/building{l1 + 1}/level{l2 + 1}/sensor{l3 + 1}";
|
||||
AddPublisherTopic(publisherName, topic, topicsByPublisher);
|
||||
|
||||
if (l2 == 0)
|
||||
{
|
||||
var singleWildcardTopic = $"{publisherName}/building{l1 + 1}/+/sensor{l3 + 1}";
|
||||
AddPublisherTopic(publisherName, singleWildcardTopic, singleWildcardTopicsByPublisher);
|
||||
}
|
||||
|
||||
if (l1 == 0 && l3 == 0)
|
||||
{
|
||||
var multiWildcardTopic = $"{publisherName}/+/level{l2 + 1}/+";
|
||||
AddPublisherTopic(publisherName, multiWildcardTopic, multiWildcardTopicsByPublisher);
|
||||
}
|
||||
|
||||
++publisherTopicCount;
|
||||
break;
|
||||
}
|
||||
|
||||
var topic = $"{publisherName}/building{l1 + 1}/level{l2 + 1}/sensor{l3 + 1}";
|
||||
AddPublisherTopic(publisherName, topic, topicsByPublisher);
|
||||
|
||||
if (l2 == 0)
|
||||
{
|
||||
var singleWildcardTopic = $"{publisherName}/building{l1 + 1}/+/sensor{l3 + 1}";
|
||||
AddPublisherTopic(publisherName, singleWildcardTopic, singleWildcardTopicsByPublisher);
|
||||
}
|
||||
|
||||
if (l1 == 0 && l3 == 0)
|
||||
{
|
||||
var multiWildcardTopic = $"{publisherName}/+/level{l2 + 1}/+";
|
||||
AddPublisherTopic(publisherName, multiWildcardTopic, multiWildcardTopicsByPublisher);
|
||||
}
|
||||
|
||||
++publisherTopicCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void AddPublisherTopic(string publisherName, string topic, Dictionary<string, List<string>> topicsByPublisher)
|
||||
static void AddPublisherTopic(string publisherName, string topic, Dictionary<string, List<string>> topicsByPublisher)
|
||||
{
|
||||
List<string> topicList;
|
||||
if (!topicsByPublisher.TryGetValue(publisherName, out topicList))
|
||||
{
|
||||
List<string> topicList;
|
||||
if (!topicsByPublisher.TryGetValue(publisherName, out topicList))
|
||||
{
|
||||
topicList = new List<string>();
|
||||
topicsByPublisher.Add(publisherName, topicList);
|
||||
}
|
||||
|
||||
topicList.Add(topic);
|
||||
topicList = new List<string>();
|
||||
topicsByPublisher.Add(publisherName, topicList);
|
||||
}
|
||||
|
||||
topicList.Add(topic);
|
||||
}
|
||||
}
|
||||
@@ -3,62 +3,53 @@
|
||||
// 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.Linq;
|
||||
|
||||
namespace MQTTnet.Benchmarks
|
||||
namespace MQTTnet.Benchmarks;
|
||||
|
||||
[MemoryDiagnoser]
|
||||
public class UnsubscribeBenchmark : BaseBenchmark
|
||||
{
|
||||
[MemoryDiagnoser]
|
||||
public class UnsubscribeBenchmark : BaseBenchmark
|
||||
const int NumPublishers = 1;
|
||||
const int NumTopicsPerPublisher = 10000;
|
||||
|
||||
IMqttClient _mqttClient;
|
||||
MqttServer _mqttServer;
|
||||
List<string> _topics;
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
MqttServer _mqttServer;
|
||||
IMqttClient _mqttClient;
|
||||
TopicGenerator.Generate(NumPublishers, NumTopicsPerPublisher, out var topicsByPublisher, out _, out _);
|
||||
_topics = topicsByPublisher.Values.First();
|
||||
|
||||
const int NumPublishers = 1;
|
||||
const int NumTopicsPerPublisher = 10000;
|
||||
var serverOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
|
||||
|
||||
List<string> _topics;
|
||||
var serverFactory = new MqttServerFactory();
|
||||
_mqttServer = serverFactory.CreateMqttServer(serverOptions);
|
||||
var clientFactory = new MqttClientFactory();
|
||||
_mqttClient = clientFactory.CreateMqttClient();
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
_mqttServer.StartAsync().GetAwaiter().GetResult();
|
||||
|
||||
var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("localhost").Build();
|
||||
|
||||
_mqttClient.ConnectAsync(clientOptions).GetAwaiter().GetResult();
|
||||
|
||||
foreach (var topic in _topics)
|
||||
{
|
||||
TopicGenerator.Generate(NumPublishers, NumTopicsPerPublisher, out var topicsByPublisher, out var singleWildcardTopicsByPublisher, out var multiWildcardTopicsByPublisher);
|
||||
_topics = topicsByPublisher.Values.First();
|
||||
|
||||
var serverOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
|
||||
|
||||
var factory = new MqttFactory();
|
||||
_mqttServer = factory.CreateMqttServer(serverOptions);
|
||||
_mqttClient = factory.CreateMqttClient();
|
||||
|
||||
_mqttServer.StartAsync().GetAwaiter().GetResult();
|
||||
|
||||
var clientOptions = new MqttClientOptionsBuilder()
|
||||
.WithTcpServer("localhost").Build();
|
||||
|
||||
_mqttClient.ConnectAsync(clientOptions).GetAwaiter().GetResult();
|
||||
|
||||
foreach (var topic in _topics)
|
||||
{
|
||||
var subscribeOptions = new MqttClientSubscribeOptionsBuilder()
|
||||
.WithTopicFilter(topic)
|
||||
.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();
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +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;
|
||||
|
||||
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; }
|
||||
}
|
||||
}
|
||||
@@ -1,18 +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;
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public sealed class ApplicationMessageSkippedEventArgs : EventArgs
|
||||
{
|
||||
public ApplicationMessageSkippedEventArgs(ManagedMqttApplicationMessage applicationMessage)
|
||||
{
|
||||
ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
|
||||
}
|
||||
|
||||
public ManagedMqttApplicationMessage ApplicationMessage { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
using System;
|
||||
using MQTTnet.Client;
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public sealed class ConnectingFailedEventArgs : EventArgs
|
||||
{
|
||||
public ConnectingFailedEventArgs(MqttClientConnectResult connectResult, Exception exception)
|
||||
{
|
||||
ConnectResult = connectResult;
|
||||
Exception = exception;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is null when the connection was failing and the server was not reachable.
|
||||
/// </summary>
|
||||
public MqttClientConnectResult ConnectResult { get; }
|
||||
|
||||
public Exception Exception { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
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;
|
||||
|
||||
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(ICollection<MqttTopicFilter> topicFilters);
|
||||
|
||||
Task UnsubscribeAsync(ICollection<string> topics);
|
||||
}
|
||||
}
|
||||
@@ -1,16 +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.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public interface IManagedMqttClientStorage
|
||||
{
|
||||
Task SaveQueuedMessagesAsync(IList<ManagedMqttApplicationMessage> messages);
|
||||
|
||||
Task<IList<ManagedMqttApplicationMessage>> LoadQueuedMessagesAsync();
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<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.ManagedClient</AssemblyName>
|
||||
<RootNamespace>MQTTnet.Extensions.ManagedClient</RootNamespace>
|
||||
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
||||
<Company>The contributors of MQTTnet</Company>
|
||||
<Product>MQTTnet</Product>
|
||||
<Description>This is an extension library which provides a managed MQTT client with additional features using MQTTnet.</Description>
|
||||
<Authors>The contributors of MQTTnet</Authors>
|
||||
<PackageId>MQTTnet.Extensions.ManagedClient</PackageId>
|
||||
<SignAssembly>false</SignAssembly>
|
||||
<DelaySign>false</DelaySign>
|
||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||
<IncludeSymbols>true</IncludeSymbols>
|
||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||
<Copyright>Copyright (c) .NET Foundation and Contributors</Copyright>
|
||||
<PackageProjectUrl>https://github.com/dotnet/MQTTnet</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/dotnet/MQTTnet.git</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<PackageTags>MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin Blazor</PackageTags>
|
||||
<NeutralLanguage>en-US</NeutralLanguage>
|
||||
<EnableNETAnalyzers>false</EnableNETAnalyzers>
|
||||
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
|
||||
<PackageIcon>nuget.png</PackageIcon>
|
||||
<EmbedUntrackedSources>true</EmbedUntrackedSources>
|
||||
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<PackageReleaseNotes>For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).</PackageReleaseNotes>
|
||||
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<NoWarn>1591;NETSDK1138</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0'">
|
||||
<CopyLocalLockFileAssemblies>false</CopyLocalLockFileAssemblies>
|
||||
<NugetTargetMoniker>UAP,Version=v10.0</NugetTargetMoniker>
|
||||
<TargetPlatformIdentifier>UAP</TargetPlatformIdentifier>
|
||||
<TargetPlatformVersion>10.0.18362.0</TargetPlatformVersion>
|
||||
<TargetPlatformMinVersion>10.0.10240.0</TargetPlatformMinVersion>
|
||||
<TargetFrameworkIdentifier>.NETCore</TargetFrameworkIdentifier>
|
||||
<TargetFrameworkVersion>v5.0</TargetFrameworkVersion>
|
||||
<DefineConstants>$(DefineConstants);WINDOWS_UWP</DefineConstants>
|
||||
<DefaultLanguage>en</DefaultLanguage>
|
||||
<LanguageTargets>$(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets</LanguageTargets>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="..\..\Images\nuget.png">
|
||||
<Pack>True</Pack>
|
||||
<PackagePath>\</PackagePath>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MQTTnet\MQTTnet.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,15 +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;
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public class ManagedMqttApplicationMessage
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
|
||||
public MqttApplicationMessage ApplicationMessage { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,51 +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;
|
||||
|
||||
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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,764 +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.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 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 MqttNetSourceLogger _logger;
|
||||
readonly BlockingQueue<ManagedMqttApplicationMessage> _messageQueue = new BlockingQueue<ManagedMqttApplicationMessage>();
|
||||
|
||||
readonly AsyncLock _messageQueueLock = new AsyncLock();
|
||||
|
||||
/// <summary>
|
||||
/// The subscriptions are managed in 2 separate buckets:
|
||||
/// <see
|
||||
/// cref="_subscriptions" />
|
||||
/// and
|
||||
/// <see
|
||||
/// cref="_unsubscriptions" />
|
||||
/// are processed during normal operation
|
||||
/// and are moved to the
|
||||
/// <see
|
||||
/// cref="_reconnectSubscriptions" />
|
||||
/// when they get processed. They can be accessed by
|
||||
/// any thread and are therefore mutex'ed.
|
||||
/// <see
|
||||
/// cref="_reconnectSubscriptions" />
|
||||
/// get sent to the broker
|
||||
/// at reconnect and are solely owned by
|
||||
/// <see
|
||||
/// cref="MaintainConnectionAsync" />
|
||||
/// .
|
||||
/// </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<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 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(ICollection<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(ICollection<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;
|
||||
}
|
||||
|
||||
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 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 = new CancellationTokenSource(Options.ClientOptions.Timeout))
|
||||
{
|
||||
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).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()
|
||||
{
|
||||
_logger.Info("Publishing subscriptions at reconnect");
|
||||
|
||||
List<MqttTopicFilter> topicFilters = null;
|
||||
|
||||
try
|
||||
{
|
||||
if (_reconnectSubscriptions.Any())
|
||||
{
|
||||
topicFilters = new List<MqttTopicFilter>();
|
||||
|
||||
foreach (var sub in _reconnectSubscriptions)
|
||||
{
|
||||
topicFilters.Add(sub.Value);
|
||||
|
||||
if (topicFilters.Count == Options.MaxTopicFiltersInSubscribeUnsubscribePackets)
|
||||
{
|
||||
await SendSubscribeUnsubscribe(topicFilters, null).ConfigureAwait(false);
|
||||
topicFilters.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
await SendSubscribeUnsubscribe(topicFilters, null).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
await HandleSubscriptionExceptionAsync(exception, 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;
|
||||
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)
|
||||
{
|
||||
await SendSubscribeUnsubscribe(addedTopicFilters, null).ConfigureAwait(false);
|
||||
addedTopicFilters.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
await SendSubscribeUnsubscribe(addedTopicFilters, null).ConfigureAwait(false);
|
||||
|
||||
var removedTopicFilters = new List<string>();
|
||||
foreach (var unSub in unsubscriptions)
|
||||
{
|
||||
removedTopicFilters.Add(unSub);
|
||||
|
||||
if (removedTopicFilters.Count == Options.MaxTopicFiltersInSubscribeUnsubscribePackets)
|
||||
{
|
||||
await SendSubscribeUnsubscribe(null, removedTopicFilters).ConfigureAwait(false);
|
||||
removedTopicFilters.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
await SendSubscribeUnsubscribe(null, removedTopicFilters).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
async Task<ReconnectionResult> ReconnectIfRequiredAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (InternalClient.IsConnected)
|
||||
{
|
||||
return ReconnectionResult.StillConnected;
|
||||
}
|
||||
|
||||
MqttClientConnectResult connectResult = null;
|
||||
try
|
||||
{
|
||||
using (var connectTimeout = new CancellationTokenSource(Options.ClientOptions.Timeout))
|
||||
{
|
||||
connectResult = await InternalClient.ConnectAsync(Options.ClientOptions, connectTimeout.Token).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (connectResult.ResultCode != MqttClientConnectResultCode.Success)
|
||||
{
|
||||
throw new MqttCommunicationException($"Client connected but server denied connection with reason '{connectResult.ResultCode}'.");
|
||||
}
|
||||
|
||||
return connectResult.IsSessionPresent ? ReconnectionResult.Recovered : ReconnectionResult.Reconnected;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
await _connectingFailedEvent.InvokeAsync(new ConnectingFailedEventArgs(connectResult, exception));
|
||||
return ReconnectionResult.NotConnected;
|
||||
}
|
||||
}
|
||||
|
||||
async Task SendSubscribeUnsubscribe(List<MqttTopicFilter> addedSubscriptions, List<string> removedSubscriptions)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (removedSubscriptions != null && removedSubscriptions.Any())
|
||||
{
|
||||
var unsubscribeOptionsBuilder = new MqttClientUnsubscribeOptionsBuilder();
|
||||
|
||||
foreach (var removedSubscription in removedSubscriptions)
|
||||
{
|
||||
unsubscribeOptionsBuilder.WithTopicFilter(removedSubscription);
|
||||
}
|
||||
|
||||
await InternalClient.UnsubscribeAsync(unsubscribeOptionsBuilder.Build()).ConfigureAwait(false);
|
||||
|
||||
//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);
|
||||
}
|
||||
|
||||
await InternalClient.SubscribeAsync(subscribeOptionsBuilder.Build()).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
await HandleSubscriptionExceptionAsync(exception, addedSubscriptions, removedSubscriptions).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
void StartPublishing()
|
||||
{
|
||||
StopPublishing();
|
||||
|
||||
var cancellationTokenSource = new CancellationTokenSource();
|
||||
var cancellationToken = cancellationTokenSource.Token;
|
||||
_publishingCancellationToken = cancellationTokenSource;
|
||||
|
||||
Task.Run(() => PublishQueuedMessagesAsync(cancellationToken), cancellationToken).RunInBackground(_logger);
|
||||
}
|
||||
|
||||
void StopMaintainingConnection()
|
||||
{
|
||||
try
|
||||
{
|
||||
_connectionCancellationToken?.Cancel(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_connectionCancellationToken?.Dispose();
|
||||
_connectionCancellationToken = null;
|
||||
}
|
||||
}
|
||||
|
||||
void StopPublishing()
|
||||
{
|
||||
try
|
||||
{
|
||||
_publishingCancellationToken?.Cancel(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_publishingCancellationToken?.Dispose();
|
||||
_publishingCancellationToken = null;
|
||||
}
|
||||
}
|
||||
|
||||
async Task TryMaintainConnectionAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
var oldConnectionState = InternalClient.IsConnected;
|
||||
var connectionState = await ReconnectIfRequiredAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (connectionState == ReconnectionResult.NotConnected)
|
||||
{
|
||||
StopPublishing();
|
||||
await Task.Delay(Options.AutoReconnectDelay, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else if (connectionState == ReconnectionResult.Reconnected)
|
||||
{
|
||||
await PublishReconnectSubscriptionsAsync().ConfigureAwait(false);
|
||||
StartPublishing();
|
||||
}
|
||||
else if (connectionState == ReconnectionResult.Recovered)
|
||||
{
|
||||
StartPublishing();
|
||||
}
|
||||
else if (connectionState == ReconnectionResult.StillConnected)
|
||||
{
|
||||
await PublishSubscriptionsAsync(Options.ConnectionCheckInterval, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (oldConnectionState != InternalClient.IsConnected)
|
||||
{
|
||||
await _connectionStateChangedEvent.InvokeAsync(EventArgs.Empty).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
catch (MqttCommunicationException exception)
|
||||
{
|
||||
_logger.Warning(exception, "Communication error while maintaining connection.");
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.Error(exception, "Error exception while maintaining connection.");
|
||||
}
|
||||
}
|
||||
|
||||
async Task TryPublishQueuedMessageAsync(ManagedMqttApplicationMessage message)
|
||||
{
|
||||
Exception transmitException = null;
|
||||
try
|
||||
{
|
||||
await InternalClient.PublishAsync(message.ApplicationMessage).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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,78 +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.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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,31 +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 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;
|
||||
}
|
||||
}
|
||||
@@ -1,97 +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 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,66 +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.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,34 +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 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; }
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user