mirror of
https://github.com/dotnet/MQTTnet.git
synced 2026-10-16 17:23:04 +08:00
Compare commits
229
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
0d3dd96f2a | ||
|
|
e0660ba887 | ||
|
|
5965ded488 | ||
|
|
fc31c3077c | ||
|
|
e604345e65 | ||
|
|
111a0d69cf | ||
|
|
1a08ec17ba | ||
|
|
c537d290c3 | ||
|
|
2180f59725 | ||
|
|
b1dc8607af | ||
|
|
101898695e | ||
|
|
9295626503 | ||
|
|
18614d4e81 | ||
|
|
560e80455f | ||
|
|
a97e88be29 | ||
|
|
3b5667c882 | ||
|
|
0790e54794 | ||
|
|
94f9fac2bd | ||
|
|
8ad86928f1 | ||
|
|
d8e5936d45 | ||
|
|
caa717544b | ||
|
|
66b77f8d6d | ||
|
|
ab5264c8ca | ||
|
|
db12309bae | ||
|
|
2953e0dcc5 | ||
|
|
1b18ac9fe3 | ||
|
|
2cdad6622a | ||
|
|
7601d11a7a | ||
|
|
0bb4c6ef09 | ||
|
|
449624fd72 | ||
|
|
99f4f46062 | ||
|
|
b08d11c069 | ||
|
|
ac41e838ec | ||
|
|
836ddf3bbc | ||
|
|
0e815c626c | ||
|
|
4b7d035e9e | ||
|
|
cb3c44f48d | ||
|
|
8b3b9d33de | ||
|
|
59d99509c8 | ||
|
|
2933b76237 | ||
|
|
36c6d8974e | ||
|
|
d6cab76728 | ||
|
|
b4f740f338 | ||
|
|
a004de25ac | ||
|
|
d0584413ca | ||
|
|
ba57537108 | ||
|
|
6dcb110737 | ||
|
|
b599a832da | ||
|
|
9cbd5dd778 | ||
|
|
ed31c51237 | ||
|
|
e01707d25e | ||
|
|
73f118fe1c | ||
|
|
0234918f30 | ||
|
|
c90ba7e061 | ||
|
|
2aa4219838 | ||
|
|
b98417ab39 | ||
|
|
ead58dd606 | ||
|
|
a93e935585 | ||
|
|
aeb07e880e | ||
|
|
e0ce1f309e | ||
|
|
d74c81c3d1 | ||
|
|
2fa7200fb3 | ||
|
|
9f7d15e1b1 | ||
|
|
fdf00f1b18 | ||
|
|
abe7bbda15 | ||
|
|
795139d78f | ||
|
|
c7390ef0d7 | ||
|
|
8a926a3db3 | ||
|
|
7d5e49b44c | ||
|
|
555c3eac7a | ||
|
|
f79649d71b | ||
|
|
c9c27b9f01 | ||
|
|
0bc47cf853 | ||
|
|
5649328bc5 | ||
|
|
726dd15c5a | ||
|
|
d513c6f85c | ||
|
|
c1c17ba162 | ||
|
|
f8aa686557 | ||
|
|
d7eb4bbe6e | ||
|
|
c17a350796 | ||
|
|
bb106f8c6e | ||
|
|
09e47fb5eb | ||
|
|
099209fff2 | ||
|
|
d572490a67 | ||
|
|
180e7f79d4 | ||
|
|
d9da7cc92e | ||
|
|
c2ce2044cc | ||
|
|
e400bfb79e | ||
|
|
3d32faa8f9 | ||
|
|
862d76471b | ||
|
|
27f2474876 | ||
|
|
01a5aeeb58 | ||
|
|
47df131e7f | ||
|
|
56750adfe4 | ||
|
|
11e64ef018 | ||
|
|
9b20e0a430 | ||
|
|
41df2ec5da | ||
|
|
e45b207375 | ||
|
|
c37866e534 | ||
|
|
bda4ab7025 | ||
|
|
32cc1ff7b1 | ||
|
|
b5952a590a | ||
|
|
581a024ceb | ||
|
|
c45cb8b04e | ||
|
|
a5e1a4d6dd | ||
|
|
e88b280eb0 | ||
|
|
e4b07f2b8c | ||
|
|
4eb6e77eb5 | ||
|
|
db1fc7625d | ||
|
|
bfec9dc5ad | ||
|
|
56f0a2c31c | ||
|
|
5ee0bb4b65 | ||
|
|
5c74728387 | ||
|
|
97c46bea0f | ||
|
|
e6c1ab4e4c | ||
|
|
6e1ec266a7 | ||
|
|
3fc6982e9a | ||
|
|
5b1cc7d713 | ||
|
|
2842f27522 | ||
|
|
7ef137e26e | ||
|
|
b1a336eb62 | ||
|
|
d481e93832 | ||
|
|
8cf5537043 | ||
|
|
d2ce9d607a | ||
|
|
36736914bc | ||
|
|
7823d8342e | ||
|
|
431142471f | ||
|
|
a3227a444a | ||
|
|
f74af45fba | ||
|
|
e050e2d61e | ||
|
|
ef85ef676a | ||
|
|
e977546297 | ||
|
|
ae92b0ed33 | ||
|
|
7e686d9aa5 | ||
|
|
d23bbf7bf3 | ||
|
|
d4dad4860a | ||
|
|
c009cbef76 | ||
|
|
37e60c73ae | ||
|
|
3ab095a568 | ||
|
|
f5d9b65139 | ||
|
|
31e5b40459 | ||
|
|
8fb55d75fe | ||
|
|
420222511b | ||
|
|
c5cec60809 | ||
|
|
12dd815421 | ||
|
|
f36f49cb3e |
+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.capitalization = camel_case
|
||||||
dotnet_naming_style.lower_camel_case_style.required_prefix = _
|
dotnet_naming_style.lower_camel_case_style.required_prefix = _
|
||||||
dotnet_naming_style.upper_camel_case_style.capitalization = pascal_case
|
dotnet_naming_style.upper_camel_case_style.capitalization = pascal_case
|
||||||
dotnet_naming_symbols.constants_symbols.applicable_accessibilities = public,internal,protected,protected_internal,private_protected
|
dotnet_naming_symbols.constants_symbols.applicable_accessibilities = public, internal, protected, protected_internal, private_protected
|
||||||
dotnet_naming_symbols.constants_symbols.applicable_kinds = field
|
dotnet_naming_symbols.constants_symbols.applicable_kinds = field
|
||||||
dotnet_naming_symbols.constants_symbols.required_modifiers = const
|
dotnet_naming_symbols.constants_symbols.required_modifiers = const
|
||||||
dotnet_naming_symbols.public_fields_symbols.applicable_accessibilities = public,internal,protected,protected_internal,private_protected
|
dotnet_naming_symbols.public_fields_symbols.applicable_accessibilities = public, internal, protected, protected_internal, private_protected
|
||||||
dotnet_naming_symbols.public_fields_symbols.applicable_kinds = field
|
dotnet_naming_symbols.public_fields_symbols.applicable_kinds = field
|
||||||
dotnet_naming_symbols.static_readonly_symbols.applicable_accessibilities = public,internal,protected,protected_internal,private_protected
|
dotnet_naming_symbols.static_readonly_symbols.applicable_accessibilities = public, internal, protected, protected_internal, private_protected
|
||||||
dotnet_naming_symbols.static_readonly_symbols.applicable_kinds = field
|
dotnet_naming_symbols.static_readonly_symbols.applicable_kinds = field
|
||||||
dotnet_naming_symbols.static_readonly_symbols.required_modifiers = static,readonly
|
dotnet_naming_symbols.static_readonly_symbols.required_modifiers = static, readonly
|
||||||
dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:none
|
dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:none
|
||||||
dotnet_style_parentheses_in_other_binary_operators = never_if_unnecessary:none
|
dotnet_style_parentheses_in_other_binary_operators = never_if_unnecessary:none
|
||||||
dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:none
|
dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:none
|
||||||
@@ -40,7 +40,6 @@ dotnet_style_qualification_for_method = false:suggestion
|
|||||||
dotnet_style_qualification_for_property = false:suggestion
|
dotnet_style_qualification_for_property = false:suggestion
|
||||||
|
|
||||||
# ReSharper properties
|
# ReSharper properties
|
||||||
resharper_autodetect_indent_settings = true
|
|
||||||
resharper_default_private_modifier = implicit
|
resharper_default_private_modifier = implicit
|
||||||
resharper_place_accessorholder_attribute_on_same_line = false
|
resharper_place_accessorholder_attribute_on_same_line = false
|
||||||
resharper_use_indent_from_vs = false
|
resharper_use_indent_from_vs = false
|
||||||
@@ -61,7 +60,7 @@ resharper_web_config_wrong_module_highlighting = warning
|
|||||||
[*]
|
[*]
|
||||||
charset = utf-8
|
charset = utf-8
|
||||||
end_of_line = crlf
|
end_of_line = crlf
|
||||||
trim_trailing_whitespace = false
|
trim_trailing_whitespace = true
|
||||||
insert_final_newline = false
|
insert_final_newline = false
|
||||||
indent_style = space
|
indent_style = space
|
||||||
indent_size = 4
|
indent_size = 4
|
||||||
|
|||||||
+100
-44
@@ -3,65 +3,121 @@ name: CI
|
|||||||
on: [push, pull_request]
|
on: [push, pull_request]
|
||||||
|
|
||||||
env:
|
env:
|
||||||
VERSION: "4.1.0.${{github.run_number}}"
|
VERSION: "5.0.0.${{github.run_number}}"
|
||||||
|
PACKAGE_SUFFIX: ""
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
|
||||||
runs-on: windows-2022
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Setup Windows SDK
|
|
||||||
uses: GuillaumeFalourd/setup-windows10-sdk-action@v1
|
|
||||||
with:
|
|
||||||
sdk-version: 18362
|
|
||||||
|
|
||||||
- name: Setup .NET SDK
|
- name: Setup .NET SDK
|
||||||
uses: actions/setup-dotnet@v1.9.0
|
uses: actions/setup-dotnet@v4
|
||||||
|
with:
|
||||||
|
dotnet-version: |
|
||||||
|
8.0.x
|
||||||
|
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Build package
|
||||||
|
run: dotnet build MQTTnet.sln --configuration Release /p:FileVersion=${{ env.VERSION }} /p:AssemblyVersion=${{ env.VERSION }} /p:PackageVersion=${{ env.VERSION }}${{ env.PACKAGE_SUFFIX }}
|
||||||
|
|
||||||
|
- name: Upload nuget packages
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: nugets
|
||||||
|
path: |
|
||||||
|
${{ github.workspace }}/Source/**/*.nupkg
|
||||||
|
${{ github.workspace }}/Source/**/*.snupkg
|
||||||
|
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Setup .NET SDK
|
||||||
|
uses: actions/setup-dotnet@v4
|
||||||
|
with:
|
||||||
|
dotnet-version: |
|
||||||
|
8.0.x
|
||||||
|
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Execute tests
|
||||||
|
run: dotnet test --framework net8.0 Source/MQTTnet.Tests/MQTTnet.Tests.csproj
|
||||||
|
|
||||||
|
sign:
|
||||||
|
needs: build
|
||||||
|
runs-on: windows-latest # Code signing must run on a Windows agent for Authenticode signing (dll/exe)
|
||||||
|
if: github.repository == 'dotnet/MQTTnet'
|
||||||
|
steps:
|
||||||
|
- name: Setup .NET SDK
|
||||||
|
uses: actions/setup-dotnet@v4
|
||||||
with:
|
with:
|
||||||
dotnet-version: |
|
dotnet-version: |
|
||||||
3.1.x
|
|
||||||
6.0.x
|
6.0.x
|
||||||
|
|
||||||
- name: Setup MSBuild
|
- name: Download nuget packages
|
||||||
uses: microsoft/setup-msbuild@v1.1
|
uses: actions/download-artifact@v4
|
||||||
|
|
||||||
- name: Checkout Code
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
|
|
||||||
- name: Setup Signing Certificate
|
|
||||||
run: |
|
|
||||||
$secret = '${{ secrets.SNC_BASE64 }}'
|
|
||||||
$decoded = [System.Convert]::FromBase64CharArray($secret, 0, $secret.Length)
|
|
||||||
Set-Content -Path ${{ github.workspace }}\certificate.snk -Value $decoded -AsByteStream
|
|
||||||
|
|
||||||
- name: Restore nuget packages
|
|
||||||
run: msbuild MQTTnet.sln /t:Restore /p:Configuration="Release" /verbosity:m
|
|
||||||
|
|
||||||
- name: Build solution
|
|
||||||
run: msbuild MQTTnet.sln /t:Build /p:Configuration="Release" /verbosity:m /p:FileVersion=${{ env.VERSION }} /p:AssemblyVersion=${{ env.VERSION }} /p:PackageVersion=${{ env.VERSION }} /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=${{ github.workspace }}\certificate.snk
|
|
||||||
|
|
||||||
- name: Collect nuget Packages
|
|
||||||
uses: actions/upload-artifact@v2
|
|
||||||
with:
|
with:
|
||||||
name: nuget Packages
|
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: |
|
path: |
|
||||||
**\*.nupkg
|
${{ github.workspace }}\nugets\**\*.nupkg
|
||||||
**\*.snupkg
|
${{ github.workspace }}\nugets\**\*.snupkg
|
||||||
|
|
||||||
- name: Setup VSTest
|
publish-myget:
|
||||||
uses: darenm/Setup-VSTest@v1
|
if: ${{ github.event_name == 'push' }}
|
||||||
|
needs: sign
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
- name: Core Tests
|
steps:
|
||||||
run: vstest.console.exe Source\MQTTnet.Tests\bin\Release\net6.0\MQTTnet.Tests.dll
|
- name: Download signed nuget packages
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: signed-nugets
|
||||||
|
path: nugets
|
||||||
|
|
||||||
- name: ASP.NET Tests
|
- name: Publish myget.com nugets
|
||||||
run: vstest.console.exe Source\MQTTnet.AspNetCore.Tests\bin\Release\netcoreapp3.1\MQTTnet.AspNetCore.Tests.dll
|
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
|
||||||
|
|
||||||
- name: Publish MyGet nugets
|
publish-nuget:
|
||||||
if: ${{ github.event_name == 'push' }}
|
# Tag schema is 'v5.0.0-rc' etc.
|
||||||
run: dotnet nuget push **/*.nupkg -k ${{ secrets.MYGET_API_KEY }} -s https://www.myget.org/F/mqttnet/api/v3/index.json --skip-duplicate
|
if: startsWith(github.ref, 'refs/tags/v')
|
||||||
|
needs: [sign, test]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
# - name: Publish nugets
|
steps:
|
||||||
# if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
|
- name: Download signed nuget packages
|
||||||
# run: dotnet nuget push **/*.nupkg -k ${{ secrets.NUGET_API_KEY }} -s https://api.nuget.org/v3/index.json --skip-duplicate
|
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
|
||||||
+20
-24
@@ -7,30 +7,30 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet", "Source\MQTTnet\M
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{B3F60ECB-45BA-4C66-8903-8BB89CA67998}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{B3F60ECB-45BA-4C66-8903-8BB89CA67998}"
|
||||||
ProjectSection(SolutionItems) = preProject
|
ProjectSection(SolutionItems) = preProject
|
||||||
|
CODE-OF-CONDUCT.md = CODE-OF-CONDUCT.md
|
||||||
LICENSE = LICENSE
|
LICENSE = LICENSE
|
||||||
README.md = README.md
|
README.md = README.md
|
||||||
CODE-OF-CONDUCT.md = CODE-OF-CONDUCT.md
|
Source\ReleaseNotes.md = Source\ReleaseNotes.md
|
||||||
|
.github\workflows\ci.yml = .github\workflows\ci.yml
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.AspNetCore", "Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj", "{F10C4060-F7EE-4A83-919F-FF723E72F94A}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.AspNetCore", "Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj", "{F10C4060-F7EE-4A83-919F-FF723E72F94A}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Extensions.Rpc", "Source\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj", "{C444E9C8-95FA-430E-9126-274129DE16CD}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Extensions.Rpc", "Source\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj", "{C444E9C8-95FA-430E-9126-274129DE16CD}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Extensions.ManagedClient", "Source\MQTTnet.Extensions.ManagedClient\MQTTnet.Extensions.ManagedClient.csproj", "{C400533A-8EBA-4F0B-BF4D-295C3708604B}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Samples", "Samples\MQTTnet.Samples.csproj", "{71CF35F5-3327-4A91-AAF4-5340F6701771}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.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
|
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
|
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
|
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
|
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
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MQTTnet.TestApp", "Source\MQTTnet.TestApp\MQTTnet.TestApp.csproj", "{175D5340-CC5B-4542-939D-4E7D15A0BC8D}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MQTTnet.Server", "Source\MQTTnet.Server\MQTTnet.Server.csproj", "{C876EFFD-C5AD-4E26-BD9F-4C48252C02BC}"
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MQTTnet.AspTestApp", "Source\MQTTnet.AspTestApp\MQTTnet.AspTestApp.csproj", "{72867E4C-4E15-4E8E-8FAB-AE9253286BBC}"
|
|
||||||
EndProject
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
@@ -50,14 +50,6 @@ Global
|
|||||||
{C444E9C8-95FA-430E-9126-274129DE16CD}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{C444E9C8-95FA-430E-9126-274129DE16CD}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{C444E9C8-95FA-430E-9126-274129DE16CD}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{C444E9C8-95FA-430E-9126-274129DE16CD}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{C444E9C8-95FA-430E-9126-274129DE16CD}.Release|Any CPU.Build.0 = Release|Any CPU
|
{C444E9C8-95FA-430E-9126-274129DE16CD}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{C400533A-8EBA-4F0B-BF4D-295C3708604B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{C400533A-8EBA-4F0B-BF4D-295C3708604B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{C400533A-8EBA-4F0B-BF4D-295C3708604B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{C400533A-8EBA-4F0B-BF4D-295C3708604B}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{2BD01D53-4CA5-4142-BE8D-313876395E3E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{2BD01D53-4CA5-4142-BE8D-313876395E3E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{2BD01D53-4CA5-4142-BE8D-313876395E3E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{2BD01D53-4CA5-4142-BE8D-313876395E3E}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{71CF35F5-3327-4A91-AAF4-5340F6701771}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{71CF35F5-3327-4A91-AAF4-5340F6701771}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{71CF35F5-3327-4A91-AAF4-5340F6701771}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{71CF35F5-3327-4A91-AAF4-5340F6701771}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{71CF35F5-3327-4A91-AAF4-5340F6701771}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{71CF35F5-3327-4A91-AAF4-5340F6701771}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
@@ -66,10 +58,6 @@ Global
|
|||||||
{B270F32A-9F3E-42EE-A989-813E35E29ADB}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{B270F32A-9F3E-42EE-A989-813E35E29ADB}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{B270F32A-9F3E-42EE-A989-813E35E29ADB}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{B270F32A-9F3E-42EE-A989-813E35E29ADB}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{B270F32A-9F3E-42EE-A989-813E35E29ADB}.Release|Any CPU.Build.0 = Release|Any CPU
|
{B270F32A-9F3E-42EE-A989-813E35E29ADB}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{A238BBBF-C75F-482D-9CC3-BB34ABA9B675}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{A238BBBF-C75F-482D-9CC3-BB34ABA9B675}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{A238BBBF-C75F-482D-9CC3-BB34ABA9B675}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{A238BBBF-C75F-482D-9CC3-BB34ABA9B675}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{2F516E76-AAC4-4219-B7D1-34CDD3CFF381}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{2F516E76-AAC4-4219-B7D1-34CDD3CFF381}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{2F516E76-AAC4-4219-B7D1-34CDD3CFF381}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{2F516E76-AAC4-4219-B7D1-34CDD3CFF381}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{2F516E76-AAC4-4219-B7D1-34CDD3CFF381}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{2F516E76-AAC4-4219-B7D1-34CDD3CFF381}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
@@ -82,13 +70,21 @@ Global
|
|||||||
{72867E4C-4E15-4E8E-8FAB-AE9253286BBC}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{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.ActiveCfg = Release|Any CPU
|
||||||
{72867E4C-4E15-4E8E-8FAB-AE9253286BBC}.Release|Any CPU.Build.0 = 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
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(NestedProjects) = preSolution
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {07536672-5CBC-4BE3-ACE0-708A431A7894}
|
SolutionGuid = {07536672-5CBC-4BE3-ACE0-708A431A7894}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
|
GlobalSection(NestedProjects) = preSolution
|
||||||
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|||||||
+13
-1
@@ -210,6 +210,7 @@
|
|||||||
</Or>
|
</Or>
|
||||||
</Entry.Match>
|
</Entry.Match>
|
||||||
<Entry.SortBy>
|
<Entry.SortBy>
|
||||||
|
<Access Order="Public Internal Protected Private" />
|
||||||
<Name />
|
<Name />
|
||||||
</Entry.SortBy>
|
</Entry.SortBy>
|
||||||
</Entry>
|
</Entry>
|
||||||
@@ -229,16 +230,27 @@
|
|||||||
<s:String x:Key="/Default/CodeStyle/FileHeader/FileHeaderText/@EntryValue">Licensed to the .NET Foundation under one or more agreements.
|
<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.
|
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>
|
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_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_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_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpRenamePlacementToArrangementMigration/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpRenamePlacementToArrangementMigration/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpUseContinuousIndentInsideBracesMigration/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpUseContinuousIndentInsideBracesMigration/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002EMemberReordering_002EMigrations_002ECSharpFileLayoutPatternRemoveIsAttributeUpgrade/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAddAccessorOwnerDeclarationBracesMigration/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAddAccessorOwnerDeclarationBracesMigration/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAlwaysTreatStructAsNotReorderableMigration/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAlwaysTreatStructAsNotReorderableMigration/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002ECSharpPlaceAttributeOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002ECSharpPlaceAttributeOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
|
||||||
<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_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/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/=PINGREQ/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=PINGRESP/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=PUBACK/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=PUBCOMP/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=PUBREC/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=PUBREL/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=SUBACK/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Tnet/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=Tnet/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=unsub/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=unsub/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=UNSUBACK/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
[](https://www.nuget.org/packages/MQTTnet)
|
[](https://www.nuget.org/packages/MQTTnet)
|
||||||
[](https://github.com/dotnet/MQTTnet/actions/workflows/ci.yml)
|
[](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://gitter.im/MQTTnet/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||||
@@ -12,7 +12,9 @@
|
|||||||
|
|
||||||
# MQTTnet
|
# 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.
|
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
|
## Features
|
||||||
|
|
||||||
@@ -28,13 +30,15 @@ MQTTnet is a high performance .NET library for MQTT based communication. It prov
|
|||||||
* Unit tested (~636 tests)
|
* Unit tested (~636 tests)
|
||||||
* No external dependencies
|
* No external dependencies
|
||||||
|
|
||||||
\* Tested on local machine (Intel i7 8700K) with MQTTnet client and server running in the same process using the TCP 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
|
### Client
|
||||||
|
|
||||||
* Communication via TCP (+TLS) or WS (WebSocket) supported
|
* Communication via TCP (+TLS) or WS (WebSocket) supported
|
||||||
* Included core _LowLevelMqttClient_ with low level functionality
|
* Included core _LowLevelMqttClient_ with low level functionality
|
||||||
* Also included _ManagedMqttClient_ which maintains the connection and subscriptions automatically. Also application 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)
|
* Rx support (via another project)
|
||||||
* Compatible with Microsoft Azure IoT Hub
|
* Compatible with Microsoft Azure IoT Hub
|
||||||
|
|
||||||
@@ -50,42 +54,28 @@ MQTTnet is a high performance .NET library for MQTT based communication. It prov
|
|||||||
* A custom message interceptor can be added which allows transforming or extending every received application message
|
* A custom message interceptor can be added which allows transforming or extending every received application message
|
||||||
* Validate subscriptions and deny subscribing of certain topics depending on requesting clients
|
* Validate subscriptions and deny subscribing of certain topics depending on requesting clients
|
||||||
|
|
||||||
## Supported frameworks
|
## Getting Started
|
||||||
|
|
||||||
| Framwork | Version |
|
MQTTnet is delivered via <b>NuGet</b> package manager. You can find the packages
|
||||||
| ------------------ | ----------- |
|
here: https://www.nuget.org/packages/MQTTnet/
|
||||||
|.NET | 5.0+ |
|
|
||||||
|.NET Framework | 4.5.2+ |
|
|
||||||
|.NET Standard | 1.3+ |
|
|
||||||
|.NET Core | 1.1+ |
|
|
||||||
|.NET Core App | 1.1+ |
|
|
||||||
| Mono | 5.2+ |
|
|
||||||
| UWP | 10.0.10240+ |
|
|
||||||
| Xamarin.Android | 7.5+ |
|
|
||||||
| Xamarin.iOS | 10.14+ |
|
|
||||||
| Blazor WebAssembly | 3.2.0+ |
|
|
||||||
|
|
||||||
## References
|
Use these command in the Package Manager console to install MQTTnet manually:
|
||||||
|
|
||||||
This library is used in the following projects:
|
```
|
||||||
|
Install-Package MQTTnet
|
||||||
|
```
|
||||||
|
|
||||||
* Azure Functions MQTT Bindings, <https://github.com/keesschollaart81/CaseOnline.Azure.WebJobs.Extensions.Mqtt>)
|
Samples for using MQTTnet are part of this repository. For starters these samples are recommended:
|
||||||
* HA4IoT (Open Source Home Automation system for .NET, <https://github.com/chkr1011/HA4IoT>)
|
|
||||||
* MQTT Client Rx (Wrapper for Reactive Extensions, <https://github.com/1iveowl/MQTTClient.rx>)
|
|
||||||
* MQTT Client Rx (Managed Client Wrapper for Reactive Extensions, <https://github.com/mmuecke/RxMQTTnet>)
|
|
||||||
* MQTT Tester (MQTT client test app for [Android](https://play.google.com/store/apps/details?id=com.liveowl.mqtttester) and [iOS](https://itunes.apple.com/us/app/mqtt-tester/id1278621826?mt=8))
|
|
||||||
* MQTTnet App (Cross platform client application for MQTT debugging, inspection etc., <https://github.com/chkr1011/MQTTnetApp>)
|
|
||||||
* Wirehome.Core (Open Source Home Automation system for .NET Core, <https://github.com/chkr1011/Wirehome.Core>)
|
|
||||||
* SparkplugNet (Sparkplug library for .Net, <https://github.com/SeppPenner/SparkplugNet>)
|
|
||||||
* Silverback (Framework to build event-driven applications - support for MQTT, Kafka & RabbitMQ) <https://github.com/BEagle1984/silverback>
|
|
||||||
|
|
||||||
Further projects using this project can be found under https://github.com/dotnet/MQTTnet/network/dependents.
|
- [Connect with a broker](https://github.com/dotnet/MQTTnet/blob/master/Samples/Client/Client_Connection_Samples.cs)
|
||||||
|
- [Subscribing to data](https://github.com/dotnet/MQTTnet/blob/master/Samples/Client/Client_Subscribe_Samples.cs)
|
||||||
If you use this library and want to see your project here please create a pull request.
|
- [Publishing data](https://github.com/dotnet/MQTTnet/blob/master/Samples/Client/Client_Publish_Samples.cs)
|
||||||
|
- [Host own broker](https://github.com/dotnet/MQTTnet/blob/master/Samples/Server/Server_Simple_Samples.cs)
|
||||||
|
|
||||||
## Code of Conduct
|
## Code of Conduct
|
||||||
|
|
||||||
This project has adopted the code of conduct defined by the Contributor Covenant to clarify expected behavior in our 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).
|
For more information see the [.NET Foundation Code of Conduct](https://dotnetfoundation.org/code-of-conduct).
|
||||||
|
|
||||||
## .NET Foundation
|
## .NET Foundation
|
||||||
|
|||||||
@@ -7,16 +7,66 @@
|
|||||||
// ReSharper disable InconsistentNaming
|
// ReSharper disable InconsistentNaming
|
||||||
|
|
||||||
using System.Security.Authentication;
|
using System.Security.Authentication;
|
||||||
using MQTTnet.Client;
|
using System.Security.Cryptography.X509Certificates;
|
||||||
using MQTTnet.Extensions.WebSocket4Net;
|
using System.Text;
|
||||||
using MQTTnet.Formatter;
|
using MQTTnet.Formatter;
|
||||||
|
using MQTTnet.Protocol;
|
||||||
using MQTTnet.Samples.Helpers;
|
using MQTTnet.Samples.Helpers;
|
||||||
|
using MQTTnet.Server;
|
||||||
|
using MQTTnet.Server.EnhancedAuthentication;
|
||||||
|
|
||||||
namespace MQTTnet.Samples.Client;
|
namespace MQTTnet.Samples.Client;
|
||||||
|
|
||||||
public static class Client_Connection_Samples
|
public static class Client_Connection_Samples
|
||||||
{
|
{
|
||||||
public static async Task Connect_Client()
|
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 afterward.
|
||||||
|
*
|
||||||
|
* See sample _Connect_Client_ for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var mqttFactory = new MqttClientFactory();
|
||||||
|
|
||||||
|
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||||
|
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||||
|
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||||
|
|
||||||
|
// This will send the DISCONNECT packet. Calling _Dispose_ without DisconnectAsync the
|
||||||
|
// connection is closed in a "not clean" way. See MQTT specification for more details.
|
||||||
|
await mqttClient.DisconnectAsync(new MqttClientDisconnectOptionsBuilder().WithReason(MqttClientDisconnectOptionsReason.NormalDisconnection).Build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task Connect()
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* This sample creates a simple MQTT client and connects to a public broker.
|
* This sample creates a simple MQTT client and connects to a public broker.
|
||||||
@@ -25,59 +75,95 @@ public static class Client_Connection_Samples
|
|||||||
* The default version of MQTT is 3.1.1.
|
* The default version of MQTT is 3.1.1.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var mqttFactory = new MqttFactory();
|
var mqttFactory = new MqttClientFactory();
|
||||||
|
|
||||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||||
{
|
// Use builder classes where possible in this project.
|
||||||
// Use builder classes where possible in this project.
|
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
|
||||||
|
|
||||||
// This will throw an exception if the server is not available.
|
// This will throw an exception if the server is not available.
|
||||||
// The result from this message returns additional data which was sent
|
// The result from this message returns additional data which was sent
|
||||||
// from the server. Please refer to the MQTT protocol specification for details.
|
// from the server. Please refer to the MQTT protocol specification for details.
|
||||||
var response = await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
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
|
// 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).
|
// gets dropped and the server will handle this as a non clean disconnect (see MQTT spec for details).
|
||||||
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 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 sample uses enhanced authentication (Kerberos) when creating the 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())
|
/*
|
||||||
|
* 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
|
Console.WriteLine($"Received AUTH data from client: {Encoding.UTF8.GetString(result.AuthenticationData)}");
|
||||||
{
|
|
||||||
using (var timeoutToken = new CancellationTokenSource(TimeSpan.FromSeconds(1)))
|
var authOptions = mqttServerFactory.CreateExchangeExtendedAuthenticationOptionsBuilder().WithAuthenticationData("reply context token").Build();
|
||||||
{
|
|
||||||
await mqttClient.ConnectAsync(mqttClientOptions, timeoutToken.Token);
|
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 sample creates a simple MQTT client and connects to a public broker using MQTTv5.
|
||||||
@@ -85,22 +171,20 @@ public static class Client_Connection_Samples
|
|||||||
* This is a modified version of the sample _Connect_Client_! See other sample for more details.
|
* 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())
|
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||||
{
|
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").WithProtocolVersion(MqttProtocolVersion.V500).Build();
|
||||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").WithProtocolVersion(MqttProtocolVersion.V500).Build();
|
|
||||||
|
|
||||||
// In MQTTv5 the response contains much more information.
|
// In MQTTv5 the response contains much more information.
|
||||||
var response = await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
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 sample creates a simple MQTT client and connects to a public broker using TLS 1.2 encryption.
|
||||||
@@ -108,79 +192,29 @@ public static class Client_Connection_Samples
|
|||||||
* This is a modified version of the sample _Connect_Client_! See other sample for more details.
|
* 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())
|
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||||
{
|
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("mqtt.fluux.io")
|
||||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("mqtt.fluux.io")
|
.WithTlsOptions(
|
||||||
.WithTls(
|
o =>
|
||||||
o =>
|
{
|
||||||
{
|
// The used public broker sometimes has invalid certificates. This sample accepts all
|
||||||
// The used public broker sometimes has invalid certificates. This sample accepts all
|
// certificates. This should not be used in live environments.
|
||||||
// certificates. This should not be used in live environments.
|
o.WithCertificateValidationHandler(_ => true);
|
||||||
o.CertificateValidationHandler = _ => true;
|
|
||||||
|
|
||||||
// The default value is determined by the OS. Set manually to force version.
|
// The default value is determined by the OS. Set manually to force version.
|
||||||
o.SslProtocol = SslProtocols.Tls12;
|
o.WithSslProtocols(SslProtocols.Tls12);
|
||||||
})
|
})
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
using (var timeout = new CancellationTokenSource(5000))
|
using var timeout = new CancellationTokenSource(5000);
|
||||||
{
|
await mqttClient.ConnectAsync(mqttClientOptions, timeout.Token);
|
||||||
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_WebSockets()
|
public static async Task Connect_Using_TLS_Encryption()
|
||||||
{
|
|
||||||
/*
|
|
||||||
* 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("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_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 implementaion 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("broker.hivemq.com:8000/mqtt").Build();
|
|
||||||
|
|
||||||
var response = await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
|
||||||
|
|
||||||
Console.WriteLine("The MQTT client is connected.");
|
|
||||||
|
|
||||||
response.DumpToConsole();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task Connect_Client_With_TLS_Encryption()
|
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* This sample creates a simple MQTT client and connects to a public broker with enabled TLS encryption.
|
* This sample creates a simple MQTT client and connects to a public broker with enabled TLS encryption.
|
||||||
@@ -188,30 +222,124 @@ public static class Client_Connection_Samples
|
|||||||
* This is a modified version of the sample _Connect_Client_! See other sample for more details.
|
* 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())
|
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||||
{
|
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883)
|
||||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883)
|
.WithTlsOptions(
|
||||||
.WithTls(
|
o => o.WithCertificateValidationHandler(
|
||||||
o =>
|
// The used public broker sometimes has invalid certificates. This sample accepts all
|
||||||
{
|
// certificates. This should not be used in live environments.
|
||||||
// The used public broker sometimes has invalid certificates. This sample accepts all
|
_ => true))
|
||||||
// certificates. This should not be used in live environments.
|
.Build();
|
||||||
o.CertificateValidationHandler = _ => true;
|
|
||||||
})
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
// In MQTTv5 the response contains much more information.
|
// In MQTTv5 the response contains much more information.
|
||||||
using (var timeout = new CancellationTokenSource(5000))
|
using var timeout = new CancellationTokenSource(5000);
|
||||||
{
|
var response = await mqttClient.ConnectAsync(mqttClientOptions, timeout.Token);
|
||||||
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()
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* This sample creates a simple MQTT client and connects to an Amazon Web Services broker.
|
||||||
|
*
|
||||||
|
* The broker requires special settings which are set here.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var mqttFactory = new MqttClientFactory();
|
||||||
|
|
||||||
|
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||||
|
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("amazon.web.services.broker")
|
||||||
|
// Disabling packet fragmentation is very important!
|
||||||
|
.WithoutPacketFragmentation()
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||||
|
|
||||||
|
Console.WriteLine("The MQTT client is connected.");
|
||||||
|
|
||||||
|
await mqttClient.DisconnectAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task Disconnect_Clean()
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* This sample disconnects from the server with sending a DISCONNECT packet.
|
||||||
|
* This way of disconnecting is treated as a clean disconnect which will not
|
||||||
|
* trigger sending the last will etc.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var mqttFactory = new MqttClientFactory();
|
||||||
|
|
||||||
|
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||||
|
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||||
|
|
||||||
|
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||||
|
|
||||||
|
// Calling _DisconnectAsync_ will send a DISCONNECT packet before closing the connection.
|
||||||
|
// Using a reason code requires MQTT version 5.0.0!
|
||||||
|
await mqttClient.DisconnectAsync(MqttClientDisconnectOptionsReason.ImplementationSpecificError);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task Disconnect_Non_Clean()
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* This sample disconnects from the server without sending a DISCONNECT packet.
|
||||||
|
* This way of disconnecting is treated as a non-clean disconnect which will
|
||||||
|
* trigger sending the last will etc.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var mqttFactory = new MqttClientFactory();
|
||||||
|
|
||||||
|
var mqttClient = mqttFactory.CreateMqttClient();
|
||||||
|
|
||||||
|
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||||
|
|
||||||
|
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||||
|
|
||||||
|
// Calling _Dispose_ or use of a _using_ statement will close the transport connection
|
||||||
|
// without sending a DISCONNECT packet to the server.
|
||||||
|
mqttClient.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task Inspect_Certificate_Validation_Errors()
|
public static async Task Inspect_Certificate_Validation_Errors()
|
||||||
@@ -221,15 +349,15 @@ public static class Client_Connection_Samples
|
|||||||
* including the reason for that status.
|
* including the reason for that status.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var mqttFactory = new MqttFactory();
|
var mqttFactory = new MqttClientFactory();
|
||||||
|
|
||||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||||
{
|
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("mqtt.fluux.io", 8883)
|
||||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("mqtt.fluux.io", 8883)
|
.WithTlsOptions(
|
||||||
.WithTls(
|
o =>
|
||||||
o =>
|
{
|
||||||
{
|
o.WithCertificateValidationHandler(
|
||||||
o.CertificateValidationHandler = eventArgs =>
|
eventArgs =>
|
||||||
{
|
{
|
||||||
eventArgs.Certificate.Subject.DumpToConsole();
|
eventArgs.Certificate.Subject.DumpToConsole();
|
||||||
eventArgs.Certificate.GetExpirationDateString().DumpToConsole();
|
eventArgs.Certificate.GetExpirationDateString().DumpToConsole();
|
||||||
@@ -237,16 +365,13 @@ public static class Client_Connection_Samples
|
|||||||
eventArgs.Chain.ChainStatus.DumpToConsole();
|
eventArgs.Chain.ChainStatus.DumpToConsole();
|
||||||
eventArgs.SslPolicyErrors.DumpToConsole();
|
eventArgs.SslPolicyErrors.DumpToConsole();
|
||||||
return true;
|
return true;
|
||||||
};
|
});
|
||||||
})
|
})
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
// In MQTTv5 the response contains much more information.
|
// In MQTTv5 the response contains much more information.
|
||||||
using (var timeout = new CancellationTokenSource(5000))
|
using var timeout = new CancellationTokenSource(5000);
|
||||||
{
|
await mqttClient.ConnectAsync(mqttClientOptions, timeout.Token);
|
||||||
await mqttClient.ConnectAsync(mqttClientOptions, timeout.Token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task Ping_Server()
|
public static async Task Ping_Server()
|
||||||
@@ -254,22 +379,20 @@ public static class Client_Connection_Samples
|
|||||||
/*
|
/*
|
||||||
* This sample sends a PINGREQ packet to the server and waits for a reply.
|
* This sample sends a PINGREQ packet to the server and waits for a reply.
|
||||||
*
|
*
|
||||||
* This is only supported in METTv5.0.0+.
|
* This is only supported in MQTTv5.0.0+.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
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();
|
||||||
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.
|
// This will throw an exception if the server does not reply.
|
||||||
await mqttClient.PingAsync(CancellationToken.None);
|
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()
|
public static async Task Reconnect_Using_Event()
|
||||||
@@ -277,26 +400,24 @@ public static class Client_Connection_Samples
|
|||||||
/*
|
/*
|
||||||
* This sample shows how to reconnect when the connection was dropped.
|
* This sample shows how to reconnect when the connection was dropped.
|
||||||
* This approach uses one of the events from the client.
|
* 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();
|
if (e.ClientWasConnected)
|
||||||
|
|
||||||
mqttClient.DisconnectedAsync += async e =>
|
|
||||||
{
|
{
|
||||||
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(mqttClient.Options);
|
};
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Reconnect_Using_Timer()
|
public static void Reconnect_Using_Timer()
|
||||||
@@ -307,42 +428,95 @@ public static class Client_Connection_Samples
|
|||||||
* This is the recommended way but requires more custom code!
|
* This is the recommended way but requires more custom code!
|
||||||
*/
|
*/
|
||||||
|
|
||||||
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();
|
||||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
|
||||||
|
|
||||||
_ = Task.Run(
|
_ = Task.Run(
|
||||||
async () =>
|
async () =>
|
||||||
|
{
|
||||||
|
// User proper cancellation and no while(true).
|
||||||
|
while (true)
|
||||||
{
|
{
|
||||||
// User proper cancellation and no while(true).
|
try
|
||||||
while (true)
|
|
||||||
{
|
{
|
||||||
try
|
// This code will also do the very first connect! So no call to _ConnectAsync_ is required in the first place.
|
||||||
|
if (!await mqttClient.TryPingAsync())
|
||||||
{
|
{
|
||||||
// This code will also do the very first connect! So no call to _ConnectAsync_ is required
|
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||||
// in the first place.
|
|
||||||
if (!mqttClient.IsConnected)
|
|
||||||
{
|
|
||||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
|
||||||
|
|
||||||
// Subscribe to topics when session is clean etc.
|
// Subscribe to topics when session is clean etc.
|
||||||
|
Console.WriteLine("The MQTT client is connected.");
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
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.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 UnusedMember.Global
|
||||||
// ReSharper disable InconsistentNaming
|
// ReSharper disable InconsistentNaming
|
||||||
|
|
||||||
using MQTTnet.Client;
|
|
||||||
|
|
||||||
namespace MQTTnet.Samples.Client;
|
namespace MQTTnet.Samples.Client;
|
||||||
|
|
||||||
public static class Client_Publish_Samples
|
public static class Client_Publish_Samples
|
||||||
@@ -23,7 +21,7 @@ public static class Client_Publish_Samples
|
|||||||
* or at least provides backward compatibility where possible.
|
* or at least provides backward compatibility where possible.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var mqttFactory = new MqttFactory();
|
var mqttFactory = new MqttClientFactory();
|
||||||
|
|
||||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||||
{
|
{
|
||||||
@@ -40,6 +38,53 @@ public static class Client_Publish_Samples
|
|||||||
|
|
||||||
await mqttClient.PublishAsync(applicationMessage, CancellationToken.None);
|
await mqttClient.PublishAsync(applicationMessage, CancellationToken.None);
|
||||||
|
|
||||||
|
await mqttClient.DisconnectAsync();
|
||||||
|
|
||||||
|
Console.WriteLine("MQTT application message is published.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task Publish_Multiple_Application_Messages()
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* This sample pushes multiple simple application message including a topic and a payload.
|
||||||
|
*
|
||||||
|
* See sample _Publish_Application_Message_ for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var mqttFactory = new MqttClientFactory();
|
||||||
|
|
||||||
|
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||||
|
{
|
||||||
|
var mqttClientOptions = new MqttClientOptionsBuilder()
|
||||||
|
.WithTcpServer("broker.hivemq.com")
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
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.");
|
Console.WriteLine("MQTT application message is published.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,27 +5,30 @@
|
|||||||
// ReSharper disable UnusedType.Global
|
// ReSharper disable UnusedType.Global
|
||||||
// ReSharper disable UnusedMember.Global
|
// ReSharper disable UnusedMember.Global
|
||||||
// ReSharper disable InconsistentNaming
|
// ReSharper disable InconsistentNaming
|
||||||
|
// ReSharper disable UnusedMember.Local
|
||||||
|
|
||||||
using MQTTnet.Client;
|
using MQTTnet.Extensions.TopicTemplate;
|
||||||
|
using MQTTnet.Packets;
|
||||||
|
using MQTTnet.Protocol;
|
||||||
using MQTTnet.Samples.Helpers;
|
using MQTTnet.Samples.Helpers;
|
||||||
|
|
||||||
namespace MQTTnet.Samples.Client;
|
namespace MQTTnet.Samples.Client;
|
||||||
|
|
||||||
public static class Client_Subscribe_Samples
|
public static class Client_Subscribe_Samples
|
||||||
{
|
{
|
||||||
|
static readonly MqttTopicTemplate sampleTemplate = new("mqttnet/samples/topic/{id}");
|
||||||
|
|
||||||
public static async Task Handle_Received_Application_Message()
|
public static async Task Handle_Received_Application_Message()
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* This sample subscribes to a topic and processes the received message.
|
* This sample subscribes to a topic and processes the received message.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var mqttFactory = new MqttFactory();
|
var mqttFactory = new MqttClientFactory();
|
||||||
|
|
||||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||||
{
|
{
|
||||||
var mqttClientOptions = new MqttClientOptionsBuilder()
|
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||||
.WithTcpServer("broker.hivemq.com")
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
// Setup message handling before connecting so that queued messages
|
// Setup message handling before connecting so that queued messages
|
||||||
// are also handled properly. When there is no event handler attached all
|
// are also handled properly. When there is no event handler attached all
|
||||||
@@ -40,9 +43,7 @@ public static class Client_Subscribe_Samples
|
|||||||
|
|
||||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||||
|
|
||||||
var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder()
|
var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder().WithTopicTemplate(sampleTemplate.WithParameter("id", "2")).Build();
|
||||||
.WithTopicFilter(f => { f.WithTopic("mqttnet/samples/topic/2"); })
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None);
|
await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None);
|
||||||
|
|
||||||
@@ -53,25 +54,36 @@ public static class Client_Subscribe_Samples
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task Subscribe_Topic()
|
public static async Task Send_Responses()
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* This sample subscribes to a topic.
|
* This sample subscribes to a topic and sends a response to the broker. This requires at least QoS level 1 to work!
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var mqttFactory = new MqttFactory();
|
var mqttFactory = new MqttClientFactory();
|
||||||
|
|
||||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||||
{
|
{
|
||||||
var mqttClientOptions = new MqttClientOptionsBuilder()
|
mqttClient.ApplicationMessageReceivedAsync += delegate(MqttApplicationMessageReceivedEventArgs args)
|
||||||
.WithTcpServer("broker.hivemq.com")
|
{
|
||||||
.Build();
|
// 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!";
|
||||||
|
|
||||||
|
// User properties require MQTT v5!
|
||||||
|
args.ResponseUserProperties.Add(new MqttUserProperty("My", "Data"));
|
||||||
|
|
||||||
|
// Now the broker will resend the message again.
|
||||||
|
return Task.CompletedTask;
|
||||||
|
};
|
||||||
|
|
||||||
|
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()
|
var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder().WithTopicTemplate(sampleTemplate.WithParameter("id", "1")).Build();
|
||||||
.WithTopicFilter(f => { f.WithTopic("mqttnet/samples/topic/1"); })
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
var response = await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None);
|
var response = await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None);
|
||||||
|
|
||||||
@@ -81,4 +93,118 @@ public static class Client_Subscribe_Samples
|
|||||||
response.DumpToConsole();
|
response.DumpToConsole();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static async Task Subscribe_Multiple_Topics()
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* This sample subscribes to several topics in a single request.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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()
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* This sample subscribes to a topic.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ConcurrentProcessingDisableAutoAcknowledge(CancellationToken shutdownToken, IMqttClient mqttClient)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* 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 += ea =>
|
||||||
|
{
|
||||||
|
ea.AutoAcknowledge = false;
|
||||||
|
|
||||||
|
async Task ProcessAsync()
|
||||||
|
{
|
||||||
|
// DO YOUR WORK HERE!
|
||||||
|
await Task.Delay(1000, shutdownToken);
|
||||||
|
await ea.AcknowledgeAsync(shutdownToken);
|
||||||
|
// WARNING: If process failures are not transient the message will be retried on every restart of the client
|
||||||
|
// A failed message will not be dispatched again to the client as MQTT does not have a NACK packet to let
|
||||||
|
// the broker know processing failed
|
||||||
|
//
|
||||||
|
// Optionally: Use a framework like Polly to create a retry policy: https://github.com/App-vNext/Polly#retry
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = Task.Run(ProcessAsync, shutdownToken);
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ConcurrentProcessingWithLimit(CancellationToken shutdownToken, IMqttClient mqttClient)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* This sample shows how to achieve concurrent processing, with:
|
||||||
|
* - a maximum concurrency limit based on Environment.ProcessorCount
|
||||||
|
*/
|
||||||
|
|
||||||
|
var concurrent = new SemaphoreSlim(Environment.ProcessorCount);
|
||||||
|
|
||||||
|
mqttClient.ApplicationMessageReceivedAsync += async ea =>
|
||||||
|
{
|
||||||
|
await concurrent.WaitAsync(shutdownToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
async Task ProcessAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// DO YOUR WORK HERE!
|
||||||
|
await Task.Delay(1000, shutdownToken);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
concurrent.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = Task.Run(ProcessAsync, shutdownToken);
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
// ReSharper disable InconsistentNaming
|
// ReSharper disable InconsistentNaming
|
||||||
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using MQTTnet.Diagnostics;
|
using MQTTnet.Diagnostics.Logger;
|
||||||
|
|
||||||
namespace MQTTnet.Samples.Diagnostics;
|
namespace MQTTnet.Samples.Diagnostics;
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ public static class Logger_Samples
|
|||||||
* to other loggers like Microsoft logger or Serilog or log4net etc.
|
* to other loggers like Microsoft logger or Serilog or log4net etc.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var mqttFactory = new MqttFactory(new MyLogger());
|
var mqttFactory = new MqttClientFactory(new MyLogger());
|
||||||
|
|
||||||
var mqttClientOptions = mqttFactory.CreateClientOptionsBuilder()
|
var mqttClientOptions = mqttFactory.CreateClientOptionsBuilder()
|
||||||
.WithTcpServer("broker.hivemq.com")
|
.WithTcpServer("broker.hivemq.com")
|
||||||
@@ -62,7 +62,7 @@ public static class Logger_Samples
|
|||||||
Console.Write(output);
|
Console.Write(output);
|
||||||
};
|
};
|
||||||
|
|
||||||
var mqttFactory = new MqttFactory(mqttEventLogger);
|
var mqttFactory = new MqttClientFactory(mqttEventLogger);
|
||||||
|
|
||||||
var mqttClientOptions = mqttFactory.CreateClientOptionsBuilder()
|
var mqttClientOptions = mqttFactory.CreateClientOptionsBuilder()
|
||||||
.WithTcpServer("broker.hivemq.com")
|
.WithTcpServer("broker.hivemq.com")
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
// ReSharper disable UnusedMember.Global
|
// ReSharper disable UnusedMember.Global
|
||||||
// ReSharper disable InconsistentNaming
|
// ReSharper disable InconsistentNaming
|
||||||
|
|
||||||
using MQTTnet.Diagnostics;
|
using MQTTnet.Diagnostics.PacketInspection;
|
||||||
|
|
||||||
namespace MQTTnet.Samples.Diagnostics;
|
namespace MQTTnet.Samples.Diagnostics;
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ public static class PackageInspection_Samples
|
|||||||
* This sample covers the inspection of outgoing packages from the client.
|
* This sample covers the inspection of outgoing packages from the client.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var mqttFactory = new MqttFactory();
|
var mqttFactory = new MqttClientFactory();
|
||||||
|
|
||||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||||
{
|
{
|
||||||
@@ -26,7 +26,7 @@ public static class PackageInspection_Samples
|
|||||||
.WithTcpServer("broker.hivemq.com")
|
.WithTcpServer("broker.hivemq.com")
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
mqttClient.InspectPackage += OnInspectPackage;
|
mqttClient.InspectPacketAsync += OnInspectPacket;
|
||||||
|
|
||||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ public static class PackageInspection_Samples
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static Task OnInspectPackage(InspectMqttPacketEventArgs eventArgs)
|
static Task OnInspectPacket(InspectMqttPacketEventArgs eventArgs)
|
||||||
{
|
{
|
||||||
if (eventArgs.Direction == MqttPacketFlowDirection.Inbound)
|
if (eventArgs.Direction == MqttPacketFlowDirection.Inbound)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,25 +2,28 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>net6.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
||||||
<EnableNETAnalyzers>false</EnableNETAnalyzers>
|
<EnableNETAnalyzers>false</EnableNETAnalyzers>
|
||||||
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
|
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
|
||||||
<NoWarn>1591</NoWarn>
|
<NoWarn>1591;NETSDK1138;NU1803;NU1901;NU1902</NoWarn>
|
||||||
|
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||||
|
<NuGetAuditMode>all</NuGetAuditMode>
|
||||||
|
<NuGetAudit>true</NuGetAudit>
|
||||||
|
<NuGetAuditLevel>low</NuGetAuditLevel>
|
||||||
|
<AnalysisLevel>latest-Recommended</AnalysisLevel>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.Compilers" Version="4.0.1" />
|
|
||||||
<PackageReference Include="Microsoft.Net.Compilers" Version="4.0.1" />
|
|
||||||
|
|
||||||
<ProjectReference Include="..\Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj" />
|
<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.Rpc\MQTTnet.Extensions.Rpc.csproj" />
|
<ProjectReference Include="..\Source\MQTTnet.Extensions.TopicTemplate\MQTTnet.Extensions.TopicTemplate.csproj" />
|
||||||
<ProjectReference Include="..\Source\MQTTnet.Extensions.WebSocket4Net\MQTTnet.Extensions.WebSocket4Net.csproj" />
|
<ProjectReference Include="..\Source\MQTTnet.Server\MQTTnet.Server.csproj"/>
|
||||||
<ProjectReference Include="..\Source\MQTTnet\MQTTnet.csproj" />
|
<ProjectReference Include="..\Source\MQTTnet\MQTTnet.csproj"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</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}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+18
-1
@@ -4,7 +4,13 @@
|
|||||||
|
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
||||||
Console.WriteLine("Welcome to MQTTnet samples!");
|
Console.BackgroundColor = ConsoleColor.White;
|
||||||
|
Console.BackgroundColor = ConsoleColor.Red;
|
||||||
|
Console.Title = "Samples - MQTTnet";
|
||||||
|
Console.WriteLine("----------------------------------------------");
|
||||||
|
Console.WriteLine("- Welcome to MQTTnet samples! -");
|
||||||
|
Console.WriteLine("----------------------------------------------");
|
||||||
|
Console.ResetColor();
|
||||||
Console.WriteLine();
|
Console.WriteLine();
|
||||||
|
|
||||||
var sampleClasses = Assembly.GetExecutingAssembly().GetExportedTypes().OrderBy(c => c.Name).ToList();
|
var sampleClasses = Assembly.GetExecutingAssembly().GetExportedTypes().OrderBy(c => c.Name).ToList();
|
||||||
@@ -16,7 +22,11 @@ foreach (var sampleClass in sampleClasses)
|
|||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Console.WriteLine();
|
||||||
|
Console.ForegroundColor = ConsoleColor.Green;
|
||||||
Console.Write("Please choose sample class (press Enter to continue): ");
|
Console.Write("Please choose sample class (press Enter to continue): ");
|
||||||
|
Console.ResetColor();
|
||||||
|
|
||||||
var input = Console.ReadLine();
|
var input = Console.ReadLine();
|
||||||
var selectedIndex = int.Parse(input ?? "0");
|
var selectedIndex = int.Parse(input ?? "0");
|
||||||
var selectedSampleClass = sampleClasses[selectedIndex];
|
var selectedSampleClass = sampleClasses[selectedIndex];
|
||||||
@@ -29,12 +39,19 @@ foreach (var sampleMethod in sampleMethods)
|
|||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Console.WriteLine();
|
||||||
|
Console.ForegroundColor = ConsoleColor.Green;
|
||||||
Console.Write("Please choose sample (press Enter to continue): ");
|
Console.Write("Please choose sample (press Enter to continue): ");
|
||||||
|
Console.ResetColor();
|
||||||
|
|
||||||
input = Console.ReadLine();
|
input = Console.ReadLine();
|
||||||
selectedIndex = int.Parse(input ?? "0");
|
selectedIndex = int.Parse(input ?? "0");
|
||||||
var selectedSampleMethod = sampleMethods[selectedIndex];
|
var selectedSampleMethod = sampleMethods[selectedIndex];
|
||||||
|
|
||||||
|
Console.WriteLine();
|
||||||
|
Console.ForegroundColor = ConsoleColor.White;
|
||||||
Console.WriteLine("Executing sample...");
|
Console.WriteLine("Executing sample...");
|
||||||
|
Console.ResetColor();
|
||||||
Console.WriteLine();
|
Console.WriteLine();
|
||||||
|
|
||||||
try
|
try
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
// ReSharper disable UnusedMember.Global
|
// ReSharper disable UnusedMember.Global
|
||||||
// ReSharper disable InconsistentNaming
|
// ReSharper disable InconsistentNaming
|
||||||
|
|
||||||
using MQTTnet.Client;
|
|
||||||
using MQTTnet.Extensions.Rpc;
|
using MQTTnet.Extensions.Rpc;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
|
|
||||||
@@ -22,7 +21,7 @@ public static class RpcClient_Samples
|
|||||||
|
|
||||||
public static async Task Send_Request()
|
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
|
// The RPC client is an addon for the existing client. So we need a regular client
|
||||||
// which is wrapped later.
|
// which is wrapped later.
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ using MQTTnet.Server;
|
|||||||
|
|
||||||
namespace MQTTnet.Samples.Server;
|
namespace MQTTnet.Samples.Server;
|
||||||
|
|
||||||
public static class Server_Asp_Net_Samples
|
public static class Server_ASP_NET_Samples
|
||||||
{
|
{
|
||||||
public static Task Start_Server_With_WebSockets_Support()
|
public static Task Start_Server_With_WebSockets_Support()
|
||||||
{
|
{
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using MQTTnet.Internal;
|
||||||
|
using MQTTnet.Packets;
|
||||||
|
using MQTTnet.Server;
|
||||||
|
|
||||||
|
namespace MQTTnet.Samples.Server;
|
||||||
|
|
||||||
|
// ReSharper disable UnusedType.Global
|
||||||
|
// ReSharper disable UnusedMember.Global
|
||||||
|
// 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()
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* This sample starts a MQTT server and attaches an event which gets fired whenever
|
||||||
|
* a client received and acknowledged a PUBLISH packet.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var mqttServerFactory = new MqttServerFactory();
|
||||||
|
var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
|
||||||
|
|
||||||
|
using (var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions))
|
||||||
|
{
|
||||||
|
// 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("Press Enter to exit.");
|
||||||
|
Console.ReadLine();
|
||||||
|
|
||||||
|
await mqttServer.StopAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
// 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
|
||||||
|
// ReSharper disable EmptyConstructor
|
||||||
|
// ReSharper disable MemberCanBeMadeStatic.Local
|
||||||
|
|
||||||
|
using MQTTnet.Internal;
|
||||||
|
using MQTTnet.Server;
|
||||||
|
|
||||||
|
namespace MQTTnet.Samples.Server;
|
||||||
|
|
||||||
|
public static class Server_Intercepting_Samples
|
||||||
|
{
|
||||||
|
public static async Task Intercept_Application_Messages()
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* This sample starts a simple MQTT server which manipulate all processed application messages.
|
||||||
|
* Please see _Server_Simple_Samples_ for more details on how to start a server.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var mqttServerFactory = new MqttServerFactory();
|
||||||
|
var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
|
||||||
|
|
||||||
|
using (var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions))
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,7 +6,11 @@
|
|||||||
// ReSharper disable UnusedMember.Global
|
// ReSharper disable UnusedMember.Global
|
||||||
// ReSharper disable InconsistentNaming
|
// ReSharper disable InconsistentNaming
|
||||||
|
|
||||||
|
using System.Buffers;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
using MQTTnet.Packets;
|
||||||
|
using MQTTnet.Protocol;
|
||||||
|
using MQTTnet.Server;
|
||||||
|
|
||||||
namespace MQTTnet.Samples.Server;
|
namespace MQTTnet.Samples.Server;
|
||||||
|
|
||||||
@@ -20,19 +24,22 @@ public static class Server_Retained_Messages_Samples
|
|||||||
|
|
||||||
var storePath = Path.Combine(Path.GetTempPath(), "RetainedMessages.json");
|
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!
|
// 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.
|
// Make sure that the server will load the retained messages.
|
||||||
server.LoadingRetainedMessageAsync += async eventArgs =>
|
server.LoadingRetainedMessageAsync += async eventArgs =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
eventArgs.LoadedRetainedMessages = await JsonSerializer.DeserializeAsync<List<MqttApplicationMessage>>(File.OpenRead(storePath));
|
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.");
|
Console.WriteLine("Retained messages loaded.");
|
||||||
}
|
}
|
||||||
catch (FileNotFoundException)
|
catch (FileNotFoundException)
|
||||||
@@ -56,7 +63,9 @@ public static class Server_Retained_Messages_Samples
|
|||||||
// used to write all retained messages to dedicated files etc. Then all files must be loaded and a full list
|
// 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.
|
// of retained messages must be provided in the loaded event.
|
||||||
|
|
||||||
var buffer = JsonSerializer.SerializeToUtf8Bytes(eventArgs.StoredRetainedMessages);
|
var models = eventArgs.StoredRetainedMessages.Select(MqttRetainedMessageModel.Create);
|
||||||
|
|
||||||
|
var buffer = JsonSerializer.SerializeToUtf8Bytes(models);
|
||||||
await File.WriteAllBytesAsync(storePath, buffer);
|
await File.WriteAllBytesAsync(storePath, buffer);
|
||||||
Console.WriteLine("Retained messages saved.");
|
Console.WriteLine("Retained messages saved.");
|
||||||
}
|
}
|
||||||
@@ -79,4 +88,56 @@ public static class Server_Retained_Messages_Samples
|
|||||||
Console.ReadLine();
|
Console.ReadLine();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sealed class MqttRetainedMessageModel
|
||||||
|
{
|
||||||
|
public string? ContentType { get; set; }
|
||||||
|
public byte[]? CorrelationData { get; set; }
|
||||||
|
public byte[]? Payload { get; set; }
|
||||||
|
public MqttPayloadFormatIndicator PayloadFormatIndicator { get; set; }
|
||||||
|
public MqttQualityOfServiceLevel QualityOfServiceLevel { get; set; }
|
||||||
|
public string? ResponseTopic { get; set; }
|
||||||
|
public string? Topic { get; set; }
|
||||||
|
public List<MqttUserProperty>? UserProperties { get; set; }
|
||||||
|
|
||||||
|
public static MqttRetainedMessageModel Create(MqttApplicationMessage message)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(message);
|
||||||
|
|
||||||
|
return new MqttRetainedMessageModel
|
||||||
|
{
|
||||||
|
Topic = message.Topic,
|
||||||
|
|
||||||
|
// Create a copy of the buffer from the payload segment because
|
||||||
|
// it cannot be serialized and deserialized with the JSON serializer.
|
||||||
|
Payload = message.Payload.ToArray(),
|
||||||
|
UserProperties = message.UserProperties,
|
||||||
|
ResponseTopic = message.ResponseTopic,
|
||||||
|
CorrelationData = message.CorrelationData,
|
||||||
|
ContentType = message.ContentType,
|
||||||
|
PayloadFormatIndicator = message.PayloadFormatIndicator,
|
||||||
|
QualityOfServiceLevel = message.QualityOfServiceLevel
|
||||||
|
|
||||||
|
// Other properties like "Retain" are not if interest in the storage.
|
||||||
|
// That's why a custom model makes sense.
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public MqttApplicationMessage ToApplicationMessage()
|
||||||
|
{
|
||||||
|
return new MqttApplicationMessage
|
||||||
|
{
|
||||||
|
Topic = Topic,
|
||||||
|
PayloadSegment = new ArraySegment<byte>(Payload ?? Array.Empty<byte>()),
|
||||||
|
PayloadFormatIndicator = PayloadFormatIndicator,
|
||||||
|
ResponseTopic = ResponseTopic,
|
||||||
|
CorrelationData = CorrelationData,
|
||||||
|
ContentType = ContentType,
|
||||||
|
UserProperties = UserProperties,
|
||||||
|
QualityOfServiceLevel = QualityOfServiceLevel,
|
||||||
|
Dup = false,
|
||||||
|
Retain = true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
// ReSharper disable UnusedMember.Global
|
// ReSharper disable UnusedMember.Global
|
||||||
// ReSharper disable InconsistentNaming
|
// ReSharper disable InconsistentNaming
|
||||||
|
|
||||||
using MQTTnet.Diagnostics;
|
using MQTTnet.Diagnostics.Logger;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
using MQTTnet.Server;
|
using MQTTnet.Server;
|
||||||
|
|
||||||
@@ -64,7 +64,7 @@ public static class Server_Simple_Samples
|
|||||||
* This sample starts a simple MQTT server which will accept any TCP connection.
|
* This sample starts a simple MQTT server which will accept any TCP connection.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var mqttFactory = new MqttFactory();
|
var mqttServerFactory = new MqttServerFactory();
|
||||||
|
|
||||||
// The port for the default endpoint is 1883.
|
// The port for the default endpoint is 1883.
|
||||||
// The default endpoint is NOT encrypted!
|
// The default endpoint is NOT encrypted!
|
||||||
@@ -77,7 +77,7 @@ public static class Server_Simple_Samples
|
|||||||
// .WithDefaultEndpointPort(1234)
|
// .WithDefaultEndpointPort(1234)
|
||||||
// .Build();
|
// .Build();
|
||||||
|
|
||||||
using (var mqttServer = mqttFactory.CreateMqttServer(mqttServerOptions))
|
using (var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions))
|
||||||
{
|
{
|
||||||
await mqttServer.StartAsync();
|
await mqttServer.StartAsync();
|
||||||
|
|
||||||
@@ -99,11 +99,11 @@ public static class Server_Simple_Samples
|
|||||||
* See sample "Run_Minimal_Server" for more details.
|
* See sample "Run_Minimal_Server" for more details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var mqttFactory = new MqttFactory(new ConsoleLogger());
|
var mqttServerFactory = new MqttServerFactory(new ConsoleLogger());
|
||||||
|
|
||||||
var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
|
var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
|
||||||
|
|
||||||
using (var mqttServer = mqttFactory.CreateMqttServer(mqttServerOptions))
|
using (var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions))
|
||||||
{
|
{
|
||||||
await mqttServer.StartAsync();
|
await mqttServer.StartAsync();
|
||||||
|
|
||||||
@@ -123,11 +123,11 @@ public static class Server_Simple_Samples
|
|||||||
* See _Run_Minimal_Server_ for more information.
|
* See _Run_Minimal_Server_ for more information.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var mqttFactory = new MqttFactory();
|
var mqttServerFactory = new MqttServerFactory();
|
||||||
|
|
||||||
var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
|
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
|
// Setup connection validation before starting the server so that there is
|
||||||
// no change to connect without valid credentials.
|
// no change to connect without valid credentials.
|
||||||
@@ -162,11 +162,11 @@ public static class Server_Simple_Samples
|
|||||||
|
|
||||||
static async Task<MqttServer> StartMqttServer()
|
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!
|
// 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();
|
||||||
var server = mqttFactory.CreateMqttServer(mqttServerOptions);
|
var server = mqttServerFactory.CreateMqttServer(mqttServerOptions);
|
||||||
await server.StartAsync();
|
await server.StartAsync();
|
||||||
return server;
|
return server;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|
||||||
|
// ReSharper disable UnusedType.Global
|
||||||
|
// ReSharper disable UnusedMember.Global
|
||||||
|
// ReSharper disable InconsistentNaming
|
||||||
|
|
||||||
|
using System.Net;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Security.Cryptography.X509Certificates;
|
||||||
|
using MQTTnet.Server;
|
||||||
|
|
||||||
|
namespace MQTTnet.Samples.Server;
|
||||||
|
|
||||||
|
public static class Server_TLS_Samples
|
||||||
|
{
|
||||||
|
public static async Task Run_Server_With_Self_Signed_Certificate()
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* This sample starts a simple MQTT server which will accept any TCP connection.
|
||||||
|
* It also has an encrypted connection using a self signed TLS certificate.
|
||||||
|
*
|
||||||
|
* See sample "Run_Minimal_Server" for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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 = mqttServerFactory.CreateMqttServer(mqttServerOptions))
|
||||||
|
{
|
||||||
|
await mqttServer.StartAsync();
|
||||||
|
|
||||||
|
Console.WriteLine("Press Enter to exit.");
|
||||||
|
Console.ReadLine();
|
||||||
|
|
||||||
|
// Stop and dispose the MQTT server if it is no longer needed!
|
||||||
|
await mqttServer.StopAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static X509Certificate2 CreateSelfSignedCertificate(string oid)
|
||||||
|
{
|
||||||
|
var sanBuilder = new SubjectAlternativeNameBuilder();
|
||||||
|
sanBuilder.AddIpAddress(IPAddress.Loopback);
|
||||||
|
sanBuilder.AddIpAddress(IPAddress.IPv6Loopback);
|
||||||
|
sanBuilder.AddDnsName("localhost");
|
||||||
|
|
||||||
|
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 X509EnhancedKeyUsageExtension(new OidCollection { new(oid) }, false));
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
return pfxCertificate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,35 +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" />
|
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.Compilers" Version="4.0.1" />
|
|
||||||
<PackageReference Include="Microsoft.Net.Compilers" Version="4.0.1" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup Condition=" '$(TargetFramework)' == 'netcoreapp3.1' ">
|
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="2.2.7" />
|
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.2.0" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup Condition=" '$(TargetFramework)' != 'netcoreapp3.1' ">
|
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="2.1.1" />
|
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.1.1" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\MQTTnet\MQTTnet.csproj" />
|
|
||||||
<ProjectReference Include="..\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj" />
|
|
||||||
<ProjectReference Include="..\MQTTnet.Tests\MQTTnet.Tests.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
@@ -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.Collections.Generic;
|
|
||||||
using System.IO.Pipelines;
|
|
||||||
using Microsoft.AspNetCore.Connections;
|
|
||||||
using Microsoft.AspNetCore.Http.Features;
|
|
||||||
|
|
||||||
namespace MQTTnet.AspNetCore.Tests.Mockups
|
|
||||||
{
|
|
||||||
public class ConnectionContextMockup : ConnectionContext
|
|
||||||
{
|
|
||||||
public override string ConnectionId { get; set; }
|
|
||||||
|
|
||||||
public override IFeatureCollection Features { get; }
|
|
||||||
|
|
||||||
public override IDictionary<object, object> Items { get; set; }
|
|
||||||
public override IDuplexPipe Transport { get; set; }
|
|
||||||
|
|
||||||
public ConnectionContextMockup()
|
|
||||||
{
|
|
||||||
//Transport = new DefaultConnectionContext
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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 Microsoft.AspNetCore.Connections;
|
|
||||||
using MQTTnet.Adapter;
|
|
||||||
using MQTTnet.Formatter;
|
|
||||||
using MQTTnet.Server;
|
|
||||||
using System;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using MQTTnet.Diagnostics;
|
|
||||||
|
|
||||||
namespace MQTTnet.AspNetCore.Tests.Mockups
|
|
||||||
{
|
|
||||||
public class ConnectionHandlerMockup: IMqttServerAdapter
|
|
||||||
{
|
|
||||||
public TaskCompletionSource<MqttConnectionContext> Context { get; } = new TaskCompletionSource<MqttConnectionContext>();
|
|
||||||
public Func<IMqttChannelAdapter, Task> ClientHandler { get; set; }
|
|
||||||
|
|
||||||
public async Task OnConnectedAsync(ConnectionContext connection)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var formatter = new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535));
|
|
||||||
var context = new MqttConnectionContext(formatter, connection);
|
|
||||||
Context.TrySetResult(context);
|
|
||||||
|
|
||||||
await ClientHandler(context);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Context.TrySetException(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task StartAsync(MqttServerOptions options, IMqttNetLogger logger)
|
|
||||||
{
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task StopAsync()
|
|
||||||
{
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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 class ReaderExtensionsTest
|
|
||||||
{
|
|
||||||
[TestMethod]
|
|
||||||
public void TestTryDeserialize()
|
|
||||||
{
|
|
||||||
var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535));
|
|
||||||
|
|
||||||
var buffer = serializer.Encode(new MqttPublishPacket {Topic = "a", Payload = new byte[5]}).Join();
|
|
||||||
|
|
||||||
var sequence = new ReadOnlySequence<byte>(buffer.Array, buffer.Offset, buffer.Count);
|
|
||||||
|
|
||||||
var part = sequence;
|
|
||||||
MqttPacket packet;
|
|
||||||
var consumed = part.Start;
|
|
||||||
var observed = part.Start;
|
|
||||||
var result = false;
|
|
||||||
var read = 0;
|
|
||||||
|
|
||||||
part = sequence.Slice(sequence.Start, 0); // empty message should fail
|
|
||||||
result = serializer.TryDecode(part, out packet, out consumed, out observed, out read);
|
|
||||||
Assert.IsFalse(result);
|
|
||||||
|
|
||||||
part = sequence.Slice(sequence.Start, 1); // partial fixed header should fail
|
|
||||||
result = serializer.TryDecode(part, out packet, out consumed, out observed, out read);
|
|
||||||
Assert.IsFalse(result);
|
|
||||||
|
|
||||||
part = sequence.Slice(sequence.Start, 4); // partial body should fail
|
|
||||||
result = serializer.TryDecode(part, out packet, out consumed, out observed, out read);
|
|
||||||
Assert.IsFalse(result);
|
|
||||||
|
|
||||||
part = sequence; // complete msg should work
|
|
||||||
result = serializer.TryDecode(part, out packet, out consumed, out observed, out read);
|
|
||||||
Assert.IsTrue(result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
@@ -1,20 +1,25 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net6.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
||||||
<EnableNETAnalyzers>false</EnableNETAnalyzers>
|
<EnableNETAnalyzers>false</EnableNETAnalyzers>
|
||||||
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
|
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
|
||||||
|
<NoWarn>1591;NETSDK1138;NU1803;NU1901;NU1902</NoWarn>
|
||||||
|
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||||
|
<NuGetAuditMode>all</NuGetAuditMode>
|
||||||
|
<NuGetAudit>true</NuGetAudit>
|
||||||
|
<NuGetAuditLevel>low</NuGetAuditLevel>
|
||||||
|
<AnalysisLevel>latest-Recommended</AnalysisLevel>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj"/>
|
<ProjectReference Include="..\..\Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj"/>
|
||||||
|
<ProjectReference Include="..\..\Source\MQTTnet.Server\MQTTnet.Server.csproj"/>
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.Compilers" Version="4.0.1" />
|
<ProjectReference Include="..\MQTTnet\MQTTnet.csproj"/>
|
||||||
<PackageReference Include="Microsoft.Net.Compilers" Version="4.0.1" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
clientId: 'mqttnet_web_client'
|
clientId: 'mqttnet_web_client'
|
||||||
}
|
}
|
||||||
|
|
||||||
const client = mqtt.connect('ws://localhost:5049/mqtt')
|
const client = mqtt.connect('ws://localhost:5000/mqtt')
|
||||||
|
|
||||||
client.on('connect', function () {
|
client.on('connect', function () {
|
||||||
console.log('Connected')
|
console.log('Connected')
|
||||||
|
|||||||
@@ -30,10 +30,7 @@ app.UseAuthorization();
|
|||||||
app.MapRazorPages();
|
app.MapRazorPages();
|
||||||
|
|
||||||
// Setup MQTT stuff.
|
// Setup MQTT stuff.
|
||||||
app.UseEndpoints(endpoints =>
|
app.MapMqtt("/mqtt");
|
||||||
{
|
|
||||||
endpoints.MapMqtt("/mqtt");
|
|
||||||
});
|
|
||||||
|
|
||||||
app.UseMqttServer(server =>
|
app.UseMqttServer(server =>
|
||||||
{
|
{
|
||||||
@@ -42,7 +39,7 @@ app.UseMqttServer(server =>
|
|||||||
_ = Task.Run(async () =>
|
_ = Task.Run(async () =>
|
||||||
{
|
{
|
||||||
var mqttApplicationMessage = new MqttApplicationMessageBuilder()
|
var mqttApplicationMessage = new MqttApplicationMessageBuilder()
|
||||||
.WithPayload($"Test application message from MQTTnet server.")
|
.WithPayload("Test application message from MQTTnet server.")
|
||||||
.WithTopic("message")
|
.WithTopic("message")
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
|
|||||||
@@ -7,15 +7,17 @@ using Microsoft.AspNetCore.Builder;
|
|||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using MQTTnet.Server;
|
using MQTTnet.Server;
|
||||||
|
|
||||||
namespace MQTTnet.AspNetCore
|
namespace MQTTnet.AspNetCore;
|
||||||
|
|
||||||
|
public static class ApplicationBuilderExtensions
|
||||||
{
|
{
|
||||||
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(...).")]
|
app.UseWebSockets();
|
||||||
public static IApplicationBuilder UseMqttEndpoint(this IApplicationBuilder app, string path = "/mqtt")
|
app.Use(
|
||||||
{
|
async (context, next) =>
|
||||||
app.UseWebSockets();
|
|
||||||
app.Use(async (context, next) =>
|
|
||||||
{
|
{
|
||||||
if (!context.WebSockets.IsWebSocketRequest || context.Request.Path != path)
|
if (!context.WebSockets.IsWebSocketRequest || context.Request.Path != path)
|
||||||
{
|
{
|
||||||
@@ -37,16 +39,15 @@ namespace MQTTnet.AspNetCore
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return app;
|
return app;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IApplicationBuilder UseMqttServer(this IApplicationBuilder app, Action<MqttServer> configure)
|
public static IApplicationBuilder UseMqttServer(this IApplicationBuilder app, Action<MqttServer> configure)
|
||||||
{
|
{
|
||||||
var server = app.ApplicationServices.GetRequiredService<MqttServer>();
|
var server = app.ApplicationServices.GetRequiredService<MqttServer>();
|
||||||
|
|
||||||
configure(server);
|
configure(server);
|
||||||
|
|
||||||
return app;
|
return app;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,18 +2,17 @@
|
|||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using MQTTnet.Server;
|
|
||||||
using System;
|
using System;
|
||||||
|
using MQTTnet.Server;
|
||||||
|
|
||||||
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));
|
||||||
{
|
|
||||||
ServiceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
|
|
||||||
}
|
|
||||||
|
|
||||||
public IServiceProvider ServiceProvider { get; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IServiceProvider ServiceProvider { get; }
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
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,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 || NET5_0_OR_GREATER
|
|
||||||
public override ValueTask DisposeAsync()
|
|
||||||
#else
|
|
||||||
public Task DisposeAsync()
|
|
||||||
#endif
|
|
||||||
{
|
|
||||||
IsConnected = false;
|
|
||||||
|
|
||||||
Transport?.Output.Complete();
|
|
||||||
Transport?.Input.Complete();
|
|
||||||
|
|
||||||
_socket?.Dispose();
|
|
||||||
|
|
||||||
#if NETCOREAPP3_1 || NET5_0_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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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,9 +2,7 @@
|
|||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
#if NETCOREAPP3_1 || NET5_0_OR_GREATER
|
|
||||||
|
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Routing;
|
using Microsoft.AspNetCore.Routing;
|
||||||
|
|
||||||
@@ -14,6 +12,8 @@ namespace MQTTnet.AspNetCore
|
|||||||
{
|
{
|
||||||
public static void MapMqtt(this IEndpointRouteBuilder endpoints, string pattern)
|
public static void MapMqtt(this IEndpointRouteBuilder endpoints, string pattern)
|
||||||
{
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(endpoints);
|
||||||
|
|
||||||
endpoints.MapConnectionHandler<MqttConnectionHandler>(pattern, options =>
|
endpoints.MapConnectionHandler<MqttConnectionHandler>(pattern, options =>
|
||||||
{
|
{
|
||||||
options.WebSockets.SubProtocolSelector = MqttSubProtocolSelector.SelectSubProtocol;
|
options.WebSockets.SubProtocolSelector = MqttSubProtocolSelector.SelectSubProtocol;
|
||||||
@@ -22,4 +22,3 @@ namespace MQTTnet.AspNetCore
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>netstandard2.0;netcoreapp2.1;netcoreapp3.1;net5.0;net6.0</TargetFrameworks>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
|
||||||
<AssemblyName>MQTTnet.AspNetCore</AssemblyName>
|
<AssemblyName>MQTTnet.AspNetCore</AssemblyName>
|
||||||
<RootNamespace>MQTTnet.AspNetCore</RootNamespace>
|
<RootNamespace>MQTTnet.AspNetCore</RootNamespace>
|
||||||
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
||||||
@@ -32,7 +31,13 @@
|
|||||||
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
<NoWarn>1591</NoWarn>
|
<NoWarn>1591;NETSDK1138;NU1803;NU1901;NU1902</NoWarn>
|
||||||
|
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||||
|
<NuGetAuditMode>all</NuGetAuditMode>
|
||||||
|
<NuGetAudit>true</NuGetAudit>
|
||||||
|
<NuGetAuditLevel>low</NuGetAuditLevel>
|
||||||
|
<NuGetAuditLevel>low</NuGetAuditLevel>
|
||||||
|
<AnalysisLevel>latest-Recommended</AnalysisLevel>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -43,23 +48,16 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
|
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All"/>
|
||||||
|
<FrameworkReference Include="Microsoft.AspNetCore.App"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release'">
|
||||||
<DefineConstants>RELEASE;NETSTANDARD2_0</DefineConstants>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp3.1' or '$(TargetFramework)' == 'net5.0' or '$(TargetFramework)' == 'net6.0' ">
|
|
||||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup Condition="'$(TargetFramework)' != 'netcoreapp3.1' and '$(TargetFramework)' != 'net5.0' and '$(TargetFramework)' != 'net6.0' ">
|
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Http.Connections" Version="1.1.0" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\MQTTnet\MQTTnet.csproj" />
|
<ProjectReference Include="..\MQTTnet.Server\MQTTnet.Server.csproj"/>
|
||||||
|
<ProjectReference Include="..\MQTTnet\MQTTnet.csproj"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
+5
-9
@@ -2,15 +2,12 @@
|
|||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using MQTTnet.Adapter;
|
|
||||||
using MQTTnet.AspNetCore.Client.Tcp;
|
|
||||||
using MQTTnet.Formatter;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Net;
|
using MQTTnet.Adapter;
|
||||||
using MQTTnet.Client;
|
using MQTTnet.Diagnostics.Logger;
|
||||||
using MQTTnet.Diagnostics;
|
using MQTTnet.Formatter;
|
||||||
|
|
||||||
namespace MQTTnet.AspNetCore.Client
|
namespace MQTTnet.AspNetCore
|
||||||
{
|
{
|
||||||
public sealed class MqttClientConnectionContextFactory : IMqttClientAdapterFactory
|
public sealed class MqttClientConnectionContextFactory : IMqttClientAdapterFactory
|
||||||
{
|
{
|
||||||
@@ -22,8 +19,7 @@ namespace MQTTnet.AspNetCore.Client
|
|||||||
{
|
{
|
||||||
case MqttClientTcpOptions tcpOptions:
|
case MqttClientTcpOptions tcpOptions:
|
||||||
{
|
{
|
||||||
var endpoint = new DnsEndPoint(tcpOptions.Server, tcpOptions.GetPort());
|
var tcpConnection = new SocketConnection(tcpOptions.RemoteEndpoint);
|
||||||
var tcpConnection = new TcpConnection(endpoint);
|
|
||||||
|
|
||||||
var formatter = new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(4096, 65535));
|
var formatter = new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(4096, 65535));
|
||||||
return new MqttConnectionContext(formatter, tcpConnection);
|
return new MqttConnectionContext(formatter, tcpConnection);
|
||||||
@@ -2,192 +2,238 @@
|
|||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using Microsoft.AspNetCore.Connections;
|
|
||||||
using Microsoft.AspNetCore.Http.Connections.Features;
|
|
||||||
using MQTTnet.Adapter;
|
|
||||||
using MQTTnet.AspNetCore.Client.Tcp;
|
|
||||||
using MQTTnet.Exceptions;
|
|
||||||
using MQTTnet.Formatter;
|
|
||||||
using MQTTnet.Packets;
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Buffers;
|
||||||
using System.IO.Pipelines;
|
using System.IO.Pipelines;
|
||||||
|
using System.Net;
|
||||||
using System.Security.Cryptography.X509Certificates;
|
using System.Security.Cryptography.X509Certificates;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Connections;
|
||||||
|
using Microsoft.AspNetCore.Http.Connections.Features;
|
||||||
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
|
using MQTTnet.Adapter;
|
||||||
|
using MQTTnet.Exceptions;
|
||||||
|
using MQTTnet.Formatter;
|
||||||
using MQTTnet.Internal;
|
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 AsyncLock _writerLock = new AsyncLock();
|
PacketFormatterAdapter = packetFormatterAdapter ?? throw new ArgumentNullException(nameof(packetFormatterAdapter));
|
||||||
|
_connection = connection ?? throw new ArgumentNullException(nameof(connection));
|
||||||
|
|
||||||
PipeReader _input;
|
if (!(_connection is SocketConnection tcp) || tcp.IsConnected)
|
||||||
PipeWriter _output;
|
|
||||||
|
|
||||||
public MqttConnectionContext(MqttPacketFormatterAdapter packetFormatterAdapter, ConnectionContext connection)
|
|
||||||
{
|
{
|
||||||
PacketFormatterAdapter = packetFormatterAdapter ?? throw new ArgumentNullException(nameof(packetFormatterAdapter));
|
_input = connection.Transport.Input;
|
||||||
Connection = connection ?? throw new ArgumentNullException(nameof(connection));
|
_output = connection.Transport.Output;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (Connection.Transport != null)
|
public long BytesReceived { get; private set; }
|
||||||
|
|
||||||
|
public long BytesSent { get; private set; }
|
||||||
|
|
||||||
|
public X509Certificate2 ClientCertificate
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
// mqtt over tcp
|
||||||
|
var tlsFeature = _connection.Features.Get<ITlsConnectionFeature>();
|
||||||
|
if (tlsFeature != null)
|
||||||
{
|
{
|
||||||
_input = Connection.Transport.Input;
|
return tlsFeature.ClientCertificate;
|
||||||
_output = Connection.Transport.Output;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// mqtt over websocket
|
||||||
|
var httpFeature = _connection.Features.Get<IHttpContextFeature>();
|
||||||
|
return httpFeature?.HttpContext?.Connection.ClientCertificate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public EndPoint RemoteEndPoint
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
// mqtt over tcp
|
||||||
|
if (_connection.RemoteEndPoint != null)
|
||||||
|
{
|
||||||
|
return _connection.RemoteEndPoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
// mqtt over websocket
|
||||||
|
var httpFeature = _connection.Features.Get<IHttpConnectionFeature>();
|
||||||
|
if (httpFeature?.RemoteIpAddress != null)
|
||||||
|
{
|
||||||
|
return new IPEndPoint(httpFeature.RemoteIpAddress, httpFeature.RemotePort);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsSecureConnection
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
// mqtt over tcp
|
||||||
|
var tlsFeature = _connection.Features.Get<ITlsConnectionFeature>();
|
||||||
|
if (tlsFeature != null)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// mqtt over websocket
|
||||||
|
var httpFeature = _connection.Features.Get<IHttpContextFeature>();
|
||||||
|
if (httpFeature?.HttpContext != null)
|
||||||
|
{
|
||||||
|
return httpFeature.HttpContext.Request.IsHttps;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public MqttPacketFormatterAdapter PacketFormatterAdapter { get; }
|
||||||
|
|
||||||
|
public async Task ConnectAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (_connection is SocketConnection tcp && !tcp.IsConnected)
|
||||||
|
{
|
||||||
|
await tcp.StartAsync().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Endpoint
|
_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
|
||||||
{
|
{
|
||||||
get
|
while (!cancellationToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
#if NETCOREAPP3_1
|
ReadResult readResult;
|
||||||
if (Connection?.RemoteEndPoint != null)
|
var readTask = _input.ReadAsync(cancellationToken);
|
||||||
|
if (readTask.IsCompleted)
|
||||||
{
|
{
|
||||||
return Connection.RemoteEndPoint.ToString();
|
readResult = readTask.Result;
|
||||||
}
|
}
|
||||||
#endif
|
else
|
||||||
var connection = Http?.HttpContext?.Connection;
|
|
||||||
if (connection == null)
|
|
||||||
{
|
{
|
||||||
return Connection.ConnectionId;
|
readResult = await readTask.ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $"{connection.RemoteIpAddress}:{connection.RemotePort}";
|
var buffer = readResult.Buffer;
|
||||||
|
|
||||||
|
var consumed = buffer.Start;
|
||||||
|
var observed = buffer.Start;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!buffer.IsEmpty)
|
||||||
|
{
|
||||||
|
if (PacketFormatterAdapter.TryDecode(buffer, out var packet, out consumed, out observed, out var received))
|
||||||
|
{
|
||||||
|
BytesReceived += received;
|
||||||
|
return packet;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (readResult.IsCompleted)
|
||||||
|
{
|
||||||
|
throw new MqttCommunicationException("Connection Aborted");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
// The buffer was sliced up to where it was consumed, so we can just advance to the start.
|
||||||
|
// We mark examined as buffer.End so that if we didn't receive a full frame, we'll wait for more data
|
||||||
|
// before yielding the read again.
|
||||||
|
_input.AdvanceTo(consumed, observed);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (Exception exception)
|
||||||
public bool IsSecureConnection => Http?.HttpContext?.Request?.IsHttps ?? false;
|
|
||||||
|
|
||||||
public X509Certificate2 ClientCertificate => Http?.HttpContext?.Connection?.ClientCertificate;
|
|
||||||
|
|
||||||
public ConnectionContext Connection { get; }
|
|
||||||
|
|
||||||
public MqttPacketFormatterAdapter PacketFormatterAdapter { get; }
|
|
||||||
|
|
||||||
public long BytesSent { get; set; }
|
|
||||||
|
|
||||||
public long BytesReceived { get; set; }
|
|
||||||
|
|
||||||
public bool IsReadingPacket { get; private set; }
|
|
||||||
|
|
||||||
IHttpContextFeature Http => Connection.Features.Get<IHttpContextFeature>();
|
|
||||||
|
|
||||||
public async Task ConnectAsync(CancellationToken cancellationToken)
|
|
||||||
{
|
{
|
||||||
if (Connection is TcpConnection tcp && !tcp.IsConnected)
|
// completing the channel makes sure that there is no more data read after a protocol error
|
||||||
{
|
_input?.Complete(exception);
|
||||||
await tcp.StartAsync().ConfigureAwait(false);
|
_output?.Complete(exception);
|
||||||
}
|
|
||||||
|
|
||||||
_input = Connection.Transport.Input;
|
throw;
|
||||||
_output = Connection.Transport.Output;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task DisconnectAsync(CancellationToken cancellationToken)
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ResetStatistics()
|
||||||
|
{
|
||||||
|
BytesReceived = 0;
|
||||||
|
BytesSent = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SendPacketAsync(MqttPacket packet, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
using (await _writerLock.EnterAsync(cancellationToken).ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
_input?.Complete();
|
|
||||||
_output?.Complete();
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<MqttPacket> ReceivePacketAsync(CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var input = Connection.Transport.Input;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
while (!cancellationToken.IsCancellationRequested)
|
var buffer = PacketFormatterAdapter.Encode(packet);
|
||||||
|
|
||||||
|
if (buffer.Payload.Length == 0)
|
||||||
{
|
{
|
||||||
ReadResult readResult;
|
// zero copy
|
||||||
var readTask = input.ReadAsync(cancellationToken);
|
// https://github.com/dotnet/runtime/blob/e31ddfdc4f574b26231233dc10c9a9c402f40590/src/libraries/System.IO.Pipelines/src/System/IO/Pipelines/StreamPipeWriter.cs#L279
|
||||||
if (readTask.IsCompleted)
|
await _output.WriteAsync(buffer.Packet, cancellationToken).ConfigureAwait(false);
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
else
|
||||||
catch (Exception e)
|
{
|
||||||
{
|
WritePacketBuffer(_output, buffer);
|
||||||
// completing the channel makes sure that there is no more data read after a protocol error
|
await _output.FlushAsync(cancellationToken).ConfigureAwait(false);
|
||||||
_input?.Complete(e);
|
}
|
||||||
_output?.Complete(e);
|
|
||||||
throw;
|
BytesSent += buffer.Length;
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
IsReadingPacket = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ResetStatistics()
|
|
||||||
{
|
|
||||||
BytesReceived = 0;
|
|
||||||
BytesSent = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task SendPacketAsync(MqttPacket packet, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var formatter = PacketFormatterAdapter;
|
|
||||||
|
|
||||||
using (await _writerLock.WaitAsync(cancellationToken).ConfigureAwait(false))
|
|
||||||
{
|
|
||||||
var buffer = formatter.Encode(packet);
|
|
||||||
var msg = buffer.Join().AsMemory();
|
|
||||||
var output = _output;
|
|
||||||
var result = await output.WriteAsync(msg, cancellationToken).ConfigureAwait(false);
|
|
||||||
if (result.IsCompleted)
|
|
||||||
{
|
|
||||||
BytesSent += msg.Length;
|
|
||||||
}
|
|
||||||
|
|
||||||
PacketFormatterAdapter.Cleanup();
|
PacketFormatterAdapter.Cleanup();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
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);
|
||||||
|
int offset = buffer.Packet.Count;
|
||||||
|
buffer.Payload.CopyTo(destination: span.Slice(offset));
|
||||||
|
output.Advance(buffer.Length);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,57 +2,56 @@
|
|||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Connections;
|
using Microsoft.AspNetCore.Connections;
|
||||||
using Microsoft.AspNetCore.Connections.Features;
|
using Microsoft.AspNetCore.Connections.Features;
|
||||||
using MQTTnet.Adapter;
|
using MQTTnet.Adapter;
|
||||||
using MQTTnet.Server;
|
using MQTTnet.Diagnostics.Logger;
|
||||||
using System;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using MQTTnet.Diagnostics;
|
|
||||||
using MQTTnet.Formatter;
|
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
|
transferFormatFeature.ActiveFormat = TransferFormat.Binary;
|
||||||
var transferFormatFeature = connection.Features.Get<ITransferFormatFeature>();
|
}
|
||||||
if (transferFormatFeature != null)
|
|
||||||
|
var formatter = new MqttPacketFormatterAdapter(new MqttBufferWriter(_serverOptions.WriterBufferSize, _serverOptions.WriterBufferSizeMax));
|
||||||
|
using (var adapter = new MqttConnectionContext(formatter, connection))
|
||||||
|
{
|
||||||
|
var clientHandler = ClientHandler;
|
||||||
|
if (clientHandler != null)
|
||||||
{
|
{
|
||||||
transferFormatFeature.ActiveFormat = TransferFormat.Binary;
|
await clientHandler(adapter).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task StartAsync(MqttServerOptions options, IMqttNetLogger logger)
|
||||||
|
{
|
||||||
|
_serverOptions = options;
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task StopAsync()
|
||||||
|
{
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -2,32 +2,47 @@
|
|||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using MQTTnet.Adapter;
|
using MQTTnet.Diagnostics.Logger;
|
||||||
using MQTTnet.Diagnostics;
|
|
||||||
using MQTTnet.Server;
|
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)
|
_mqttFactory = mqttFactory ?? throw new ArgumentNullException(nameof(mqttFactory));
|
||||||
: base(options, adapters, logger)
|
_hostApplicationLifetime = hostApplicationLifetime;
|
||||||
{
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public Task StartAsync(CancellationToken cancellationToken)
|
public async Task StartAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
_ = StartAsync();
|
// The yield makes sure that the hosted service is considered up and running.
|
||||||
return Task.CompletedTask;
|
await Task.Yield();
|
||||||
}
|
|
||||||
|
|
||||||
public Task StopAsync(CancellationToken cancellationToken)
|
_hostApplicationLifetime.ApplicationStarted.Register(OnStarted);
|
||||||
{
|
}
|
||||||
return StopAsync();
|
|
||||||
}
|
public Task StopAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return StopAsync(_mqttFactory.CreateMqttServerStopOptionsBuilder().Build());
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnStarted()
|
||||||
|
{
|
||||||
|
_ = StartAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,31 +7,28 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
|
|
||||||
namespace MQTTnet.AspNetCore
|
namespace MQTTnet.AspNetCore;
|
||||||
|
|
||||||
|
public static class MqttSubProtocolSelector
|
||||||
{
|
{
|
||||||
public static 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));
|
subProtocol = SelectSubProtocol(requestedSubProtocolValues);
|
||||||
|
|
||||||
string subProtocol = null;
|
|
||||||
if (request.Headers.TryGetValue("Sec-WebSocket-Protocol", out var requestedSubProtocolValues))
|
|
||||||
{
|
|
||||||
subProtocol = SelectSubProtocol(requestedSubProtocolValues);
|
|
||||||
}
|
|
||||||
|
|
||||||
return subProtocol;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string SelectSubProtocol(IList<string> requestedSubProtocolValues)
|
return subProtocol;
|
||||||
{
|
}
|
||||||
if (requestedSubProtocolValues == null) throw new ArgumentNullException(nameof(requestedSubProtocolValues));
|
|
||||||
|
|
||||||
// Order the protocols to also match "mqtt", "mqttv-3.1", "mqttv-3.11" etc.
|
public static string SelectSubProtocol(IList<string> requestedSubProtocolValues)
|
||||||
return requestedSubProtocolValues
|
{
|
||||||
.OrderByDescending(p => p.Length)
|
ArgumentNullException.ThrowIfNull(requestedSubProtocolValues);
|
||||||
.FirstOrDefault(p => p.ToLower().StartsWith("mqtt"));
|
|
||||||
}
|
// 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"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,66 +2,67 @@
|
|||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.WebSockets;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using MQTTnet.Adapter;
|
using MQTTnet.Adapter;
|
||||||
|
using MQTTnet.Diagnostics.Logger;
|
||||||
using MQTTnet.Formatter;
|
using MQTTnet.Formatter;
|
||||||
using MQTTnet.Implementations;
|
using MQTTnet.Implementations;
|
||||||
using MQTTnet.Server;
|
using MQTTnet.Server;
|
||||||
using System;
|
|
||||||
using System.Net.WebSockets;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using MQTTnet.Diagnostics;
|
|
||||||
|
|
||||||
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 = new MqttNetNullLogger();
|
}
|
||||||
|
|
||||||
public Func<IMqttChannelAdapter, Task> ClientHandler { get; set; }
|
public async Task RunWebSocketConnectionAsync(WebSocket webSocket, HttpContext httpContext)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(webSocket);
|
||||||
|
|
||||||
public Task StartAsync(MqttServerOptions options, IMqttNetLogger logger)
|
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
|
||||||
{
|
{
|
||||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
var isSecureConnection = clientCertificate != null;
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task StopAsync()
|
var clientHandler = ClientHandler;
|
||||||
{
|
if (clientHandler != null)
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task RunWebSocketConnectionAsync(WebSocket webSocket, HttpContext httpContext)
|
|
||||||
{
|
|
||||||
if (webSocket == null) throw new ArgumentNullException(nameof(webSocket));
|
|
||||||
|
|
||||||
var endpoint = $"{httpContext.Connection.RemoteIpAddress}:{httpContext.Connection.RemotePort}";
|
|
||||||
|
|
||||||
var clientCertificate = await httpContext.Connection.GetClientCertificateAsync().ConfigureAwait(false);
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
var isSecureConnection = clientCertificate != null;
|
var formatter = new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535));
|
||||||
|
var channel = new MqttWebSocketChannel(webSocket, remoteEndPoint, isSecureConnection, clientCertificate);
|
||||||
|
|
||||||
var clientHandler = ClientHandler;
|
using (var channelAdapter = new MqttChannelAdapter(channel, formatter, _logger))
|
||||||
if (clientHandler != null)
|
|
||||||
{
|
{
|
||||||
var formatter = new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535));
|
await clientHandler(channelAdapter).ConfigureAwait(false);
|
||||||
var channel = new MqttWebSocketChannel(webSocket, endpoint, isSecureConnection, clientCertificate);
|
|
||||||
|
|
||||||
using (var channelAdapter = new MqttChannelAdapter(channel, formatter, null, _logger))
|
|
||||||
{
|
|
||||||
await clientHandler(channelAdapter).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
{
|
|
||||||
clientCertificate?.Dispose();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
public void Dispose()
|
|
||||||
{
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -4,110 +4,132 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
using MQTTnet.Adapter;
|
using MQTTnet.Adapter;
|
||||||
using MQTTnet.Exceptions;
|
using MQTTnet.Exceptions;
|
||||||
using MQTTnet.Formatter;
|
using MQTTnet.Formatter;
|
||||||
using MQTTnet.Packets;
|
using MQTTnet.Packets;
|
||||||
|
|
||||||
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,
|
ArgumentNullException.ThrowIfNull(formatter);
|
||||||
in ReadOnlySequence<byte> input,
|
|
||||||
out MqttPacket packet,
|
packet = null;
|
||||||
out SequencePosition consumed,
|
consumed = input.Start;
|
||||||
out SequencePosition observed,
|
observed = input.End;
|
||||||
out int bytesRead)
|
bytesRead = 0;
|
||||||
|
var copy = input;
|
||||||
|
|
||||||
|
if (copy.Length < 2)
|
||||||
{
|
{
|
||||||
if (formatter == null) throw new ArgumentNullException(nameof(formatter));
|
return false;
|
||||||
|
|
||||||
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 = bodySlice.GetMemory().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(this in ReadOnlySequence<byte> input)
|
if (!TryReadBodyLength(ref copy, out var headerLength, out var bodyLength))
|
||||||
{
|
{
|
||||||
if (input.IsSingleSegment)
|
return false;
|
||||||
{
|
|
||||||
return input.First;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Should be rare
|
|
||||||
return input.ToArray();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
{
|
{
|
||||||
// Alorithm taken from https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/errata01/os/mqtt-v3.1.1-errata01-os-complete.html.
|
return false;
|
||||||
var multiplier = 1;
|
}
|
||||||
var value = 0;
|
|
||||||
byte encodedByte;
|
|
||||||
var index = 1;
|
|
||||||
headerLength = 0;
|
|
||||||
bodyLength = 0;
|
|
||||||
|
|
||||||
var temp = input.Slice(0, Math.Min(5, input.Length)).GetMemory().Span;
|
var bodySlice = copy.Slice(0, bodyLength);
|
||||||
|
var bodySegment = GetArraySegment(ref bodySlice);
|
||||||
|
|
||||||
do
|
var receivedMqttPacket = new ReceivedMqttPacket(fixedHeader, bodySegment, headerLength + bodyLength);
|
||||||
{
|
if (formatter.ProtocolVersion == MqttProtocolVersion.Unknown)
|
||||||
if (index == temp.Length)
|
{
|
||||||
{
|
formatter.DetectProtocolVersion(receivedMqttPacket);
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
encodedByte = temp[index];
|
packet = formatter.Decode(receivedMqttPacket);
|
||||||
index++;
|
consumed = bodySlice.End;
|
||||||
|
observed = bodySlice.End;
|
||||||
|
bytesRead = headerLength + bodyLength;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
value += (byte)(encodedByte & 127) * multiplier;
|
static ArraySegment<byte> GetArraySegment(ref ReadOnlySequence<byte> input)
|
||||||
if (multiplier > 128 * 128 * 128)
|
{
|
||||||
{
|
if (input.IsSingleSegment && MemoryMarshal.TryGetArray(input.First, out var segment))
|
||||||
throw new MqttProtocolViolationException($"Remaining length is invalid (Data={string.Join(",", temp.Slice(1, index).ToArray())}).");
|
{
|
||||||
}
|
return segment;
|
||||||
|
}
|
||||||
|
|
||||||
multiplier *= 128;
|
// Should be rare
|
||||||
} while ((encodedByte & 128) != 0);
|
var array = input.ToArray();
|
||||||
|
return new ArraySegment<byte>(array);
|
||||||
|
}
|
||||||
|
|
||||||
input = input.Slice(index);
|
|
||||||
|
|
||||||
headerLength = index;
|
static void ThrowProtocolViolationException(ReadOnlySpan<byte> valueSpan, int index)
|
||||||
bodyLength = value;
|
{
|
||||||
return true;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -4,102 +4,106 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using MQTTnet.Adapter;
|
using MQTTnet.Diagnostics.Logger;
|
||||||
using MQTTnet.Diagnostics;
|
|
||||||
using MQTTnet.Implementations;
|
|
||||||
using MQTTnet.Server;
|
using MQTTnet.Server;
|
||||||
|
using MQTTnet.Server.Internal.Adapter;
|
||||||
|
|
||||||
namespace MQTTnet.AspNetCore
|
namespace MQTTnet.AspNetCore;
|
||||||
|
|
||||||
|
public static class ServiceCollectionExtensions
|
||||||
{
|
{
|
||||||
public static class ServiceCollectionExtensions
|
public static IServiceCollection AddHostedMqttServer(this IServiceCollection services, MqttServerOptions options)
|
||||||
{
|
{
|
||||||
public static IServiceCollection AddMqttServer(this IServiceCollection serviceCollection, Action<MqttServerOptionsBuilder> configure = null)
|
ArgumentNullException.ThrowIfNull(services);
|
||||||
{
|
ArgumentNullException.ThrowIfNull(options);
|
||||||
if (serviceCollection is null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(serviceCollection));
|
|
||||||
}
|
|
||||||
|
|
||||||
serviceCollection.AddMqttConnectionHandler();
|
services.AddSingleton(options);
|
||||||
serviceCollection.AddHostedMqttServer(configure);
|
services.AddHostedMqttServer();
|
||||||
|
|
||||||
return serviceCollection;
|
return services;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IServiceCollection AddHostedMqttServer(this IServiceCollection services, MqttServerOptions options)
|
public static IServiceCollection AddHostedMqttServer(this IServiceCollection services, Action<MqttServerOptionsBuilder> configure)
|
||||||
{
|
{
|
||||||
if (options == null) throw new ArgumentNullException(nameof(options));
|
ArgumentNullException.ThrowIfNull(services);
|
||||||
|
|
||||||
services.AddSingleton(options);
|
var serverOptionsBuilder = new MqttServerOptionsBuilder();
|
||||||
|
|
||||||
services.AddHostedMqttServer();
|
configure?.Invoke(serverOptionsBuilder);
|
||||||
|
|
||||||
return services;
|
var options = serverOptionsBuilder.Build();
|
||||||
}
|
|
||||||
|
|
||||||
public static IServiceCollection AddHostedMqttServer(this IServiceCollection services, Action<MqttServerOptionsBuilder> configure = null)
|
return AddHostedMqttServer(services, options);
|
||||||
{
|
}
|
||||||
services.AddSingleton(s =>
|
|
||||||
{
|
|
||||||
var serverOptionsBuilder = new MqttServerOptionsBuilder();
|
|
||||||
configure?.Invoke(serverOptionsBuilder);
|
|
||||||
return serverOptionsBuilder.Build();
|
|
||||||
});
|
|
||||||
|
|
||||||
services.AddHostedMqttServer();
|
public static void AddHostedMqttServer(this IServiceCollection services)
|
||||||
|
{
|
||||||
|
// The user may have these services already registered.
|
||||||
|
services.TryAddSingleton<IMqttNetLogger>(MqttNetNullLogger.Instance);
|
||||||
|
services.TryAddSingleton(new MqttServerFactory());
|
||||||
|
|
||||||
return services;
|
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)
|
public static IServiceCollection AddHostedMqttServerWithServices(this IServiceCollection services, Action<AspNetMqttServerOptionsBuilder> configure)
|
||||||
{
|
{
|
||||||
services.AddSingleton(s =>
|
ArgumentNullException.ThrowIfNull(services);
|
||||||
|
|
||||||
|
services.AddSingleton(
|
||||||
|
s =>
|
||||||
{
|
{
|
||||||
var builder = new AspNetMqttServerOptionsBuilder(s);
|
var builder = new AspNetMqttServerOptionsBuilder(s);
|
||||||
configure(builder);
|
configure(builder);
|
||||||
return builder.Build();
|
return builder.Build();
|
||||||
});
|
});
|
||||||
|
|
||||||
services.AddHostedMqttServer();
|
services.AddHostedMqttServer();
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|
||||||
static IServiceCollection AddHostedMqttServer(this IServiceCollection services)
|
public static IServiceCollection AddMqttConnectionHandler(this IServiceCollection services)
|
||||||
{
|
{
|
||||||
var logger = new MqttNetEventLogger();
|
services.AddSingleton<MqttConnectionHandler>();
|
||||||
|
services.AddSingleton<IMqttServerAdapter>(s => s.GetService<MqttConnectionHandler>());
|
||||||
|
|
||||||
services.AddSingleton<IMqttNetLogger>(logger);
|
return services;
|
||||||
services.AddSingleton<MqttHostedServer>();
|
}
|
||||||
services.AddSingleton<IHostedService>(s => s.GetService<MqttHostedServer>());
|
|
||||||
services.AddSingleton<MqttServer>(s => s.GetService<MqttHostedServer>());
|
|
||||||
|
|
||||||
return services;
|
public static void AddMqttLogger(this IServiceCollection services, IMqttNetLogger logger)
|
||||||
}
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(services);
|
||||||
|
|
||||||
public static IServiceCollection AddMqttWebSocketServerAdapter(this IServiceCollection services)
|
services.AddSingleton(logger);
|
||||||
{
|
}
|
||||||
services.AddSingleton<MqttWebSocketServerAdapter>();
|
|
||||||
services.AddSingleton<IMqttServerAdapter>(s => s.GetService<MqttWebSocketServerAdapter>());
|
|
||||||
|
|
||||||
return services;
|
public static IServiceCollection AddMqttServer(this IServiceCollection serviceCollection, Action<MqttServerOptionsBuilder> configure = null)
|
||||||
}
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(serviceCollection);
|
||||||
|
|
||||||
public static IServiceCollection AddMqttTcpServerAdapter(this IServiceCollection services)
|
serviceCollection.AddMqttConnectionHandler();
|
||||||
{
|
serviceCollection.AddHostedMqttServer(configure);
|
||||||
services.AddSingleton<MqttTcpServerAdapter>();
|
|
||||||
services.AddSingleton<IMqttServerAdapter>(s => s.GetService<MqttTcpServerAdapter>());
|
|
||||||
|
|
||||||
return services;
|
return serviceCollection;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IServiceCollection AddMqttConnectionHandler(this IServiceCollection services)
|
public static IServiceCollection AddMqttTcpServerAdapter(this IServiceCollection services)
|
||||||
{
|
{
|
||||||
services.AddSingleton<MqttConnectionHandler>();
|
services.AddSingleton<MqttTcpServerAdapter>();
|
||||||
services.AddSingleton<IMqttServerAdapter>(s => s.GetService<MqttConnectionHandler>());
|
services.AddSingleton<IMqttServerAdapter>(s => s.GetService<MqttTcpServerAdapter>());
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static IServiceCollection AddMqttWebSocketServerAdapter(this IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddSingleton<MqttWebSocketServerAdapter>();
|
||||||
|
services.AddSingleton<IMqttServerAdapter>(s => s.GetService<MqttWebSocketServerAdapter>());
|
||||||
|
|
||||||
|
return services;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO.Pipelines;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace MQTTnet.AspNetCore;
|
||||||
|
|
||||||
|
public class SocketAwaitable : ICriticalNotifyCompletion
|
||||||
|
{
|
||||||
|
static readonly Action _callbackCompleted = () =>
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
|
readonly PipeScheduler _ioScheduler;
|
||||||
|
int _bytesTransferred;
|
||||||
|
|
||||||
|
Action _callback;
|
||||||
|
SocketError _error;
|
||||||
|
|
||||||
|
public SocketAwaitable(PipeScheduler ioScheduler)
|
||||||
|
{
|
||||||
|
_ioScheduler = ioScheduler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsCompleted => ReferenceEquals(_callback, _callbackCompleted);
|
||||||
|
|
||||||
|
public void Complete(int bytesTransferred, SocketError socketError)
|
||||||
|
{
|
||||||
|
_error = socketError;
|
||||||
|
_bytesTransferred = bytesTransferred;
|
||||||
|
var continuation = Interlocked.Exchange(ref _callback, _callbackCompleted);
|
||||||
|
|
||||||
|
if (continuation != null)
|
||||||
|
{
|
||||||
|
_ioScheduler.Schedule(state => ((Action)state)(), continuation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public SocketAwaitable GetAwaiter()
|
||||||
|
{
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetResult()
|
||||||
|
{
|
||||||
|
Debug.Assert(ReferenceEquals(_callback, _callbackCompleted));
|
||||||
|
|
||||||
|
_callback = null;
|
||||||
|
|
||||||
|
if (_error != SocketError.Success)
|
||||||
|
{
|
||||||
|
throw new SocketException((int)_error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _bytesTransferred;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnCompleted(Action continuation)
|
||||||
|
{
|
||||||
|
if (ReferenceEquals(_callback, _callbackCompleted) || ReferenceEquals(Interlocked.CompareExchange(ref _callback, continuation, null), _callbackCompleted))
|
||||||
|
{
|
||||||
|
Task.Run(continuation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UnsafeOnCompleted(Action continuation)
|
||||||
|
{
|
||||||
|
OnCompleted(continuation);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,261 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.IO.Pipelines;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Connections;
|
||||||
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
|
using MQTTnet.Exceptions;
|
||||||
|
|
||||||
|
namespace MQTTnet.AspNetCore;
|
||||||
|
|
||||||
|
public sealed class SocketConnection : ConnectionContext
|
||||||
|
{
|
||||||
|
readonly EndPoint _endPoint;
|
||||||
|
volatile bool _aborted;
|
||||||
|
IDuplexPipe _application;
|
||||||
|
SocketReceiver _receiver;
|
||||||
|
SocketSender _sender;
|
||||||
|
|
||||||
|
Socket _socket;
|
||||||
|
|
||||||
|
public SocketConnection(EndPoint endPoint)
|
||||||
|
{
|
||||||
|
_endPoint = endPoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SocketConnection(Socket socket)
|
||||||
|
{
|
||||||
|
_socket = socket;
|
||||||
|
_endPoint = socket.RemoteEndPoint;
|
||||||
|
|
||||||
|
_sender = new SocketSender(_socket, PipeScheduler.ThreadPool);
|
||||||
|
_receiver = new SocketReceiver(_socket, PipeScheduler.ThreadPool);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ConnectionId { get; set; }
|
||||||
|
public override IFeatureCollection Features { get; }
|
||||||
|
|
||||||
|
public bool IsConnected { get; private set; }
|
||||||
|
public override IDictionary<object, object> Items { get; set; }
|
||||||
|
public override IDuplexPipe Transport { get; set; }
|
||||||
|
|
||||||
|
public override ValueTask DisposeAsync()
|
||||||
|
{
|
||||||
|
IsConnected = false;
|
||||||
|
|
||||||
|
Transport?.Output.Complete();
|
||||||
|
Transport?.Input.Complete();
|
||||||
|
|
||||||
|
_socket?.Dispose();
|
||||||
|
|
||||||
|
return base.DisposeAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task StartAsync()
|
||||||
|
{
|
||||||
|
if (_socket == null)
|
||||||
|
{
|
||||||
|
_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
|
||||||
|
_sender = new SocketSender(_socket, PipeScheduler.ThreadPool);
|
||||||
|
_receiver = new SocketReceiver(_socket, PipeScheduler.ThreadPool);
|
||||||
|
await _socket.ConnectAsync(_endPoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
var pair = DuplexPipe.CreateConnectionPair(PipeOptions.Default, PipeOptions.Default);
|
||||||
|
|
||||||
|
Transport = pair.Transport;
|
||||||
|
_application = pair.Application;
|
||||||
|
|
||||||
|
_ = ExecuteAsync();
|
||||||
|
|
||||||
|
IsConnected = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Exception ConnectionAborted()
|
||||||
|
{
|
||||||
|
return new MqttCommunicationException("Connection Aborted");
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task DoReceive()
|
||||||
|
{
|
||||||
|
Exception error = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await ProcessReceives();
|
||||||
|
}
|
||||||
|
catch (SocketException ex) when (ex.SocketErrorCode == SocketError.ConnectionReset)
|
||||||
|
{
|
||||||
|
error = new MqttCommunicationException(ex);
|
||||||
|
}
|
||||||
|
catch (SocketException ex) when (ex.SocketErrorCode == SocketError.OperationAborted || ex.SocketErrorCode == SocketError.ConnectionAborted ||
|
||||||
|
ex.SocketErrorCode == SocketError.Interrupted || ex.SocketErrorCode == SocketError.InvalidArgument)
|
||||||
|
{
|
||||||
|
if (!_aborted)
|
||||||
|
{
|
||||||
|
// Calling Dispose after ReceiveAsync can cause an "InvalidArgument" error on *nix.
|
||||||
|
error = ConnectionAborted();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (ObjectDisposedException)
|
||||||
|
{
|
||||||
|
if (!_aborted)
|
||||||
|
{
|
||||||
|
error = ConnectionAborted();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException ex)
|
||||||
|
{
|
||||||
|
error = ex;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
error = new IOException(ex.Message, ex);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (_aborted)
|
||||||
|
{
|
||||||
|
error = error ?? ConnectionAborted();
|
||||||
|
}
|
||||||
|
|
||||||
|
_application.Output.Complete(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task<Exception> DoSend()
|
||||||
|
{
|
||||||
|
Exception error = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await ProcessSends();
|
||||||
|
}
|
||||||
|
catch (SocketException ex) when (ex.SocketErrorCode == SocketError.OperationAborted)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
catch (ObjectDisposedException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
catch (IOException ex)
|
||||||
|
{
|
||||||
|
error = ex;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
error = new IOException(ex.Message, ex);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_aborted = true;
|
||||||
|
_socket.Shutdown(SocketShutdown.Both);
|
||||||
|
}
|
||||||
|
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task ExecuteAsync()
|
||||||
|
{
|
||||||
|
Exception sendError = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Spawn send and receive logic
|
||||||
|
var receiveTask = DoReceive();
|
||||||
|
var sendTask = DoSend();
|
||||||
|
|
||||||
|
// If the sending task completes then close the receive
|
||||||
|
// We don't need to do this in the other direction because the kestrel
|
||||||
|
// will trigger the output closing once the input is complete.
|
||||||
|
if (await Task.WhenAny(receiveTask, sendTask).ConfigureAwait(false) == sendTask)
|
||||||
|
{
|
||||||
|
// Tell the reader it's being aborted
|
||||||
|
_socket.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now wait for both to complete
|
||||||
|
await receiveTask;
|
||||||
|
sendError = await sendTask;
|
||||||
|
|
||||||
|
// Dispose the socket(should noop if already called)
|
||||||
|
_socket.Dispose();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Unexpected exception in {nameof(SocketConnection)}.{nameof(StartAsync)}: " + ex);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
// Complete the output after disposing the socket
|
||||||
|
await _application.Input.CompleteAsync(sendError).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task ProcessReceives()
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
// Ensure we have some reasonable amount of buffer space
|
||||||
|
var buffer = _application.Output.GetMemory();
|
||||||
|
|
||||||
|
var bytesReceived = await _receiver.ReceiveAsync(buffer);
|
||||||
|
|
||||||
|
if (bytesReceived == 0)
|
||||||
|
{
|
||||||
|
// FIN
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
_application.Output.Advance(bytesReceived);
|
||||||
|
|
||||||
|
var flushTask = _application.Output.FlushAsync();
|
||||||
|
|
||||||
|
if (!flushTask.IsCompleted)
|
||||||
|
{
|
||||||
|
await flushTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = flushTask.GetAwaiter().GetResult();
|
||||||
|
if (result.IsCompleted)
|
||||||
|
{
|
||||||
|
// Pipe consumer is shut down, do we stop writing
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task ProcessSends()
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
// Wait for data to write from the pipe producer
|
||||||
|
var result = await _application.Input.ReadAsync();
|
||||||
|
var buffer = result.Buffer;
|
||||||
|
|
||||||
|
if (result.IsCanceled)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var end = buffer.End;
|
||||||
|
var isCompleted = result.IsCompleted;
|
||||||
|
if (!buffer.IsEmpty)
|
||||||
|
{
|
||||||
|
await _sender.SendAsync(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
_application.Input.AdvanceTo(end);
|
||||||
|
|
||||||
|
if (isCompleted)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.IO.Pipelines;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
|
||||||
|
namespace MQTTnet.AspNetCore;
|
||||||
|
|
||||||
|
public sealed class SocketReceiver
|
||||||
|
{
|
||||||
|
readonly SocketAwaitable _awaitable;
|
||||||
|
readonly SocketAsyncEventArgs _eventArgs = new();
|
||||||
|
readonly Socket _socket;
|
||||||
|
|
||||||
|
public SocketReceiver(Socket socket, PipeScheduler scheduler)
|
||||||
|
{
|
||||||
|
_socket = socket;
|
||||||
|
_awaitable = new SocketAwaitable(scheduler);
|
||||||
|
_eventArgs.UserToken = _awaitable;
|
||||||
|
_eventArgs.Completed += (_, e) => ((SocketAwaitable)e.UserToken).Complete(e.BytesTransferred, e.SocketError);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SocketAwaitable ReceiveAsync(Memory<byte> buffer)
|
||||||
|
{
|
||||||
|
_eventArgs.SetBuffer(buffer);
|
||||||
|
|
||||||
|
if (!_socket.ReceiveAsync(_eventArgs))
|
||||||
|
{
|
||||||
|
_awaitable.Complete(_eventArgs.BytesTransferred, _eventArgs.SocketError);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _awaitable;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Buffers;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO.Pipelines;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace MQTTnet.AspNetCore;
|
||||||
|
|
||||||
|
public sealed class SocketSender
|
||||||
|
{
|
||||||
|
readonly SocketAwaitable _awaitable;
|
||||||
|
readonly SocketAsyncEventArgs _eventArgs = new();
|
||||||
|
readonly Socket _socket;
|
||||||
|
|
||||||
|
List<ArraySegment<byte>> _bufferList;
|
||||||
|
|
||||||
|
public SocketSender(Socket socket, PipeScheduler scheduler)
|
||||||
|
{
|
||||||
|
_socket = socket;
|
||||||
|
_awaitable = new SocketAwaitable(scheduler);
|
||||||
|
_eventArgs.UserToken = _awaitable;
|
||||||
|
_eventArgs.Completed += (_, e) => ((SocketAwaitable)e.UserToken).Complete(e.BytesTransferred, e.SocketError);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SocketAwaitable SendAsync(in ReadOnlySequence<byte> buffers)
|
||||||
|
{
|
||||||
|
if (buffers.IsSingleSegment)
|
||||||
|
{
|
||||||
|
return SendAsync(buffers.First);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_eventArgs.MemoryBuffer.Equals(Memory<byte>.Empty))
|
||||||
|
{
|
||||||
|
_eventArgs.SetBuffer(null, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
_eventArgs.BufferList = GetBufferList(buffers);
|
||||||
|
|
||||||
|
if (!_socket.SendAsync(_eventArgs))
|
||||||
|
{
|
||||||
|
_awaitable.Complete(_eventArgs.BytesTransferred, _eventArgs.SocketError);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _awaitable;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<ArraySegment<byte>> GetBufferList(in ReadOnlySequence<byte> buffer)
|
||||||
|
{
|
||||||
|
Debug.Assert(!buffer.IsEmpty);
|
||||||
|
Debug.Assert(!buffer.IsSingleSegment);
|
||||||
|
|
||||||
|
if (_bufferList == null)
|
||||||
|
{
|
||||||
|
_bufferList = new List<ArraySegment<byte>>();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Buffers are pooled, so it's OK to root them until the next multi-buffer write.
|
||||||
|
_bufferList.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var b in buffer)
|
||||||
|
{
|
||||||
|
_bufferList.Add(b.GetArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
return _bufferList;
|
||||||
|
}
|
||||||
|
|
||||||
|
SocketAwaitable SendAsync(ReadOnlyMemory<byte> memory)
|
||||||
|
{
|
||||||
|
// The BufferList getter is much less expensive then the setter.
|
||||||
|
if (_eventArgs.BufferList != null)
|
||||||
|
{
|
||||||
|
_eventArgs.BufferList = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_eventArgs.SetBuffer(MemoryMarshal.AsMemory(memory));
|
||||||
|
|
||||||
|
if (!_socket.SendAsync(_eventArgs))
|
||||||
|
{
|
||||||
|
_awaitable.Complete(_eventArgs.BytesTransferred, _eventArgs.SocketError);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _awaitable;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using BenchmarkDotNet.Attributes;
|
||||||
|
using BenchmarkDotNet.Jobs;
|
||||||
|
using MQTTnet.Internal;
|
||||||
|
|
||||||
|
namespace MQTTnet.Benchmarks
|
||||||
|
{
|
||||||
|
[SimpleJob(RuntimeMoniker.Net60)]
|
||||||
|
[MemoryDiagnoser]
|
||||||
|
public class AsyncLockBenchmark : BaseBenchmark
|
||||||
|
{
|
||||||
|
[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++)
|
||||||
|
{
|
||||||
|
tasks[i] = Task.Run(
|
||||||
|
async () =>
|
||||||
|
{
|
||||||
|
using (await asyncLock.EnterAsync().ConfigureAwait(false))
|
||||||
|
{
|
||||||
|
var localI = globalI;
|
||||||
|
await Task.Delay(5).ConfigureAwait(false); // Increase the chance for wrong data.
|
||||||
|
localI++;
|
||||||
|
globalI = localI;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (globalI != tasksCount)
|
||||||
|
{
|
||||||
|
throw new Exception($"Code is broken ({globalI})!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public async Task Wait_100_000_Times()
|
||||||
|
{
|
||||||
|
var asyncLock = new AsyncLock();
|
||||||
|
|
||||||
|
using (var cancellationToken = new CancellationTokenSource())
|
||||||
|
{
|
||||||
|
for (var i = 0; i < 100000; i++)
|
||||||
|
{
|
||||||
|
using (await asyncLock.EnterAsync(cancellationToken.Token).ConfigureAwait(false))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,7 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
namespace MQTTnet.Benchmarks
|
namespace MQTTnet.Benchmarks
|
||||||
{
|
{
|
||||||
public abstract class BaseBenchmark
|
public abstract class BaseBenchmark
|
||||||
|
|||||||
@@ -2,54 +2,25 @@
|
|||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using BenchmarkDotNet.Attributes;
|
|
||||||
using MQTTnet.Adapter;
|
|
||||||
using MQTTnet.Internal;
|
|
||||||
using MQTTnet.Packets;
|
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using MQTTnet.Diagnostics;
|
using BenchmarkDotNet.Attributes;
|
||||||
|
using MQTTnet.Adapter;
|
||||||
|
using MQTTnet.Diagnostics.Logger;
|
||||||
using MQTTnet.Formatter;
|
using MQTTnet.Formatter;
|
||||||
|
using MQTTnet.Packets;
|
||||||
using MQTTnet.Tests.Mockups;
|
using MQTTnet.Tests.Mockups;
|
||||||
|
|
||||||
namespace MQTTnet.Benchmarks
|
namespace MQTTnet.Benchmarks
|
||||||
{
|
{
|
||||||
[MemoryDiagnoser]
|
[MemoryDiagnoser]
|
||||||
public sealed class ChannelAdapterBenchmark
|
public sealed class ChannelAdapterBenchmark : BaseBenchmark
|
||||||
{
|
{
|
||||||
MqttChannelAdapter _channelAdapter;
|
MqttChannelAdapter _channelAdapter;
|
||||||
int _iterations;
|
int _iterations;
|
||||||
MemoryStream _stream;
|
|
||||||
MqttPublishPacket _packet;
|
MqttPublishPacket _packet;
|
||||||
|
MemoryStream _stream;
|
||||||
[GlobalSetup]
|
|
||||||
public void Setup()
|
|
||||||
{
|
|
||||||
_packet = new MqttPublishPacket
|
|
||||||
{
|
|
||||||
Topic = "A"
|
|
||||||
};
|
|
||||||
|
|
||||||
var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535));
|
|
||||||
|
|
||||||
var serializedPacket = Join(serializer.Encode(_packet).Join());
|
|
||||||
|
|
||||||
_iterations = 10000;
|
|
||||||
|
|
||||||
_stream = new MemoryStream(_iterations * serializedPacket.Length);
|
|
||||||
|
|
||||||
for (var i = 0; i < _iterations; i++)
|
|
||||||
{
|
|
||||||
_stream.Write(serializedPacket, 0, serializedPacket.Length);
|
|
||||||
}
|
|
||||||
|
|
||||||
_stream.Position = 0;
|
|
||||||
|
|
||||||
var channel = new MemoryMqttChannel(_stream);
|
|
||||||
|
|
||||||
_channelAdapter = new MqttChannelAdapter(channel, serializer, null, new MqttNetEventLogger());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Benchmark]
|
[Benchmark]
|
||||||
public void Receive_10000_Messages()
|
public void Receive_10000_Messages()
|
||||||
@@ -77,6 +48,34 @@ namespace MQTTnet.Benchmarks
|
|||||||
_stream.Position = 0;
|
_stream.Position = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[GlobalSetup]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
_packet = new MqttPublishPacket
|
||||||
|
{
|
||||||
|
Topic = "A"
|
||||||
|
};
|
||||||
|
|
||||||
|
var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535));
|
||||||
|
|
||||||
|
var serializedPacket = Join(serializer.Encode(_packet).Join());
|
||||||
|
|
||||||
|
_iterations = 10000;
|
||||||
|
|
||||||
|
_stream = new MemoryStream(_iterations * serializedPacket.Length);
|
||||||
|
|
||||||
|
for (var i = 0; i < _iterations; i++)
|
||||||
|
{
|
||||||
|
_stream.Write(serializedPacket, 0, serializedPacket.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
_stream.Position = 0;
|
||||||
|
|
||||||
|
var channel = new MemoryMqttChannel(_stream);
|
||||||
|
|
||||||
|
_channelAdapter = new MqttChannelAdapter(channel, serializer, new MqttNetEventLogger());
|
||||||
|
}
|
||||||
|
|
||||||
static byte[] Join(params ArraySegment<byte>[] chunks)
|
static byte[] Join(params ArraySegment<byte>[] chunks)
|
||||||
{
|
{
|
||||||
var buffer = new MemoryStream();
|
var buffer = new MemoryStream();
|
||||||
|
|||||||
@@ -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 BenchmarkDotNet.Jobs;
|
|
||||||
using BenchmarkDotNet.Validators;
|
|
||||||
|
|
||||||
namespace MQTTnet.Benchmarks.Configurations
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// this options may be used to run benchmarks in debugmode and attach a performance profiler
|
|
||||||
/// https://benchmarkdotnet.org/Configs/Configs.htm
|
|
||||||
/// </summary>
|
|
||||||
public class AllowNonOptimized : BaseConfig
|
|
||||||
{
|
|
||||||
public AllowNonOptimized()
|
|
||||||
{
|
|
||||||
AddValidator(JitOptimizationsValidator.DontFailOnError); // ALLOW NON-OPTIMIZED DLLS
|
|
||||||
AddJob(Job.InProcess);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +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 BenchmarkDotNet.Configs;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace MQTTnet.Benchmarks.Configurations
|
|
||||||
{
|
|
||||||
public class BaseConfig : ManualConfig
|
|
||||||
{
|
|
||||||
public BaseConfig()
|
|
||||||
{
|
|
||||||
AddLogger(DefaultConfig.Instance.GetLoggers().ToArray()); // manual config has no loggers by default
|
|
||||||
AddExporter(DefaultConfig.Instance.GetExporters().ToArray()); // manual config has no exporters by default
|
|
||||||
AddColumnProvider(DefaultConfig.Instance.GetColumnProviders().ToArray()); // manual config has no columns by default
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +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 BenchmarkDotNet.Environments;
|
|
||||||
using BenchmarkDotNet.Jobs;
|
|
||||||
using BenchmarkDotNet.Toolchains.CsProj;
|
|
||||||
|
|
||||||
namespace MQTTnet.Benchmarks.Configurations
|
|
||||||
{
|
|
||||||
public class RuntimeCompareConfig : BaseConfig
|
|
||||||
{
|
|
||||||
public RuntimeCompareConfig()
|
|
||||||
{
|
|
||||||
AddJob(Job.Default.WithRuntime(ClrRuntime.Net48));
|
|
||||||
AddJob(Job.Default.WithRuntime(CoreRuntime.Core50).WithToolchain(CsProjCoreToolchain.NetCoreApp50));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,14 +4,14 @@
|
|||||||
|
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using BenchmarkDotNet.Jobs;
|
using BenchmarkDotNet.Jobs;
|
||||||
using MQTTnet.Diagnostics;
|
using MQTTnet.Diagnostics.Logger;
|
||||||
|
|
||||||
namespace MQTTnet.Benchmarks
|
namespace MQTTnet.Benchmarks
|
||||||
{
|
{
|
||||||
[SimpleJob(RuntimeMoniker.Net60)]
|
[SimpleJob(RuntimeMoniker.Net60)]
|
||||||
[RPlotExporter]
|
[RPlotExporter]
|
||||||
[MemoryDiagnoser]
|
[MemoryDiagnoser]
|
||||||
public class LoggerBenchmark
|
public class LoggerBenchmark : BaseBenchmark
|
||||||
{
|
{
|
||||||
MqttNetNullLogger _nullLogger;
|
MqttNetNullLogger _nullLogger;
|
||||||
MqttNetSourceLogger _sourceNullLogger;
|
MqttNetSourceLogger _sourceNullLogger;
|
||||||
|
|||||||
@@ -1,39 +1,31 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<DebugType>Full</DebugType>
|
<DebugType>Full</DebugType>
|
||||||
<TargetFrameworks>net5.0;net6.0</TargetFrameworks>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<LangVersion>7.2</LangVersion>
|
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
<EnableNETAnalyzers>false</EnableNETAnalyzers>
|
<EnableNETAnalyzers>false</EnableNETAnalyzers>
|
||||||
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
|
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
|
||||||
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
||||||
|
<NoWarn>1591;NETSDK1138;NU1803;NU1901;NU1902;CS8892</NoWarn>
|
||||||
|
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
||||||
|
<NuGetAuditMode>all</NuGetAuditMode>
|
||||||
|
<NuGetAudit>true</NuGetAudit>
|
||||||
|
<NuGetAuditLevel>low</NuGetAuditLevel>
|
||||||
|
<AnalysisLevel>latest-Recommended</AnalysisLevel>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="BenchmarkDotNet" Version="0.13.1" />
|
<PackageReference Include="BenchmarkDotNet" Version="0.14.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore" Version="2.2.0" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup Condition="'$(TargetFramework)' != 'net5.0'">
|
|
||||||
<PackageReference Include="System.IO.Pipelines" Version="4.7.2" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup Condition="'$(TargetFramework)' == 'net5.0'">
|
|
||||||
<PackageReference Include="System.IO.Pipelines" Version="5.0.0" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup Condition="'$(TargetFramework)' == 'net6.0'">
|
|
||||||
<PackageReference Include="System.IO.Pipelines" Version="5.0.0" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj" />
|
<ProjectReference Include="..\..\Source\MQTTnet.Server\MQTTnet.Server.csproj"/>
|
||||||
|
<ProjectReference Include="..\..\Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj"/>
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.Compilers" Version="4.0.1" />
|
<ProjectReference Include="..\MQTTnet.Tests\MQTTnet.Tests.csproj"/>
|
||||||
<PackageReference Include="Microsoft.Net.Compilers" Version="4.0.1" />
|
<ProjectReference Include="..\MQTTnet\MQTTnet.csproj"/>
|
||||||
<ProjectReference Include="..\MQTTnet.Tests\MQTTnet.Tests.csproj" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
using BenchmarkDotNet.Attributes;
|
||||||
|
using BenchmarkDotNet.Jobs;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
|
||||||
|
namespace MQTTnet.Benchmarks
|
||||||
|
{
|
||||||
|
[SimpleJob(RuntimeMoniker.Net60)]
|
||||||
|
[RPlotExporter, RankColumn]
|
||||||
|
[MemoryDiagnoser]
|
||||||
|
public class MemoryCopyBenchmark
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,234 +1,217 @@
|
|||||||
using BenchmarkDotNet.Attributes;
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
using BenchmarkDotNet.Jobs;
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
using MQTTnet.Client;
|
// See the LICENSE file in the project root for more information.
|
||||||
using MQTTnet.Server;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using BenchmarkDotNet.Attributes;
|
||||||
|
using MQTTnet.Packets;
|
||||||
|
using MQTTnet.Server;
|
||||||
|
|
||||||
|
namespace MQTTnet.Benchmarks;
|
||||||
|
|
||||||
namespace MQTTnet.Benchmarks
|
[MemoryDiagnoser]
|
||||||
|
public class MessageDeliveryBenchmark : BaseBenchmark
|
||||||
{
|
{
|
||||||
/// <summary>
|
List<string> _allSubscribedTopics; // Keep track of the subset of topics that are subscribed
|
||||||
/// Create a number of topics, publish, subscribe, and wait for response
|
CancellationTokenSource _cancellationTokenSource;
|
||||||
/// </summary>
|
|
||||||
[MemoryDiagnoser]
|
object _lockMsgCount;
|
||||||
public class MessageDeliveryBenchmark
|
int _messagesExpectedCount;
|
||||||
|
int _messagesReceivedCount;
|
||||||
|
Dictionary<string, IMqttClient> _mqttPublisherClientsByPublisherName;
|
||||||
|
|
||||||
|
MqttServer _mqttServer;
|
||||||
|
List<IMqttClient> _mqttSubscriberClients;
|
||||||
|
Dictionary<string, string> _publisherByTopic;
|
||||||
|
List<MqttApplicationMessage> _topicPublishMessages;
|
||||||
|
|
||||||
|
Dictionary<string, List<string>> _topicsByPublisher;
|
||||||
|
|
||||||
|
[Params(1000, 10000)] public int NumPublishers;
|
||||||
|
|
||||||
|
[Params(5, 10, 20, 50)] public int NumSubscribedTopicsPerSubscriber;
|
||||||
|
|
||||||
|
[Params(10)] public int NumSubscribers;
|
||||||
|
|
||||||
|
[Params(1, 5)] public int NumTopicsPerPublisher;
|
||||||
|
|
||||||
|
[GlobalCleanup]
|
||||||
|
public void Cleanup()
|
||||||
{
|
{
|
||||||
List<MqttApplicationMessage> _topicPublishMessages;
|
foreach (var mp in _mqttPublisherClientsByPublisherName)
|
||||||
|
|
||||||
[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()
|
|
||||||
{
|
{
|
||||||
_lockMsgCount = new object();
|
var mqttPublisherClient = mp.Value;
|
||||||
|
mqttPublisherClient.DisconnectAsync().GetAwaiter().GetResult();
|
||||||
Dictionary<string, List<string>> singleWildcardTopicsByPublisher;
|
mqttPublisherClient.Dispose();
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
_mqttPublisherClientsByPublisherName.Clear();
|
||||||
/// Publish messages and wait for messages sent to subscribers
|
|
||||||
/// </summary>
|
foreach (var mqttSubscriber in _mqttSubscriberClients)
|
||||||
[Benchmark]
|
|
||||||
public void DeliverMessages()
|
|
||||||
{
|
{
|
||||||
// There should be one message received per publish for each subscribed topic
|
mqttSubscriber.DisconnectAsync().GetAwaiter().GetResult();
|
||||||
_messagesExpectedCount = NumSubscribedTopicsPerSubscriber * NumSubscribers;
|
mqttSubscriber.Dispose();
|
||||||
|
|
||||||
// Loop for a while and exchange messages
|
|
||||||
|
|
||||||
_messagesReceivedCount = 0;
|
|
||||||
|
|
||||||
_cancellationTokenSource = new CancellationTokenSource();
|
|
||||||
|
|
||||||
// same payload for all messages
|
|
||||||
var payload = new byte[] { 1, 2, 3, 4 };
|
|
||||||
|
|
||||||
var tasks = new List<Task>();
|
|
||||||
|
|
||||||
// publish a message for each subscribed topic
|
|
||||||
foreach (var topic in _allSubscribedTopics)
|
|
||||||
{
|
|
||||||
var message = new MqttApplicationMessageBuilder()
|
|
||||||
.WithTopic(topic)
|
|
||||||
.WithPayload(payload)
|
|
||||||
.Build();
|
|
||||||
// pick the correct publisher
|
|
||||||
var publisherName = _publisherByTopic[topic];
|
|
||||||
var publisherClient = _mqttPublisherClientsByPublisherName[publisherName];
|
|
||||||
_ = publisherClient.PublishAsync(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait one message per publish to be received by subscriber (in the subscriber's application message handler)
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Task.Delay(30000, _cancellationTokenSource.Token).GetAwaiter().GetResult();
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
_cancellationTokenSource.Dispose();
|
|
||||||
|
|
||||||
if (_messagesReceivedCount < _messagesExpectedCount)
|
|
||||||
{
|
|
||||||
throw new Exception(string.Format("Messages Received Count mismatch, expected {0}, received {1}", _messagesExpectedCount, _messagesReceivedCount));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[GlobalCleanup]
|
_mqttSubscriberClients.Clear();
|
||||||
public void Cleanup()
|
|
||||||
|
_mqttServer.StopAsync().GetAwaiter().GetResult();
|
||||||
|
_mqttServer.Dispose();
|
||||||
|
_mqttServer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Publish messages and wait for messages sent to subscribers
|
||||||
|
/// </summary>
|
||||||
|
[Benchmark]
|
||||||
|
public void DeliverMessages()
|
||||||
|
{
|
||||||
|
// There should be one message received per publish for each subscribed topic
|
||||||
|
_messagesExpectedCount = NumSubscribedTopicsPerSubscriber * NumSubscribers;
|
||||||
|
|
||||||
|
// Loop for a while and exchange messages
|
||||||
|
|
||||||
|
_messagesReceivedCount = 0;
|
||||||
|
|
||||||
|
_cancellationTokenSource = new CancellationTokenSource();
|
||||||
|
|
||||||
|
// same payload for all messages
|
||||||
|
var payload = new byte[] { 1, 2, 3, 4 };
|
||||||
|
|
||||||
|
var tasks = new List<Task>();
|
||||||
|
|
||||||
|
// publish a message for each subscribed topic
|
||||||
|
foreach (var topic in _allSubscribedTopics)
|
||||||
{
|
{
|
||||||
foreach (var mp in _mqttPublisherClientsByPublisherName)
|
var message = new MqttApplicationMessageBuilder().WithTopic(topic).WithPayload(payload).Build();
|
||||||
{
|
// pick the correct publisher
|
||||||
var mqttPublisherClient = mp.Value;
|
var publisherName = _publisherByTopic[topic];
|
||||||
mqttPublisherClient.DisconnectAsync().GetAwaiter().GetResult();
|
var publisherClient = _mqttPublisherClientsByPublisherName[publisherName];
|
||||||
mqttPublisherClient.Dispose();
|
_ = publisherClient.PublishAsync(message);
|
||||||
}
|
}
|
||||||
_mqttPublisherClientsByPublisherName.Clear();
|
|
||||||
|
|
||||||
foreach (var mqttSubscriber in _mqttSubscriberClients)
|
// Wait one message per publish to be received by subscriber (in the subscriber's application message handler)
|
||||||
{
|
try
|
||||||
mqttSubscriber.DisconnectAsync().GetAwaiter().GetResult();
|
{
|
||||||
mqttSubscriber.Dispose();
|
Task.Delay(30000, _cancellationTokenSource.Token).GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
_mqttSubscriberClients.Clear();
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
_mqttServer.StopAsync().GetAwaiter().GetResult();
|
_cancellationTokenSource.Dispose();
|
||||||
_mqttServer.Dispose();
|
|
||||||
_mqttServer = null;
|
if (_messagesReceivedCount < _messagesExpectedCount)
|
||||||
|
{
|
||||||
|
throw new Exception(string.Format("Messages Received Count mismatch, expected {0}, received {1}", _messagesExpectedCount, _messagesReceivedCount));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[GlobalSetup]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
_lockMsgCount = new object();
|
||||||
|
|
||||||
|
Dictionary<string, List<string>> singleWildcardTopicsByPublisher;
|
||||||
|
Dictionary<string, List<string>> multiWildcardTopicsByPublisher;
|
||||||
|
|
||||||
|
TopicGenerator.Generate(NumPublishers, NumTopicsPerPublisher, out _topicsByPublisher, out singleWildcardTopicsByPublisher, out multiWildcardTopicsByPublisher);
|
||||||
|
|
||||||
|
var topics = _topicsByPublisher.First().Value;
|
||||||
|
_topicPublishMessages = new List<MqttApplicationMessage>();
|
||||||
|
// Prepare messages, same for each publisher
|
||||||
|
foreach (var topic in topics)
|
||||||
|
{
|
||||||
|
var message = new MqttApplicationMessageBuilder().WithTopic(topic).WithPayload(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 }).Build();
|
||||||
|
_topicPublishMessages.Add(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create server
|
||||||
|
var serverFactory = new MqttServerFactory();
|
||||||
|
var clientFactory = new MqttClientFactory();
|
||||||
|
var serverOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
|
||||||
|
_mqttServer = serverFactory.CreateMqttServer(serverOptions);
|
||||||
|
_mqttServer.StartAsync().GetAwaiter().GetResult();
|
||||||
|
|
||||||
|
// Create publisher clients
|
||||||
|
_mqttPublisherClientsByPublisherName = new Dictionary<string, IMqttClient>();
|
||||||
|
foreach (var pt in _topicsByPublisher)
|
||||||
|
{
|
||||||
|
var publisherName = pt.Key;
|
||||||
|
var mqttClient = clientFactory.CreateMqttClient();
|
||||||
|
var publisherOptions = new MqttClientOptionsBuilder().WithTcpServer("localhost").WithClientId(publisherName).WithKeepAlivePeriod(TimeSpan.FromSeconds(30)).Build();
|
||||||
|
mqttClient.ConnectAsync(publisherOptions).GetAwaiter().GetResult();
|
||||||
|
_mqttPublisherClientsByPublisherName.Add(publisherName, mqttClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create subscriber clients
|
||||||
|
_mqttSubscriberClients = new List<IMqttClient>();
|
||||||
|
for (var i = 0; i < NumSubscribers; i++)
|
||||||
|
{
|
||||||
|
var mqttSubscriberClient = clientFactory.CreateMqttClient();
|
||||||
|
_mqttSubscriberClients.Add(mqttSubscriberClient);
|
||||||
|
|
||||||
|
var subscriberOptions = new MqttClientOptionsBuilder().WithTcpServer("localhost").WithClientId("subscriber" + i).Build();
|
||||||
|
mqttSubscriberClient.ApplicationMessageReceivedAsync += r =>
|
||||||
|
{
|
||||||
|
// count messages and signal cancellation when expected message count is reached
|
||||||
|
lock (_lockMsgCount)
|
||||||
|
{
|
||||||
|
++_messagesReceivedCount;
|
||||||
|
if (_messagesReceivedCount == _messagesExpectedCount)
|
||||||
|
{
|
||||||
|
_cancellationTokenSource.Cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
};
|
||||||
|
mqttSubscriberClient.ConnectAsync(subscriberOptions).GetAwaiter().GetResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var 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;
|
||||||
|
var topicIndexStep = totalNumTopics / (NumSubscribedTopicsPerSubscriber * NumSubscribers);
|
||||||
|
if (topicIndexStep * NumSubscribedTopicsPerSubscriber * NumSubscribers != totalNumTopics)
|
||||||
|
{
|
||||||
|
throw new Exception(
|
||||||
|
string.Format(
|
||||||
|
"The total number of topics must be divisible by the number of subscribed topics across all subscribers. Total number of topics: {0}, topic step: {1}",
|
||||||
|
totalNumTopics,
|
||||||
|
topicIndexStep));
|
||||||
|
}
|
||||||
|
|
||||||
|
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.Attributes;
|
||||||
using BenchmarkDotNet.Jobs;
|
using BenchmarkDotNet.Jobs;
|
||||||
using MQTTnet.Client;
|
|
||||||
using MQTTnet.Server;
|
using MQTTnet.Server;
|
||||||
|
|
||||||
namespace MQTTnet.Benchmarks
|
namespace MQTTnet.Benchmarks;
|
||||||
|
|
||||||
|
[SimpleJob(RuntimeMoniker.Net60)]
|
||||||
|
[RPlotExporter]
|
||||||
|
[RankColumn]
|
||||||
|
[MemoryDiagnoser]
|
||||||
|
public class MessageProcessingBenchmark : BaseBenchmark
|
||||||
{
|
{
|
||||||
[SimpleJob(RuntimeMoniker.Net60)]
|
MqttApplicationMessage _message;
|
||||||
[RPlotExporter, RankColumn]
|
IMqttClient _mqttClient;
|
||||||
[MemoryDiagnoser]
|
MqttServer _mqttServer;
|
||||||
public class MessageProcessingBenchmark
|
|
||||||
|
[Benchmark]
|
||||||
|
public void Send_10000_Messages()
|
||||||
{
|
{
|
||||||
MqttServer _mqttServer;
|
for (var i = 0; i < 10000; i++)
|
||||||
IMqttClient _mqttClient;
|
|
||||||
MqttApplicationMessage _message;
|
|
||||||
|
|
||||||
[GlobalSetup]
|
|
||||||
public void Setup()
|
|
||||||
{
|
{
|
||||||
var serverOptions = new MqttServerOptionsBuilder().Build();
|
_mqttClient.PublishAsync(_message).GetAwaiter().GetResult();
|
||||||
|
|
||||||
var factory = new MqttFactory();
|
|
||||||
_mqttServer = factory.CreateMqttServer(serverOptions);
|
|
||||||
_mqttClient = factory.CreateMqttClient();
|
|
||||||
|
|
||||||
_mqttServer.StartAsync().GetAwaiter().GetResult();
|
|
||||||
|
|
||||||
var clientOptions = new MqttClientOptionsBuilder()
|
|
||||||
.WithTcpServer("localhost").Build();
|
|
||||||
|
|
||||||
_mqttClient.ConnectAsync(clientOptions).GetAwaiter().GetResult();
|
|
||||||
|
|
||||||
_message = new MqttApplicationMessageBuilder()
|
|
||||||
.WithTopic("A")
|
|
||||||
.Build();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Benchmark]
|
|
||||||
public void Send_10000_Messages()
|
|
||||||
{
|
|
||||||
for (var i = 0; i < 10000; i++)
|
|
||||||
{
|
|
||||||
_mqttClient.PublishAsync(_message).GetAwaiter().GetResult();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[GlobalSetup]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
var serverOptions = new MqttServerOptionsBuilder().Build();
|
||||||
|
|
||||||
|
var serverFactory = new MqttServerFactory();
|
||||||
|
_mqttServer = serverFactory.CreateMqttServer(serverOptions);
|
||||||
|
var clientFactory = new MqttClientFactory();
|
||||||
|
_mqttClient = clientFactory.CreateMqttClient();
|
||||||
|
|
||||||
|
_mqttServer.StartAsync().GetAwaiter().GetResult();
|
||||||
|
|
||||||
|
var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("localhost").Build();
|
||||||
|
|
||||||
|
_mqttClient.ConnectAsync(clientOptions).GetAwaiter().GetResult();
|
||||||
|
|
||||||
|
_message = new MqttApplicationMessageBuilder().WithTopic("A").Build();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -4,18 +4,16 @@
|
|||||||
|
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using BenchmarkDotNet.Jobs;
|
using BenchmarkDotNet.Jobs;
|
||||||
using MQTTnet.Client;
|
|
||||||
using Microsoft.AspNetCore;
|
using Microsoft.AspNetCore;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using MQTTnet.AspNetCore.Client;
|
|
||||||
using MQTTnet.AspNetCore;
|
using MQTTnet.AspNetCore;
|
||||||
using MQTTnet.Diagnostics;
|
using MQTTnet.Diagnostics.Logger;
|
||||||
|
|
||||||
namespace MQTTnet.Benchmarks
|
namespace MQTTnet.Benchmarks
|
||||||
{
|
{
|
||||||
[SimpleJob(RuntimeMoniker.Net60)]
|
[SimpleJob(RuntimeMoniker.Net60)]
|
||||||
[MemoryDiagnoser]
|
[MemoryDiagnoser]
|
||||||
public class MessageProcessingMqttConnectionContextBenchmark
|
public class MessageProcessingMqttConnectionContextBenchmark : BaseBenchmark
|
||||||
{
|
{
|
||||||
IWebHost _host;
|
IWebHost _host;
|
||||||
IMqttClient _mqttClient;
|
IMqttClient _mqttClient;
|
||||||
@@ -38,7 +36,7 @@ namespace MQTTnet.Benchmarks
|
|||||||
})
|
})
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
var factory = new MqttFactory();
|
var factory = new MqttClientFactory();
|
||||||
_mqttClient = factory.CreateMqttClient(new MqttNetEventLogger(), new MqttClientConnectionContextFactory());
|
_mqttClient = factory.CreateMqttClient(new MqttNetEventLogger(), new MqttClientConnectionContextFactory());
|
||||||
|
|
||||||
_host.StartAsync().GetAwaiter().GetResult();
|
_host.StartAsync().GetAwaiter().GetResult();
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Text;
|
||||||
|
using BenchmarkDotNet.Attributes;
|
||||||
|
using BenchmarkDotNet.Jobs;
|
||||||
|
using MQTTnet.Formatter;
|
||||||
|
|
||||||
|
namespace MQTTnet.Benchmarks
|
||||||
|
{
|
||||||
|
[SimpleJob(RuntimeMoniker.Net60)]
|
||||||
|
[MemoryDiagnoser]
|
||||||
|
public class MqttBufferReaderBenchmark
|
||||||
|
{
|
||||||
|
byte[] _buffer;
|
||||||
|
int _bufferLength;
|
||||||
|
|
||||||
|
[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;
|
||||||
|
}
|
||||||
|
|
||||||
|
[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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
|
// 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;
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using BenchmarkDotNet.Jobs;
|
using BenchmarkDotNet.Jobs;
|
||||||
using MQTTnet.AspNetCore;
|
|
||||||
using MQTTnet.Formatter;
|
using MQTTnet.Formatter;
|
||||||
using MQTTnet.Tests.Mockups;
|
using MQTTnet.Tests.Mockups;
|
||||||
|
|
||||||
@@ -9,7 +12,7 @@ namespace MQTTnet.Benchmarks
|
|||||||
{
|
{
|
||||||
[SimpleJob(RuntimeMoniker.Net60)]
|
[SimpleJob(RuntimeMoniker.Net60)]
|
||||||
[MemoryDiagnoser]
|
[MemoryDiagnoser]
|
||||||
public class MqttPacketReaderWriterBenchmark
|
public class MqttPacketReaderWriterBenchmark : BaseBenchmark
|
||||||
{
|
{
|
||||||
readonly byte[] _demoPayload = new byte[1024];
|
readonly byte[] _demoPayload = new byte[1024];
|
||||||
|
|
||||||
@@ -27,17 +30,17 @@ namespace MQTTnet.Benchmarks
|
|||||||
|
|
||||||
var writer = new MqttBufferWriter(4096, 65535);
|
var writer = new MqttBufferWriter(4096, 65535);
|
||||||
writer.WriteString("A relative short string.");
|
writer.WriteString("A relative short string.");
|
||||||
writer.WriteBinaryData(_demoPayload);
|
writer.WriteBinary(_demoPayload);
|
||||||
writer.WriteByte(0x01);
|
writer.WriteByte(0x01);
|
||||||
writer.WriteByte(0x02);
|
writer.WriteByte(0x02);
|
||||||
writer.WriteVariableByteInteger(5647382);
|
writer.WriteVariableByteInteger(5647382);
|
||||||
writer.WriteString("A relative short string.");
|
writer.WriteString("A relative short string.");
|
||||||
writer.WriteVariableByteInteger(8574489);
|
writer.WriteVariableByteInteger(8574489);
|
||||||
writer.WriteBinaryData(_demoPayload);
|
writer.WriteBinary(_demoPayload);
|
||||||
writer.WriteByte(2);
|
writer.WriteByte(2);
|
||||||
writer.WriteByte(0x02);
|
writer.WriteByte(0x02);
|
||||||
writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug");
|
writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug");
|
||||||
writer.WriteBinaryData(_demoPayload);
|
writer.WriteBinary(_demoPayload);
|
||||||
|
|
||||||
_readPayload = new ArraySegment<byte>(writer.GetBuffer(), 0, writer.Length).ToArray();
|
_readPayload = new ArraySegment<byte>(writer.GetBuffer(), 0, writer.Length).ToArray();
|
||||||
}
|
}
|
||||||
@@ -80,11 +83,11 @@ namespace MQTTnet.Benchmarks
|
|||||||
writer.WriteVariableByteInteger(5647382);
|
writer.WriteVariableByteInteger(5647382);
|
||||||
writer.WriteString("A relative short string.");
|
writer.WriteString("A relative short string.");
|
||||||
writer.WriteVariableByteInteger(8574589);
|
writer.WriteVariableByteInteger(8574589);
|
||||||
writer.WriteBinaryData(_demoPayload);
|
writer.WriteBinary(_demoPayload);
|
||||||
writer.WriteByte(2);
|
writer.WriteByte(2);
|
||||||
writer.WriteByte(0x02);
|
writer.WriteByte(0x02);
|
||||||
writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug");
|
writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug");
|
||||||
writer.WriteBinaryData(_demoPayload);
|
writer.WriteBinary(_demoPayload);
|
||||||
|
|
||||||
writer.Reset(0);
|
writer.Reset(0);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,87 +2,87 @@
|
|||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using BenchmarkDotNet.Attributes;
|
using System;
|
||||||
using MQTTnet.Channel;
|
using System.Buffers;
|
||||||
using MQTTnet.Implementations;
|
using System.Reflection;
|
||||||
using MQTTnet.Server;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using BenchmarkDotNet.Attributes;
|
||||||
using BenchmarkDotNet.Jobs;
|
using BenchmarkDotNet.Jobs;
|
||||||
using MQTTnet.Client;
|
using MQTTnet.Channel;
|
||||||
using MQTTnet.Diagnostics;
|
using MQTTnet.Diagnostics.Logger;
|
||||||
|
using MQTTnet.Implementations;
|
||||||
|
using MQTTnet.Server;
|
||||||
|
using MQTTnet.Server.Internal.Adapter;
|
||||||
|
|
||||||
namespace MQTTnet.Benchmarks
|
namespace MQTTnet.Benchmarks;
|
||||||
|
|
||||||
|
[SimpleJob(RuntimeMoniker.Net60)]
|
||||||
|
[MemoryDiagnoser]
|
||||||
|
public class MqttTcpChannelBenchmark : BaseBenchmark
|
||||||
{
|
{
|
||||||
[SimpleJob(RuntimeMoniker.Net60)]
|
IMqttChannel _clientChannel;
|
||||||
[MemoryDiagnoser]
|
MqttServer _mqttServer;
|
||||||
public sealed class MqttTcpChannelBenchmark
|
IMqttChannel _serverChannel;
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public async Task Send_10000_Chunks()
|
||||||
{
|
{
|
||||||
MqttServer _mqttServer;
|
var size = 5;
|
||||||
IMqttChannel _serverChannel;
|
var iterations = 10000;
|
||||||
IMqttChannel _clientChannel;
|
|
||||||
|
|
||||||
[GlobalSetup]
|
await Task.WhenAll(WriteAsync(iterations, size), ReadAsync(iterations, size));
|
||||||
public void Setup()
|
}
|
||||||
|
|
||||||
|
[GlobalSetup]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
var serverFactory = new MqttServerFactory();
|
||||||
|
var tcpServer = new MqttTcpServerAdapter();
|
||||||
|
tcpServer.ClientHandler += args =>
|
||||||
{
|
{
|
||||||
var factory = new MqttFactory();
|
_serverChannel = (IMqttChannel)args.GetType().GetField("_channel", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(args);
|
||||||
var tcpServer = new MqttTcpServerAdapter();
|
|
||||||
tcpServer.ClientHandler += args =>
|
|
||||||
{
|
|
||||||
_serverChannel =
|
|
||||||
(IMqttChannel)args.GetType().GetField("_channel",
|
|
||||||
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)
|
|
||||||
.GetValue(args);
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
};
|
};
|
||||||
|
|
||||||
var serverOptions = new MqttServerOptionsBuilder().Build();
|
var serverOptions = new MqttServerOptionsBuilder().Build();
|
||||||
_mqttServer = factory.CreateMqttServer(serverOptions, new[] { tcpServer }, new MqttNetEventLogger());
|
_mqttServer = serverFactory.CreateMqttServer(serverOptions, new[] { tcpServer }, new MqttNetEventLogger());
|
||||||
|
|
||||||
|
|
||||||
_mqttServer.StartAsync().GetAwaiter().GetResult();
|
_mqttServer.StartAsync().GetAwaiter().GetResult();
|
||||||
|
|
||||||
var clientOptions = new MqttClientOptionsBuilder()
|
var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("localhost").Build();
|
||||||
.WithTcpServer("localhost").Build();
|
|
||||||
|
|
||||||
var tcpOptions = (MqttClientTcpOptions)clientOptions.ChannelOptions;
|
var tcpOptions = (MqttClientTcpOptions)clientOptions.ChannelOptions;
|
||||||
_clientChannel = new MqttTcpChannel(new MqttClientOptions { ChannelOptions = tcpOptions });
|
_clientChannel = new MqttTcpChannel(new MqttClientOptions { ChannelOptions = tcpOptions });
|
||||||
|
|
||||||
_clientChannel.ConnectAsync(CancellationToken.None).GetAwaiter().GetResult();
|
_clientChannel.ConnectAsync(CancellationToken.None).GetAwaiter().GetResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task ReadAsync(int iterations, int size)
|
||||||
|
{
|
||||||
|
await Task.Yield();
|
||||||
|
|
||||||
|
var expected = iterations * size;
|
||||||
|
long read = 0;
|
||||||
|
|
||||||
|
while (read < expected)
|
||||||
|
{
|
||||||
|
var readResult = await _clientChannel.ReadAsync(new byte[size], 0, size, CancellationToken.None).ConfigureAwait(false);
|
||||||
|
read += readResult;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Benchmark]
|
async Task WriteAsync(int iterations, int size)
|
||||||
public async Task Send_10000_Chunks()
|
{
|
||||||
|
await Task.Yield();
|
||||||
|
|
||||||
|
var buffer = new ReadOnlySequence<byte>(new byte[size]);
|
||||||
|
|
||||||
|
for (var i = 0; i < iterations; i++)
|
||||||
{
|
{
|
||||||
var size = 5;
|
await _serverChannel.WriteAsync(buffer, true, CancellationToken.None).ConfigureAwait(false);
|
||||||
var iterations = 10000;
|
|
||||||
|
|
||||||
await Task.WhenAll(WriteAsync(iterations, size), ReadAsync(iterations, size));
|
|
||||||
}
|
|
||||||
|
|
||||||
async Task ReadAsync(int iterations, int size)
|
|
||||||
{
|
|
||||||
await Task.Yield();
|
|
||||||
|
|
||||||
var expected = iterations * size;
|
|
||||||
long read = 0;
|
|
||||||
|
|
||||||
while (read < expected)
|
|
||||||
{
|
|
||||||
var readresult = await _clientChannel.ReadAsync(new byte[size], 0, size, CancellationToken.None).ConfigureAwait(false);
|
|
||||||
read += readresult;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async Task WriteAsync(int iterations, int size)
|
|
||||||
{
|
|
||||||
await Task.Yield();
|
|
||||||
|
|
||||||
for (var i = 0; i < iterations; i++)
|
|
||||||
{
|
|
||||||
await _serverChannel.WriteAsync(new byte[size], 0, size, CancellationToken.None).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,81 +3,140 @@
|
|||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using BenchmarkDotNet.Loggers;
|
||||||
using BenchmarkDotNet.Running;
|
using BenchmarkDotNet.Running;
|
||||||
using MQTTnet.Benchmarks.Configurations;
|
|
||||||
using MQTTnet.Diagnostics;
|
using MQTTnet.Diagnostics;
|
||||||
|
|
||||||
namespace MQTTnet.Benchmarks
|
namespace MQTTnet.Benchmarks
|
||||||
{
|
{
|
||||||
public static class Program
|
public static class Program
|
||||||
{
|
{
|
||||||
public static void Main(string[] args)
|
static int _selectedBenchmarkIndex;
|
||||||
{
|
static List<Type> _benchmarks;
|
||||||
Console.WriteLine($"MQTTnet - Benchmarks ({TargetFrameworkProvider.TargetFramework})");
|
|
||||||
Console.WriteLine("--------------------------------------------------------");
|
|
||||||
Console.WriteLine("1 = MessageProcessingBenchmark");
|
|
||||||
Console.WriteLine("2 = SerializerBenchmark");
|
|
||||||
Console.WriteLine("3 = LoggerBenchmark");
|
|
||||||
Console.WriteLine("4 = TopicFilterComparerBenchmark");
|
|
||||||
Console.WriteLine("5 = ChannelAdapterBenchmark");
|
|
||||||
Console.WriteLine("6 = MqttTcpChannelBenchmark");
|
|
||||||
Console.WriteLine("7 = TcpPipesBenchmark");
|
|
||||||
Console.WriteLine("8 = MessageProcessingMqttConnectionContextBenchmark");
|
|
||||||
Console.WriteLine("9 = ServerProcessingBenchmark");
|
|
||||||
Console.WriteLine("a = MqttPacketReaderWriterBenchmark");
|
|
||||||
Console.WriteLine("b = RoundtripBenchmark");
|
|
||||||
Console.WriteLine("c = SubscribeBenchmark");
|
|
||||||
Console.WriteLine("d = UnsubscribeBenchmark");
|
|
||||||
Console.WriteLine("e = MessageDeliveryBenchmark");
|
|
||||||
|
|
||||||
var pressedKey = Console.ReadKey(true);
|
public static void Main(string[] arguments)
|
||||||
switch (pressedKey.KeyChar)
|
{
|
||||||
|
_benchmarks = CollectBenchmarks();
|
||||||
|
HandleArguments(arguments);
|
||||||
|
|
||||||
|
while (true)
|
||||||
{
|
{
|
||||||
case '1':
|
RenderMenu();
|
||||||
BenchmarkRunner.Run<MessageProcessingBenchmark>();
|
|
||||||
break;
|
var key = Console.ReadKey(true);
|
||||||
case '2':
|
|
||||||
BenchmarkRunner.Run<SerializerBenchmark>();
|
if (key.Key == ConsoleKey.DownArrow)
|
||||||
break;
|
{
|
||||||
case '3':
|
_selectedBenchmarkIndex++;
|
||||||
BenchmarkRunner.Run<LoggerBenchmark>();
|
if (_selectedBenchmarkIndex > _benchmarks.Count - 1)
|
||||||
break;
|
{
|
||||||
case '4':
|
_selectedBenchmarkIndex = 0;
|
||||||
BenchmarkRunner.Run<TopicFilterComparerBenchmark>();
|
}
|
||||||
break;
|
}
|
||||||
case '5':
|
else if (key.Key == ConsoleKey.UpArrow)
|
||||||
BenchmarkRunner.Run<ChannelAdapterBenchmark>();
|
{
|
||||||
break;
|
_selectedBenchmarkIndex--;
|
||||||
case '6':
|
if (_selectedBenchmarkIndex < 0)
|
||||||
BenchmarkRunner.Run<MqttTcpChannelBenchmark>();
|
{
|
||||||
break;
|
_selectedBenchmarkIndex = _benchmarks.Count - 1;
|
||||||
case '7':
|
}
|
||||||
BenchmarkRunner.Run<TcpPipesBenchmark>();
|
}
|
||||||
break;
|
else if (key.Key == ConsoleKey.Escape)
|
||||||
case '8':
|
{
|
||||||
BenchmarkRunner.Run<MessageProcessingMqttConnectionContextBenchmark>(new RuntimeCompareConfig());
|
Environment.Exit(0);
|
||||||
break;
|
}
|
||||||
case '9':
|
else if (key.Key == ConsoleKey.Enter)
|
||||||
BenchmarkRunner.Run<ServerProcessingBenchmark>();
|
{
|
||||||
break;
|
|
||||||
case 'a':
|
|
||||||
BenchmarkRunner.Run(typeof(MqttPacketReaderWriterBenchmark));
|
|
||||||
break;
|
|
||||||
case 'b':
|
|
||||||
BenchmarkRunner.Run<RoundtripProcessingBenchmark>();
|
|
||||||
break;
|
|
||||||
case 'c':
|
|
||||||
BenchmarkRunner.Run<SubscribeBenchmark>();
|
|
||||||
break;
|
|
||||||
case 'd':
|
|
||||||
BenchmarkRunner.Run<UnsubscribeBenchmark>();
|
|
||||||
break;
|
|
||||||
case 'e':
|
|
||||||
BenchmarkRunner.Run<MessageDeliveryBenchmark>();
|
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BenchmarkRunner.Run(_benchmarks[_selectedBenchmarkIndex]);
|
||||||
|
|
||||||
|
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||||
|
Console.WriteLine("Press any key to exit");
|
||||||
Console.ReadLine();
|
Console.ReadLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static List<Type> CollectBenchmarks()
|
||||||
|
{
|
||||||
|
var benchmarks = new List<Type>();
|
||||||
|
|
||||||
|
var types = typeof(Program).Assembly.GetExportedTypes();
|
||||||
|
foreach (var type in types)
|
||||||
|
{
|
||||||
|
if (typeof(BaseBenchmark).IsAssignableFrom(type))
|
||||||
|
{
|
||||||
|
if (type != typeof(BaseBenchmark))
|
||||||
|
{
|
||||||
|
benchmarks.Add(type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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]));
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,240 @@
|
|||||||
|
using BenchmarkDotNet.Attributes;
|
||||||
|
using BenchmarkDotNet.Jobs;
|
||||||
|
using MQTTnet.Adapter;
|
||||||
|
using MQTTnet.AspNetCore;
|
||||||
|
using MQTTnet.Exceptions;
|
||||||
|
using MQTTnet.Formatter;
|
||||||
|
using MQTTnet.Packets;
|
||||||
|
using System;
|
||||||
|
using System.Buffers;
|
||||||
|
using System.IO;
|
||||||
|
using System.IO.Pipelines;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace MQTTnet.Benchmarks
|
||||||
|
{
|
||||||
|
[SimpleJob(RuntimeMoniker.Net60)]
|
||||||
|
[RPlotExporter, RankColumn]
|
||||||
|
[MemoryDiagnoser]
|
||||||
|
public class ReaderExtensionsBenchmark
|
||||||
|
{
|
||||||
|
MqttPacketFormatterAdapter mqttPacketFormatter;
|
||||||
|
MemoryStream stream;
|
||||||
|
|
||||||
|
[GlobalSetup]
|
||||||
|
public void GlobalSetup()
|
||||||
|
{
|
||||||
|
mqttPacketFormatter = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535));
|
||||||
|
var mqttMessage = new MqttApplicationMessageBuilder()
|
||||||
|
.WithTopic("topic")
|
||||||
|
.WithPayload(new byte[10 * 1024])
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var packet = MqttPublishPacketFactory.Create(mqttMessage);
|
||||||
|
|
||||||
|
var buffer = mqttPacketFormatter.Encode(packet);
|
||||||
|
stream = new MemoryStream();
|
||||||
|
stream.Write(buffer.Packet);
|
||||||
|
stream.Write(buffer.Payload.ToArray());
|
||||||
|
mqttPacketFormatter.Cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark(Baseline = true)]
|
||||||
|
public async Task Before()
|
||||||
|
{
|
||||||
|
stream.Position = 0;
|
||||||
|
var input = PipeReader.Create(stream);
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
ReadResult readResult;
|
||||||
|
var readTask = input.ReadAsync();
|
||||||
|
if (readTask.IsCompleted)
|
||||||
|
{
|
||||||
|
readResult = readTask.Result;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
readResult = await readTask.ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
var buffer = readResult.Buffer;
|
||||||
|
|
||||||
|
var consumed = buffer.Start;
|
||||||
|
var observed = buffer.Start;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!buffer.IsEmpty)
|
||||||
|
{
|
||||||
|
if (ReaderExtensions_Before.TryDecode(mqttPacketFormatter, buffer, out var packet, out consumed, out observed, out var received))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (readResult.IsCompleted)
|
||||||
|
{
|
||||||
|
throw new MqttCommunicationException("Connection Aborted");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
// The buffer was sliced up to where it was consumed, so we can just advance to the start.
|
||||||
|
// We mark examined as buffer.End so that if we didn't receive a full frame, we'll wait for more data
|
||||||
|
// before yielding the read again.
|
||||||
|
input.AdvanceTo(consumed, observed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public async Task After()
|
||||||
|
{
|
||||||
|
stream.Position = 0;
|
||||||
|
var input = PipeReader.Create(stream);
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
ReadResult readResult;
|
||||||
|
var readTask = input.ReadAsync();
|
||||||
|
if (readTask.IsCompleted)
|
||||||
|
{
|
||||||
|
readResult = readTask.Result;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
readResult = await readTask.ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
var buffer = readResult.Buffer;
|
||||||
|
|
||||||
|
var consumed = buffer.Start;
|
||||||
|
var observed = buffer.Start;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!buffer.IsEmpty)
|
||||||
|
{
|
||||||
|
if (ReaderExtensions.TryDecode(mqttPacketFormatter, buffer, out var packet, out consumed, out observed, out var received))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (readResult.IsCompleted)
|
||||||
|
{
|
||||||
|
throw new MqttCommunicationException("Connection Aborted");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
// The buffer was sliced up to where it was consumed, so we can just advance to the start.
|
||||||
|
// We mark examined as buffer.End so that if we didn't receive a full frame, we'll wait for more data
|
||||||
|
// before yielding the read again.
|
||||||
|
input.AdvanceTo(consumed, observed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ReaderExtensions_Before
|
||||||
|
{
|
||||||
|
public static bool TryDecode(MqttPacketFormatterAdapter formatter,
|
||||||
|
in ReadOnlySequence<byte> input,
|
||||||
|
out MqttPacket packet,
|
||||||
|
out SequencePosition consumed,
|
||||||
|
out SequencePosition observed,
|
||||||
|
out int bytesRead)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(formatter);
|
||||||
|
|
||||||
|
packet = null;
|
||||||
|
consumed = input.Start;
|
||||||
|
observed = input.End;
|
||||||
|
bytesRead = 0;
|
||||||
|
var copy = input;
|
||||||
|
|
||||||
|
if (copy.Length < 2)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var fixedHeader = copy.First.Span[0];
|
||||||
|
if (!TryReadBodyLength(ref copy, out int headerLength, out var bodyLength))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (copy.Length < bodyLength)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var bodySlice = copy.Slice(0, bodyLength);
|
||||||
|
var buffer = GetMemory(bodySlice).ToArray();
|
||||||
|
|
||||||
|
var receivedMqttPacket = new ReceivedMqttPacket(fixedHeader, new ArraySegment<byte>(buffer, 0, buffer.Length), buffer.Length + 2);
|
||||||
|
|
||||||
|
if (formatter.ProtocolVersion == MqttProtocolVersion.Unknown)
|
||||||
|
{
|
||||||
|
formatter.DetectProtocolVersion(receivedMqttPacket);
|
||||||
|
}
|
||||||
|
|
||||||
|
packet = formatter.Decode(receivedMqttPacket);
|
||||||
|
consumed = bodySlice.End;
|
||||||
|
observed = bodySlice.End;
|
||||||
|
bytesRead = headerLength + bodyLength;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ReadOnlyMemory<byte> GetMemory(in ReadOnlySequence<byte> input)
|
||||||
|
{
|
||||||
|
if (input.IsSingleSegment)
|
||||||
|
{
|
||||||
|
return input.First;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should be rare
|
||||||
|
return input.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool TryReadBodyLength(ref ReadOnlySequence<byte> input, out int headerLength, out int bodyLength)
|
||||||
|
{
|
||||||
|
// Alorithm taken from https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/errata01/os/mqtt-v3.1.1-errata01-os-complete.html.
|
||||||
|
var multiplier = 1;
|
||||||
|
var value = 0;
|
||||||
|
byte encodedByte;
|
||||||
|
var index = 1;
|
||||||
|
headerLength = 0;
|
||||||
|
bodyLength = 0;
|
||||||
|
|
||||||
|
var temp = GetMemory(input.Slice(0, Math.Min(5, input.Length))).Span;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
if (index == temp.Length)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
encodedByte = temp[index];
|
||||||
|
index++;
|
||||||
|
|
||||||
|
value += (byte)(encodedByte & 127) * multiplier;
|
||||||
|
if (multiplier > 128 * 128 * 128)
|
||||||
|
{
|
||||||
|
throw new MqttProtocolViolationException($"Remaining length is invalid (Data={string.Join(",", temp.Slice(1, index).ToArray())}).");
|
||||||
|
}
|
||||||
|
|
||||||
|
multiplier *= 128;
|
||||||
|
} while ((encodedByte & 128) != 0);
|
||||||
|
|
||||||
|
input = input.Slice(index);
|
||||||
|
|
||||||
|
headerLength = index;
|
||||||
|
bodyLength = value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,7 +8,7 @@ namespace MQTTnet.Benchmarks
|
|||||||
[SimpleJob(RuntimeMoniker.Net60)]
|
[SimpleJob(RuntimeMoniker.Net60)]
|
||||||
[RPlotExporter, RankColumn]
|
[RPlotExporter, RankColumn]
|
||||||
[MemoryDiagnoser]
|
[MemoryDiagnoser]
|
||||||
public class RoundtripProcessingBenchmark
|
public class RoundtripProcessingBenchmark : BaseBenchmark
|
||||||
{
|
{
|
||||||
[GlobalSetup]
|
[GlobalSetup]
|
||||||
public void GlobalSetup()
|
public void GlobalSetup()
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
using BenchmarkDotNet.Attributes;
|
||||||
|
using BenchmarkDotNet.Jobs;
|
||||||
|
using MQTTnet.Formatter;
|
||||||
|
using System;
|
||||||
|
using System.Buffers;
|
||||||
|
using System.IO;
|
||||||
|
using System.IO.Pipelines;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace MQTTnet.Benchmarks
|
||||||
|
{
|
||||||
|
[SimpleJob(RuntimeMoniker.Net60)]
|
||||||
|
[RPlotExporter, RankColumn]
|
||||||
|
[MemoryDiagnoser]
|
||||||
|
public class SendPacketAsyncBenchmark : BaseBenchmark
|
||||||
|
{
|
||||||
|
MemoryStream stream;
|
||||||
|
MqttPacketBuffer buffer;
|
||||||
|
|
||||||
|
[GlobalSetup]
|
||||||
|
public void GlobalSetup()
|
||||||
|
{
|
||||||
|
stream = new MemoryStream(1024);
|
||||||
|
var packet = new ArraySegment<byte>(new byte[10]);
|
||||||
|
buffer = new MqttPacketBuffer(packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark(Baseline = true)]
|
||||||
|
public async ValueTask Before()
|
||||||
|
{
|
||||||
|
stream.Position = 0;
|
||||||
|
var output = PipeWriter.Create(stream);
|
||||||
|
|
||||||
|
WritePacketBuffer(output, buffer);
|
||||||
|
await output.FlushAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public async ValueTask After()
|
||||||
|
{
|
||||||
|
stream.Position = 0;
|
||||||
|
var output = PipeWriter.Create(stream);
|
||||||
|
|
||||||
|
if (buffer.Payload.Length == 0)
|
||||||
|
{
|
||||||
|
await output.WriteAsync(buffer.Packet).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
WritePacketBuffer(output, buffer);
|
||||||
|
await output.FlushAsync().ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void WritePacketBuffer(PipeWriter output, MqttPacketBuffer buffer)
|
||||||
|
{
|
||||||
|
// copy MqttPacketBuffer's Packet and Payload to the same buffer block of PipeWriter
|
||||||
|
// MqttPacket will be transmitted within the bounds of a WebSocket frame after PipeWriter.FlushAsync
|
||||||
|
|
||||||
|
var span = output.GetSpan(buffer.Length);
|
||||||
|
|
||||||
|
buffer.Packet.AsSpan().CopyTo(span);
|
||||||
|
buffer.Payload.CopyTo(span.Slice(buffer.Packet.Count));
|
||||||
|
|
||||||
|
output.Advance(buffer.Length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,7 +13,9 @@ using MQTTnet.Channel;
|
|||||||
using MQTTnet.Formatter;
|
using MQTTnet.Formatter;
|
||||||
using MQTTnet.Formatter.V3;
|
using MQTTnet.Formatter.V3;
|
||||||
using BenchmarkDotNet.Jobs;
|
using BenchmarkDotNet.Jobs;
|
||||||
using MQTTnet.Diagnostics;
|
using MQTTnet.Diagnostics.Logger;
|
||||||
|
using System.Buffers;
|
||||||
|
using System.Net;
|
||||||
|
|
||||||
namespace MQTTnet.Benchmarks
|
namespace MQTTnet.Benchmarks
|
||||||
{
|
{
|
||||||
@@ -54,7 +56,7 @@ namespace MQTTnet.Benchmarks
|
|||||||
public void Deserialize_10000_Messages()
|
public void Deserialize_10000_Messages()
|
||||||
{
|
{
|
||||||
var channel = new BenchmarkMqttChannel(_serializedPacket);
|
var channel = new BenchmarkMqttChannel(_serializedPacket);
|
||||||
var reader = new MqttChannelAdapter(channel, new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535)), null, new MqttNetEventLogger());
|
var reader = new MqttChannelAdapter(channel, new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535)), new MqttNetEventLogger());
|
||||||
|
|
||||||
for (var i = 0; i < 10000; i++)
|
for (var i = 0; i < 10000; i++)
|
||||||
{
|
{
|
||||||
@@ -75,7 +77,7 @@ namespace MQTTnet.Benchmarks
|
|||||||
_position = _buffer.Offset;
|
_position = _buffer.Offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Endpoint { get; } = string.Empty;
|
public EndPoint RemoteEndPoint { get; set; }
|
||||||
|
|
||||||
public bool IsSecureConnection { get; } = false;
|
public bool IsSecureConnection { get; } = false;
|
||||||
|
|
||||||
@@ -104,7 +106,7 @@ namespace MQTTnet.Benchmarks
|
|||||||
return Task.FromResult(count);
|
return Task.FromResult(count);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
public Task WriteAsync(ReadOnlySequence<byte> buffer, bool isEndOfPacket, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
throw new NotSupportedException();
|
throw new NotSupportedException();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using BenchmarkDotNet.Jobs;
|
using BenchmarkDotNet.Jobs;
|
||||||
using MQTTnet.Tests.Mockups;
|
using MQTTnet.Tests.Mockups;
|
||||||
@@ -8,7 +12,7 @@ namespace MQTTnet.Benchmarks
|
|||||||
[SimpleJob(RuntimeMoniker.Net60)]
|
[SimpleJob(RuntimeMoniker.Net60)]
|
||||||
[RPlotExporter, RankColumn]
|
[RPlotExporter, RankColumn]
|
||||||
[MemoryDiagnoser]
|
[MemoryDiagnoser]
|
||||||
public class ServerProcessingBenchmark
|
public class ServerProcessingBenchmark : BaseBenchmark
|
||||||
{
|
{
|
||||||
[GlobalSetup]
|
[GlobalSetup]
|
||||||
public void GlobalSetup()
|
public void GlobalSetup()
|
||||||
|
|||||||
@@ -1,62 +1,60 @@
|
|||||||
using BenchmarkDotNet.Attributes;
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
using BenchmarkDotNet.Jobs;
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
using MQTTnet.Client;
|
// See the LICENSE file in the project root for more information.
|
||||||
using MQTTnet.Server;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using BenchmarkDotNet.Attributes;
|
||||||
|
using MQTTnet.Server;
|
||||||
|
|
||||||
namespace MQTTnet.Benchmarks
|
namespace MQTTnet.Benchmarks;
|
||||||
|
|
||||||
|
[MemoryDiagnoser]
|
||||||
|
public class SubscribeBenchmark : BaseBenchmark
|
||||||
{
|
{
|
||||||
[MemoryDiagnoser]
|
const int NumPublishers = 1;
|
||||||
public class SubscribeBenchmark
|
const int NumTopicsPerPublisher = 10000;
|
||||||
|
IMqttClient _mqttClient;
|
||||||
|
MqttServer _mqttServer;
|
||||||
|
|
||||||
|
List<string> _topics;
|
||||||
|
|
||||||
|
[GlobalCleanup]
|
||||||
|
public void Cleanup()
|
||||||
{
|
{
|
||||||
MqttServer _mqttServer;
|
_mqttClient.DisconnectAsync().GetAwaiter().GetResult();
|
||||||
IMqttClient _mqttClient;
|
_mqttServer.StopAsync().GetAwaiter().GetResult();
|
||||||
|
_mqttServer.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
const int NumPublishers = 1;
|
[GlobalSetup]
|
||||||
const int NumTopicsPerPublisher = 10000;
|
public void Setup()
|
||||||
|
{
|
||||||
|
TopicGenerator.Generate(NumPublishers, NumTopicsPerPublisher, out var topicsByPublisher, out var singleWildcardTopicsByPublisher, out var multiWildcardTopicsByPublisher);
|
||||||
|
_topics = topicsByPublisher.Values.First();
|
||||||
|
|
||||||
List<string> _topics;
|
var serverOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
|
||||||
|
|
||||||
[GlobalSetup]
|
var serverFactory = new MqttServerFactory();
|
||||||
public void Setup()
|
_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);
|
var subscribeOptions = new MqttClientSubscribeOptionsBuilder().WithTopicFilter(topic).Build();
|
||||||
_topics = topicsByPublisher.Values.First();
|
|
||||||
|
|
||||||
var serverOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
|
_mqttClient.SubscribeAsync(subscribeOptions).GetAwaiter().GetResult();
|
||||||
|
|
||||||
var factory = new MqttFactory();
|
|
||||||
_mqttServer = factory.CreateMqttServer(serverOptions);
|
|
||||||
_mqttClient = factory.CreateMqttClient();
|
|
||||||
|
|
||||||
_mqttServer.StartAsync().GetAwaiter().GetResult();
|
|
||||||
|
|
||||||
var clientOptions = new MqttClientOptionsBuilder()
|
|
||||||
.WithTcpServer("localhost").Build();
|
|
||||||
|
|
||||||
_mqttClient.ConnectAsync(clientOptions).GetAwaiter().GetResult();
|
|
||||||
}
|
|
||||||
|
|
||||||
[GlobalCleanup]
|
|
||||||
public void Cleanup()
|
|
||||||
{
|
|
||||||
_mqttClient.DisconnectAsync().GetAwaiter().GetResult();
|
|
||||||
_mqttServer.StopAsync().GetAwaiter().GetResult();
|
|
||||||
_mqttServer.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Benchmark]
|
|
||||||
public void Subscribe_10000_Topics()
|
|
||||||
{
|
|
||||||
foreach (var topic in _topics)
|
|
||||||
{
|
|
||||||
var subscribeOptions = new MqttClientSubscribeOptionsBuilder()
|
|
||||||
.WithTopicFilter(topic, Protocol.MqttQualityOfServiceLevel.AtMostOnce)
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
_mqttClient.SubscribeAsync(subscribeOptions).GetAwaiter().GetResult();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -9,13 +9,13 @@ using System.Threading;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using BenchmarkDotNet.Jobs;
|
using BenchmarkDotNet.Jobs;
|
||||||
using MQTTnet.AspNetCore.Client.Tcp;
|
using MQTTnet.AspNetCore;
|
||||||
|
|
||||||
namespace MQTTnet.Benchmarks
|
namespace MQTTnet.Benchmarks
|
||||||
{
|
{
|
||||||
[SimpleJob(RuntimeMoniker.Net60)]
|
[SimpleJob(RuntimeMoniker.Net60)]
|
||||||
[MemoryDiagnoser]
|
[MemoryDiagnoser]
|
||||||
public class TcpPipesBenchmark
|
public class TcpPipesBenchmark : BaseBenchmark
|
||||||
{
|
{
|
||||||
IDuplexPipe _client;
|
IDuplexPipe _client;
|
||||||
IDuplexPipe _server;
|
IDuplexPipe _server;
|
||||||
@@ -29,12 +29,12 @@ namespace MQTTnet.Benchmarks
|
|||||||
|
|
||||||
var task = Task.Run(() => server.AcceptSocket());
|
var task = Task.Run(() => server.AcceptSocket());
|
||||||
|
|
||||||
var clientConnection = new TcpConnection(new IPEndPoint(IPAddress.Loopback, 1883));
|
var clientConnection = new SocketConnection(new IPEndPoint(IPAddress.Loopback, 1883));
|
||||||
|
|
||||||
clientConnection.StartAsync().GetAwaiter().GetResult();
|
clientConnection.StartAsync().GetAwaiter().GetResult();
|
||||||
_client = clientConnection.Transport;
|
_client = clientConnection.Transport;
|
||||||
|
|
||||||
var serverConnection = new TcpConnection(task.GetAwaiter().GetResult());
|
var serverConnection = new SocketConnection(task.GetAwaiter().GetResult());
|
||||||
serverConnection.StartAsync().GetAwaiter().GetResult();
|
serverConnection.StartAsync().GetAwaiter().GetResult();
|
||||||
_server = serverConnection.Transport;
|
_server = serverConnection.Transport;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ namespace MQTTnet.Benchmarks
|
|||||||
[SimpleJob(RuntimeMoniker.Net60)]
|
[SimpleJob(RuntimeMoniker.Net60)]
|
||||||
[RPlotExporter]
|
[RPlotExporter]
|
||||||
[MemoryDiagnoser]
|
[MemoryDiagnoser]
|
||||||
public class TopicFilterComparerBenchmark
|
public class TopicFilterComparerBenchmark : BaseBenchmark
|
||||||
{
|
{
|
||||||
static readonly char[] TopicLevelSeparator = { '/' };
|
static readonly char[] TopicLevelSeparator = { '/' };
|
||||||
|
|
||||||
@@ -73,15 +73,8 @@ namespace MQTTnet.Benchmarks
|
|||||||
|
|
||||||
static bool LegacyMethodByStringSplit(string topic, string filter)
|
static bool LegacyMethodByStringSplit(string topic, string filter)
|
||||||
{
|
{
|
||||||
if (topic == null)
|
ArgumentNullException.ThrowIfNull(topic);
|
||||||
{
|
ArgumentNullException.ThrowIfNull(filter);
|
||||||
throw new ArgumentNullException(nameof(topic));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filter == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(filter));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.Equals(topic, filter, StringComparison.Ordinal))
|
if (string.Equals(topic, filter, StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,31 +1,30 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace MQTTnet.Benchmarks
|
namespace MQTTnet.Benchmarks
|
||||||
{
|
{
|
||||||
public class TopicGenerator
|
public class TopicGenerator
|
||||||
{
|
{
|
||||||
public static void Generate(
|
public static void Generate(
|
||||||
int numPublishers, int numTopicsPerPublisher,
|
int numPublishers,
|
||||||
out Dictionary<string, List<string>> topicsByPublisher,
|
int numTopicsPerPublisher,
|
||||||
out Dictionary<string, List<string>> singleWildcardTopicsByPublisher,
|
out Dictionary<string, List<string>> topicsByPublisher,
|
||||||
out Dictionary<string, List<string>> multiWildcardTopicsByPublisher
|
out Dictionary<string, List<string>> singleWildcardTopicsByPublisher,
|
||||||
)
|
out Dictionary<string, List<string>> multiWildcardTopicsByPublisher)
|
||||||
{
|
{
|
||||||
topicsByPublisher = new Dictionary<string, List<string>>();
|
topicsByPublisher = new Dictionary<string, List<string>>();
|
||||||
singleWildcardTopicsByPublisher = new Dictionary<string, List<string>>();
|
singleWildcardTopicsByPublisher = new Dictionary<string, List<string>>();
|
||||||
multiWildcardTopicsByPublisher = new Dictionary<string, List<string>>();
|
multiWildcardTopicsByPublisher = new Dictionary<string, List<string>>();
|
||||||
|
|
||||||
// Find some reasonable distribution across three topic levels
|
// Find some reasonable distribution across three topic levels
|
||||||
var topicsPerLevel = (int)Math.Pow(numTopicsPerPublisher, (1.0 / 3.0));
|
var topicsPerLevel = (int)Math.Pow(numTopicsPerPublisher, 1.0 / 3.0);
|
||||||
if (topicsPerLevel <= 0)
|
if (topicsPerLevel <= 0)
|
||||||
{
|
{
|
||||||
topicsPerLevel = 1;
|
topicsPerLevel = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int numLevel1Topics = topicsPerLevel;
|
var numLevel1Topics = topicsPerLevel;
|
||||||
int numLevel2Topics = topicsPerLevel;
|
var numLevel2Topics = topicsPerLevel;
|
||||||
|
|
||||||
var maxNumLevel3Topics = 1 + (int)((double)numTopicsPerPublisher / numLevel1Topics / numLevel2Topics);
|
var maxNumLevel3Topics = 1 + (int)((double)numTopicsPerPublisher / numLevel1Topics / numLevel2Topics);
|
||||||
if (maxNumLevel3Topics <= 0)
|
if (maxNumLevel3Topics <= 0)
|
||||||
@@ -35,7 +34,7 @@ namespace MQTTnet.Benchmarks
|
|||||||
|
|
||||||
for (var p = 0; p < numPublishers; ++p)
|
for (var p = 0; p < numPublishers; ++p)
|
||||||
{
|
{
|
||||||
int publisherTopicCount = 0;
|
var publisherTopicCount = 0;
|
||||||
var publisherName = "pub" + p;
|
var publisherName = "pub" + p;
|
||||||
for (var l1 = 0; l1 < numLevel1Topics; ++l1)
|
for (var l1 = 0; l1 < numLevel1Topics; ++l1)
|
||||||
{
|
{
|
||||||
@@ -44,19 +43,22 @@ namespace MQTTnet.Benchmarks
|
|||||||
for (var l3 = 0; l3 < maxNumLevel3Topics; ++l3)
|
for (var l3 = 0; l3 < maxNumLevel3Topics; ++l3)
|
||||||
{
|
{
|
||||||
if (publisherTopicCount >= numTopicsPerPublisher)
|
if (publisherTopicCount >= numTopicsPerPublisher)
|
||||||
|
{
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
var topic = string.Format("{0}/building{1}/level{2}/sensor{3}", publisherName, l1 + 1, l2 + 1, l3 + 1);
|
var topic = $"{publisherName}/building{l1 + 1}/level{l2 + 1}/sensor{l3 + 1}";
|
||||||
AddPublisherTopic(publisherName, topic, topicsByPublisher);
|
AddPublisherTopic(publisherName, topic, topicsByPublisher);
|
||||||
|
|
||||||
if (l2 == 0)
|
if (l2 == 0)
|
||||||
{
|
{
|
||||||
var singleWildcardTopic = string.Format("{0}/building{1}/+/sensor{2}", publisherName, l1 + 1, l3 + 1);
|
var singleWildcardTopic = $"{publisherName}/building{l1 + 1}/+/sensor{l3 + 1}";
|
||||||
AddPublisherTopic(publisherName, singleWildcardTopic, singleWildcardTopicsByPublisher);
|
AddPublisherTopic(publisherName, singleWildcardTopic, singleWildcardTopicsByPublisher);
|
||||||
}
|
}
|
||||||
if ((l1 == 0) && (l3 == 0))
|
|
||||||
|
if (l1 == 0 && l3 == 0)
|
||||||
{
|
{
|
||||||
var multiWildcardTopic = string.Format("{0}/+/level{1}/+", publisherName, l2 + 1);
|
var multiWildcardTopic = $"{publisherName}/+/level{l2 + 1}/+";
|
||||||
AddPublisherTopic(publisherName, multiWildcardTopic, multiWildcardTopicsByPublisher);
|
AddPublisherTopic(publisherName, multiWildcardTopic, multiWildcardTopicsByPublisher);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,6 +77,7 @@ namespace MQTTnet.Benchmarks
|
|||||||
topicList = new List<string>();
|
topicList = new List<string>();
|
||||||
topicsByPublisher.Add(publisherName, topicList);
|
topicsByPublisher.Add(publisherName, topicList);
|
||||||
}
|
}
|
||||||
|
|
||||||
topicList.Add(topic);
|
topicList.Add(topic);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,60 +1,57 @@
|
|||||||
using BenchmarkDotNet.Attributes;
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
using MQTTnet.Client;
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
using MQTTnet.Server;
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using BenchmarkDotNet.Attributes;
|
||||||
|
using MQTTnet.Server;
|
||||||
|
|
||||||
namespace MQTTnet.Benchmarks
|
namespace MQTTnet.Benchmarks;
|
||||||
|
|
||||||
|
[MemoryDiagnoser]
|
||||||
|
public class UnsubscribeBenchmark : BaseBenchmark
|
||||||
{
|
{
|
||||||
[MemoryDiagnoser]
|
const int NumPublishers = 1;
|
||||||
public class UnsubscribeBenchmark
|
const int NumTopicsPerPublisher = 10000;
|
||||||
|
IMqttClient _mqttClient;
|
||||||
|
MqttServer _mqttServer;
|
||||||
|
|
||||||
|
List<string> _topics;
|
||||||
|
|
||||||
|
[GlobalSetup]
|
||||||
|
public void Setup()
|
||||||
{
|
{
|
||||||
MqttServer _mqttServer;
|
TopicGenerator.Generate(NumPublishers, NumTopicsPerPublisher, out var topicsByPublisher, out var singleWildcardTopicsByPublisher, out var multiWildcardTopicsByPublisher);
|
||||||
IMqttClient _mqttClient;
|
_topics = topicsByPublisher.Values.First();
|
||||||
|
|
||||||
const int NumPublishers = 1;
|
var serverOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
|
||||||
const int NumTopicsPerPublisher = 10000;
|
|
||||||
|
|
||||||
List<string> _topics;
|
var serverFactory = new MqttServerFactory();
|
||||||
|
_mqttServer = serverFactory.CreateMqttServer(serverOptions);
|
||||||
|
var clientFactory = new MqttClientFactory();
|
||||||
|
_mqttClient = clientFactory.CreateMqttClient();
|
||||||
|
|
||||||
[GlobalSetup]
|
_mqttServer.StartAsync().GetAwaiter().GetResult();
|
||||||
public void Setup()
|
|
||||||
|
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);
|
var subscribeOptions = new MqttClientSubscribeOptionsBuilder().WithTopicFilter(topic).Build();
|
||||||
_topics = topicsByPublisher.Values.First();
|
_mqttClient.SubscribeAsync(subscribeOptions).GetAwaiter().GetResult();
|
||||||
|
|
||||||
var serverOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
|
|
||||||
|
|
||||||
var factory = new MqttFactory();
|
|
||||||
_mqttServer = factory.CreateMqttServer(serverOptions);
|
|
||||||
_mqttClient = factory.CreateMqttClient();
|
|
||||||
|
|
||||||
_mqttServer.StartAsync().GetAwaiter().GetResult();
|
|
||||||
|
|
||||||
var clientOptions = new MqttClientOptionsBuilder()
|
|
||||||
.WithTcpServer("localhost").Build();
|
|
||||||
|
|
||||||
_mqttClient.ConnectAsync(clientOptions).GetAwaiter().GetResult();
|
|
||||||
|
|
||||||
foreach (var topic in _topics)
|
|
||||||
{
|
|
||||||
var subscribeOptions = new MqttClientSubscribeOptionsBuilder()
|
|
||||||
.WithTopicFilter(topic)
|
|
||||||
.Build();
|
|
||||||
_mqttClient.SubscribeAsync(subscribeOptions).GetAwaiter().GetResult();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Benchmark]
|
[Benchmark]
|
||||||
public void Unsubscribe_10000_Topics()
|
public void Unsubscribe_10000_Topics()
|
||||||
|
{
|
||||||
|
foreach (var topic in _topics)
|
||||||
{
|
{
|
||||||
foreach (var topic in _topics)
|
var unsubscribeOptions = new MqttClientUnsubscribeOptionsBuilder().WithTopicFilter(topic).Build();
|
||||||
{
|
_mqttClient.UnsubscribeAsync(unsubscribeOptions).GetAwaiter().GetResult();
|
||||||
var unsubscribeOptions = new MqttClientUnsubscribeOptionsBuilder()
|
|
||||||
.WithTopicFilter(topic)
|
|
||||||
.Build();
|
|
||||||
_mqttClient.UnsubscribeAsync(unsubscribeOptions).GetAwaiter().GetResult();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<packages>
|
|
||||||
<package id="BenchmarkDotNet" version="0.10.14" targetFramework="net462" />
|
|
||||||
<package id="BenchmarkDotNet.Core" version="0.10.14" targetFramework="net462" />
|
|
||||||
<package id="BenchmarkDotNet.Toolchains.Roslyn" version="0.10.14" targetFramework="net462" />
|
|
||||||
<package id="Microsoft.CodeAnalysis.Analyzers" version="1.1.0" targetFramework="net462" />
|
|
||||||
<package id="Microsoft.CodeAnalysis.Common" version="2.6.1" targetFramework="net462" />
|
|
||||||
<package id="Microsoft.CodeAnalysis.CSharp" version="2.6.1" targetFramework="net462" />
|
|
||||||
<package id="Microsoft.DotNet.InternalAbstractions" version="1.0.0" targetFramework="net462" />
|
|
||||||
<package id="Microsoft.DotNet.PlatformAbstractions" version="1.1.1" targetFramework="net462" />
|
|
||||||
<package id="Microsoft.Win32.Registry" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.AppContext" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Collections" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Collections.Concurrent" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Collections.Immutable" version="1.3.1" targetFramework="net462" />
|
|
||||||
<package id="System.Console" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Diagnostics.Debug" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Diagnostics.FileVersionInfo" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Diagnostics.StackTrace" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Diagnostics.Tools" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Dynamic.Runtime" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Globalization" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.IO.Compression" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.IO.FileSystem" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.IO.FileSystem.Primitives" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Linq" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Linq.Expressions" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Reflection" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Reflection.Metadata" version="1.4.2" targetFramework="net462" />
|
|
||||||
<package id="System.Resources.ResourceManager" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Runtime" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Runtime.Extensions" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Runtime.InteropServices" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Runtime.Numerics" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Security.Cryptography.Algorithms" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Security.Cryptography.Encoding" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Security.Cryptography.Primitives" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Security.Cryptography.X509Certificates" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Text.Encoding" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Text.Encoding.CodePages" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Text.Encoding.Extensions" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Threading" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Threading.Tasks" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Threading.Tasks.Extensions" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Threading.Tasks.Parallel" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Threading.Thread" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.ValueTuple" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Xml.ReaderWriter" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Xml.XDocument" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Xml.XmlDocument" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Xml.XmlSerializer" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Xml.XPath" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Xml.XPath.XDocument" version="4.3.0" targetFramework="net462" />
|
|
||||||
</packages>
|
|
||||||
@@ -1,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();
|
|
||||||
|
|
||||||
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,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,753 +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;
|
|
||||||
|
|
||||||
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.Topic);
|
|
||||||
|
|
||||||
ManagedMqttApplicationMessage removedMessage = null;
|
|
||||||
ApplicationMessageSkippedEventArgs applicationMessageSkippedEventArgs = null;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using (await _messageQueueLock.WaitAsync(CancellationToken.None).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()
|
|
||||||
{
|
|
||||||
ThrowIfDisposed();
|
|
||||||
|
|
||||||
StopPublishing();
|
|
||||||
StopMaintainingConnection();
|
|
||||||
|
|
||||||
_messageQueue.Clear();
|
|
||||||
|
|
||||||
if (_maintainConnectionTask != null)
|
|
||||||
{
|
|
||||||
await Task.WhenAny(_maintainConnectionTask);
|
|
||||||
_maintainConnectionTask = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task SubscribeAsync(ICollection<MqttTopicFilter> topicFilters)
|
|
||||||
{
|
|
||||||
ThrowIfDisposed();
|
|
||||||
|
|
||||||
if (topicFilters == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(topicFilters));
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var topicFilter in topicFilters)
|
|
||||||
{
|
|
||||||
MqttTopicValidator.ThrowIfInvalidSubscribe(topicFilter.Topic);
|
|
||||||
}
|
|
||||||
|
|
||||||
lock (_subscriptions)
|
|
||||||
{
|
|
||||||
foreach (var topicFilter in topicFilters)
|
|
||||||
{
|
|
||||||
_subscriptions[topicFilter.Topic] = topicFilter;
|
|
||||||
_unsubscriptions.Remove(topicFilter.Topic);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_subscriptionsQueuedSignal.Release();
|
|
||||||
|
|
||||||
return Task.FromResult(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 Task.FromResult(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
_logger.Warning(exception, "Synchronizing subscriptions failed.");
|
|
||||||
|
|
||||||
if (_synchronizingSubscriptionsFailedEvent.HasHandlers)
|
|
||||||
{
|
|
||||||
await _synchronizingSubscriptionsFailedEvent.InvokeAsync(new ManagedProcessFailedEventArgs(exception)).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
|
|
||||||
{
|
|
||||||
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");
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (_reconnectSubscriptions.Any())
|
|
||||||
{
|
|
||||||
var 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).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);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (addedSubscriptions != null && addedSubscriptions.Any())
|
|
||||||
{
|
|
||||||
var subscribeOptionsBuilder = new MqttClientSubscribeOptionsBuilder();
|
|
||||||
|
|
||||||
foreach (var addedSubscription in addedSubscriptions)
|
|
||||||
{
|
|
||||||
subscribeOptionsBuilder.WithTopicFilter(addedSubscription);
|
|
||||||
}
|
|
||||||
|
|
||||||
await InternalClient.SubscribeAsync(subscribeOptionsBuilder.Build()).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception exception)
|
|
||||||
{
|
|
||||||
await HandleSubscriptionExceptionAsync(exception).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void StartPublishing()
|
|
||||||
{
|
|
||||||
StopPublishing();
|
|
||||||
|
|
||||||
var cancellationTokenSource = new CancellationTokenSource();
|
|
||||||
var cancellationToken = cancellationTokenSource.Token;
|
|
||||||
_publishingCancellationToken = cancellationTokenSource;
|
|
||||||
|
|
||||||
Task.Run(() => PublishQueuedMessagesAsync(cancellationToken), cancellationToken).RunInBackground(_logger);
|
|
||||||
}
|
|
||||||
|
|
||||||
void StopMaintainingConnection()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_connectionCancellationToken?.Cancel(false);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
_connectionCancellationToken?.Dispose();
|
|
||||||
_connectionCancellationToken = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void StopPublishing()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_publishingCancellationToken?.Cancel(false);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
_publishingCancellationToken?.Dispose();
|
|
||||||
_publishingCancellationToken = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async Task TryMaintainConnectionAsync(CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var oldConnectionState = InternalClient.IsConnected;
|
|
||||||
var connectionState = await ReconnectIfRequiredAsync(cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
if (connectionState == ReconnectionResult.NotConnected)
|
|
||||||
{
|
|
||||||
StopPublishing();
|
|
||||||
await Task.Delay(Options.AutoReconnectDelay, cancellationToken).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
else if (connectionState == ReconnectionResult.Reconnected)
|
|
||||||
{
|
|
||||||
await PublishReconnectSubscriptionsAsync().ConfigureAwait(false);
|
|
||||||
StartPublishing();
|
|
||||||
}
|
|
||||||
else if (connectionState == ReconnectionResult.Recovered)
|
|
||||||
{
|
|
||||||
StartPublishing();
|
|
||||||
}
|
|
||||||
else if (connectionState == ReconnectionResult.StillConnected)
|
|
||||||
{
|
|
||||||
await PublishSubscriptionsAsync(Options.ConnectionCheckInterval, cancellationToken).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldConnectionState != InternalClient.IsConnected)
|
|
||||||
{
|
|
||||||
await _connectionStateChangedEvent.InvokeAsync(EventArgs.Empty).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
catch (MqttCommunicationException exception)
|
|
||||||
{
|
|
||||||
_logger.Warning(exception, "Communication error while maintaining connection.");
|
|
||||||
}
|
|
||||||
catch (Exception exception)
|
|
||||||
{
|
|
||||||
_logger.Error(exception, "Error exception while maintaining connection.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async Task TryPublishQueuedMessageAsync(ManagedMqttApplicationMessage message)
|
|
||||||
{
|
|
||||||
Exception transmitException = null;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await InternalClient.PublishAsync(message.ApplicationMessage).ConfigureAwait(false);
|
|
||||||
|
|
||||||
using (await _messageQueueLock.WaitAsync(CancellationToken.None).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.WaitAsync(CancellationToken.None).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 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.WaitAsync(CancellationToken.None).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.WaitAsync(CancellationToken.None).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,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 class ManagedProcessFailedEventArgs : EventArgs
|
|
||||||
{
|
|
||||||
public ManagedProcessFailedEventArgs(Exception exception)
|
|
||||||
{
|
|
||||||
Exception = exception ?? throw new ArgumentNullException(nameof(exception));
|
|
||||||
}
|
|
||||||
|
|
||||||
public Exception Exception { get; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,33 +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.Diagnostics;
|
|
||||||
|
|
||||||
namespace MQTTnet.Extensions.ManagedClient
|
|
||||||
{
|
|
||||||
public static class MqttFactoryExtensions
|
|
||||||
{
|
|
||||||
public static IManagedMqttClient CreateManagedMqttClient(this MqttFactory factory, IMqttClient mqttClient = null)
|
|
||||||
{
|
|
||||||
if (factory == null) throw new ArgumentNullException(nameof(factory));
|
|
||||||
|
|
||||||
if (mqttClient == null)
|
|
||||||
{
|
|
||||||
return new ManagedMqttClient(factory.CreateMqttClient(), factory.DefaultLogger);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ManagedMqttClient(mqttClient, factory.DefaultLogger);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IManagedMqttClient CreateManagedMqttClient(this MqttFactory factory, IMqttNetLogger logger)
|
|
||||||
{
|
|
||||||
if (factory == null) throw new ArgumentNullException(nameof(factory));
|
|
||||||
if (logger == null) throw new ArgumentNullException(nameof(logger));
|
|
||||||
|
|
||||||
return new ManagedMqttClient(factory.CreateMqttClient(logger), logger);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+2
@@ -10,6 +10,8 @@ namespace MQTTnet.Extensions.Rpc
|
|||||||
{
|
{
|
||||||
public MqttRpcTopicPair CreateRpcTopics(TopicGenerationContext context)
|
public MqttRpcTopicPair CreateRpcTopics(TopicGenerationContext context)
|
||||||
{
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(context);
|
||||||
|
|
||||||
if (context.MethodName.Contains("/") || context.MethodName.Contains("+") || context.MethodName.Contains("#"))
|
if (context.MethodName.Contains("/") || context.MethodName.Contains("+") || context.MethodName.Contains("#"))
|
||||||
{
|
{
|
||||||
throw new ArgumentException("The method name cannot contain /, + or #.");
|
throw new ArgumentException("The method name cannot contain /, + or #.");
|
||||||
@@ -1,4 +1,9 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
@@ -7,8 +12,8 @@ namespace MQTTnet.Extensions.Rpc
|
|||||||
{
|
{
|
||||||
public interface IMqttRpcClient : IDisposable
|
public interface IMqttRpcClient : IDisposable
|
||||||
{
|
{
|
||||||
Task<byte[]> ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel);
|
Task<byte[]> ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary<string,object> parameters = null);
|
||||||
|
|
||||||
Task<byte[]> ExecuteAsync(string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, CancellationToken cancellationToken = default);
|
Task<byte[]> ExecuteAsync(string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary<string, object> parameters = null, CancellationToken cancellationToken = default);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0</TargetFrameworks>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks>
|
|
||||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
|
|
||||||
|
|
||||||
<AssemblyName>MQTTnet.Extensions.Rpc</AssemblyName>
|
<AssemblyName>MQTTnet.Extensions.Rpc</AssemblyName>
|
||||||
<RootNamespace>MQTTnet.Extensions.Rpc</RootNamespace>
|
<RootNamespace>MQTTnet.Extensions.Rpc</RootNamespace>
|
||||||
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
||||||
@@ -33,20 +30,12 @@
|
|||||||
<PackageReleaseNotes>For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).</PackageReleaseNotes>
|
<PackageReleaseNotes>For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).</PackageReleaseNotes>
|
||||||
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
<NoWarn>1591</NoWarn>
|
<NoWarn>1591;NETSDK1138;NU1803;NU1901;NU1902</NoWarn>
|
||||||
</PropertyGroup>
|
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||||
|
<NuGetAuditMode>all</NuGetAuditMode>
|
||||||
<PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0'">
|
<NuGetAudit>true</NuGetAudit>
|
||||||
<CopyLocalLockFileAssemblies>false</CopyLocalLockFileAssemblies>
|
<NuGetAuditLevel>low</NuGetAuditLevel>
|
||||||
<NugetTargetMoniker>UAP,Version=v10.0</NugetTargetMoniker>
|
<AnalysisLevel>latest-Recommended</AnalysisLevel>
|
||||||
<TargetPlatformIdentifier>UAP</TargetPlatformIdentifier>
|
|
||||||
<TargetPlatformVersion>10.0.18362.0</TargetPlatformVersion>
|
|
||||||
<TargetPlatformMinVersion>10.0.10240.0</TargetPlatformMinVersion>
|
|
||||||
<TargetFrameworkIdentifier>.NETCore</TargetFrameworkIdentifier>
|
|
||||||
<TargetFrameworkVersion>v5.0</TargetFrameworkVersion>
|
|
||||||
<DefineConstants>$(DefineConstants);WINDOWS_UWP</DefineConstants>
|
|
||||||
<DefaultLanguage>en</DefaultLanguage>
|
|
||||||
<LanguageTargets>$(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets</LanguageTargets>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -57,11 +46,11 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
|
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\MQTTnet\MQTTnet.csproj" />
|
<ProjectReference Include="..\MQTTnet\MQTTnet.csproj"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
<wpf:ResourceDictionary xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib"
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=options/@EntryIndexedValue">True</s:Boolean>
|
xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=options_005Ctopicgeneration/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
xml:space="preserve">
|
||||||
|
<s:Boolean
|
||||||
|
x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=options/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean
|
||||||
|
x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=options_005Ctopicgeneration/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||||
@@ -3,31 +3,25 @@
|
|||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using MQTTnet.Client;
|
|
||||||
|
|
||||||
namespace MQTTnet.Extensions.Rpc
|
namespace MQTTnet.Extensions.Rpc
|
||||||
{
|
{
|
||||||
public static class MqttFactoryExtensions
|
public static class MqttFactoryExtensions
|
||||||
{
|
{
|
||||||
public static IMqttRpcClient CreateMqttRpcClient(this MqttFactory factory, IMqttClient mqttClient)
|
public static IMqttRpcClient CreateMqttRpcClient(this MqttClientFactory clientFactory, IMqttClient mqttClient)
|
||||||
{
|
{
|
||||||
return factory.CreateMqttRpcClient(mqttClient, new MqttRpcClientOptions
|
return clientFactory.CreateMqttRpcClient(
|
||||||
{
|
mqttClient,
|
||||||
TopicGenerationStrategy = new DefaultMqttRpcClientTopicGenerationStrategy()
|
new MqttRpcClientOptions
|
||||||
});
|
{
|
||||||
|
TopicGenerationStrategy = new DefaultMqttRpcClientTopicGenerationStrategy()
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IMqttRpcClient CreateMqttRpcClient(this MqttFactory _, IMqttClient mqttClient, MqttRpcClientOptions rpcClientOptions)
|
public static IMqttRpcClient CreateMqttRpcClient(this MqttClientFactory _, IMqttClient mqttClient, MqttRpcClientOptions rpcClientOptions)
|
||||||
{
|
{
|
||||||
if (mqttClient == null)
|
ArgumentNullException.ThrowIfNull(mqttClient);
|
||||||
{
|
ArgumentNullException.ThrowIfNull(rpcClientOptions);
|
||||||
throw new ArgumentNullException(nameof(mqttClient));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rpcClientOptions == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(rpcClientOptions));
|
|
||||||
}
|
|
||||||
|
|
||||||
return new MqttRpcClient(mqttClient, rpcClientOptions);
|
return new MqttRpcClient(mqttClient, rpcClientOptions);
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user