mirror of
https://github.com/dotnet/MQTTnet.git
synced 2026-10-16 01:03:01 +08:00
Compare commits
234
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
798a0e2250 | ||
|
|
6e5351ce75 | ||
|
|
483447e783 | ||
|
|
18731d9f02 | ||
|
|
7be55ce922 | ||
|
|
14463d1ef9 | ||
|
|
eeb0f7a261 | ||
|
|
52c8f00ef8 | ||
|
|
bef2ec6fba | ||
|
|
d8f06280d5 | ||
|
|
cf68e5e8d5 | ||
|
|
68da92fd4b | ||
|
|
c1b46b0ae7 | ||
|
|
745d1d25fe | ||
|
|
905f070c37 | ||
|
|
bca2ded8eb | ||
|
|
8321806acf | ||
|
|
081ef3077a | ||
|
|
a3fbb928d6 | ||
|
|
42ba46b885 | ||
|
|
07409e3a35 | ||
|
|
1a3e97afec | ||
|
|
d74d83453f | ||
|
|
99e70fdbbb | ||
|
|
535a4e010c | ||
|
|
37c1eecce5 | ||
|
|
e791d9aba4 | ||
|
|
5ebc5fcb7e | ||
|
|
51979fa5a4 | ||
|
|
7ac7f256ef | ||
|
|
e7ccd897b0 | ||
|
|
d5d72ac37d | ||
|
|
a5ef10fddb | ||
|
|
c97bd82334 | ||
|
|
75ebf116cf | ||
|
|
2f7f55e3f5 | ||
|
|
e43e7f8e36 | ||
|
|
247988e633 | ||
|
|
93a9d8d1db | ||
|
|
24c418cf43 | ||
|
|
948c7c2b69 | ||
|
|
1cdc343e6d | ||
|
|
27771f0146 | ||
|
|
e7de2b913a | ||
|
|
3dd3279656 | ||
|
|
c5934bf18b | ||
|
|
7715bf37dc | ||
|
|
9dbe1e4b12 | ||
|
|
f13b3eb6bb | ||
|
|
8eb884fa5d | ||
|
|
9f81a2bb71 | ||
|
|
e2f8d0f0ac | ||
|
|
981ea02149 | ||
|
|
8566fd5395 | ||
|
|
8089c6bdbc | ||
|
|
ef5b6c9b32 | ||
|
|
4aa4a12e47 | ||
|
|
6bf2b4eaca | ||
|
|
6ef6b57064 | ||
|
|
7d4027958c | ||
|
|
bdb74642c2 | ||
|
|
63d85184e1 | ||
|
|
0506857f0d | ||
|
|
6417baa081 | ||
|
|
74bb68ad73 | ||
|
|
81a4e7cb86 | ||
|
|
2fdbb02dc8 | ||
|
|
c4eb05105b | ||
|
|
bda21f0fcd | ||
|
|
980a5d0a6d | ||
|
|
e18a91a4b5 | ||
|
|
b95d352236 | ||
|
|
c5f4cf1f9b | ||
|
|
8650002523 | ||
|
|
25c1da6a21 | ||
|
|
4e3ede42c6 | ||
|
|
918a3c8d8b | ||
|
|
290419b3b6 | ||
|
|
32b6146532 | ||
|
|
3a73ec21f7 | ||
|
|
41d5b70344 | ||
|
|
b4769778af | ||
|
|
473f9c31b1 | ||
|
|
ae4f168066 | ||
|
|
c5578a93e8 | ||
|
|
f4b2a10616 | ||
|
|
ed63aa4910 | ||
|
|
4481869d42 | ||
|
|
abbf14b1ec | ||
|
|
4f9e54ba1d | ||
|
|
d80188167a | ||
|
|
4ab956abd1 | ||
|
|
1aa91e3828 | ||
|
|
2410d7b4c1 | ||
|
|
b7328e5fd2 | ||
|
|
7f5c437c46 | ||
|
|
ae5e038548 | ||
|
|
67e0fb8b0b | ||
|
|
b763542329 | ||
|
|
d061da6077 | ||
|
|
82a235aef4 | ||
|
|
fd55982d98 | ||
|
|
aabf180b23 | ||
|
|
1245d8299a | ||
|
|
1ffffaff47 | ||
|
|
c204f6d8b2 | ||
|
|
b775fb256b | ||
|
|
f82d382f23 | ||
|
|
4214af283a | ||
|
|
b32a5c9fa0 | ||
|
|
e7e13a631f | ||
|
|
c12096705f | ||
|
|
eb25500f50 | ||
|
|
4df06980a1 | ||
|
|
f3499013f4 | ||
|
|
158a7fca46 | ||
|
|
09ab30fb0f | ||
|
|
ae84aa43d2 | ||
|
|
b431a5d3fe | ||
|
|
b9683c2943 | ||
|
|
1ba8f74555 | ||
|
|
ec637f7946 | ||
|
|
2599b3dfdc | ||
|
|
d22caa7b70 | ||
|
|
01c90badf9 | ||
|
|
e4f605fbd5 | ||
|
|
6fdee9e0db | ||
|
|
cae7206733 | ||
|
|
e2e7aacc09 | ||
|
|
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 |
+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
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
|
|
||||||
+104
-39
@@ -3,65 +3,130 @@ name: CI
|
|||||||
on: [push, pull_request]
|
on: [push, pull_request]
|
||||||
|
|
||||||
env:
|
env:
|
||||||
VERSION: "4.1.3.${{github.run_number}}"
|
VERSION: "5.2.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:
|
with:
|
||||||
dotnet-version: |
|
dotnet-version: |
|
||||||
3.1.x
|
8.0.x
|
||||||
6.0.x
|
10.0.x
|
||||||
|
|
||||||
- name: Setup MSBuild
|
- name: Checkout code
|
||||||
uses: microsoft/setup-msbuild@v1.1
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Checkout Code
|
- name: Load strong name certificate
|
||||||
uses: actions/checkout@v2
|
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}
|
||||||
|
|
||||||
- name: Setup Signing Certificate
|
|
||||||
run: |
|
run: |
|
||||||
$secret = '${{ secrets.SNC_BASE64 }}'
|
echo "$SNC_BASE64" | base64 --decode > "${{ github.workspace }}/certificate.snk"
|
||||||
$decoded = [System.Convert]::FromBase64CharArray($secret, 0, $secret.Length)
|
shell: bash
|
||||||
Set-Content -Path ${{ github.workspace }}\certificate.snk -Value $decoded -AsByteStream
|
env:
|
||||||
|
SNC_BASE64: ${{ secrets.SNC_BASE64 }}
|
||||||
|
|
||||||
- name: Restore nuget packages
|
- name: Build package (signed)
|
||||||
run: msbuild MQTTnet.sln /t:Restore /p:Configuration="Release" /verbosity:m
|
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}
|
||||||
|
run: dotnet build MQTTnet.slnx --configuration Release /p:FileVersion=${{ env.VERSION }} /p:AssemblyVersion=${{ env.VERSION }} /p:PackageVersion=${{ env.VERSION }}${{ env.PACKAGE_SUFFIX }} /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=${{ github.workspace }}/certificate.snk
|
||||||
|
|
||||||
- name: Build solution
|
- name: Build package (unsigned)
|
||||||
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
|
if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository }}
|
||||||
|
run: dotnet build MQTTnet.slnx --configuration Release /p:FileVersion=${{ env.VERSION }} /p:AssemblyVersion=${{ env.VERSION }} /p:PackageVersion=${{ env.VERSION }}${{ env.PACKAGE_SUFFIX }}
|
||||||
|
|
||||||
- name: Collect nuget Packages
|
- name: Upload nuget packages
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: nuget Packages
|
name: nugets
|
||||||
path: |
|
path: |
|
||||||
**\*.nupkg
|
${{ github.workspace }}/Source/**/*.nupkg
|
||||||
**\*.snupkg
|
${{ github.workspace }}/Source/**/*.snupkg
|
||||||
|
|
||||||
- name: Setup VSTest
|
test:
|
||||||
uses: darenm/Setup-VSTest@v1
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
- name: Core Tests
|
steps:
|
||||||
run: vstest.console.exe Source\MQTTnet.Tests\bin\Release\net6.0\MQTTnet.Tests.dll
|
- name: Setup .NET SDK
|
||||||
|
uses: actions/setup-dotnet@v4
|
||||||
|
with:
|
||||||
|
dotnet-version: |
|
||||||
|
8.0.x
|
||||||
|
10.0.x
|
||||||
|
|
||||||
- name: ASP.NET Tests
|
- name: Checkout code
|
||||||
run: vstest.console.exe Source\MQTTnet.AspNetCore.Tests\bin\Release\netcoreapp3.1\MQTTnet.AspNetCore.Tests.dll
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Publish MyGet nugets
|
- name: Execute tests
|
||||||
|
run: dotnet test --framework net10.0 --configuration Release --project 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' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)
|
||||||
|
steps:
|
||||||
|
- name: Download nuget packages
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: nugets
|
||||||
|
path: nugets
|
||||||
|
|
||||||
|
- name: Install sign CLI tool
|
||||||
|
run: dotnet tool install --tool-path . sign --version 0.9.1-beta.25379.1
|
||||||
|
|
||||||
|
- name: Sign nugets
|
||||||
|
shell: pwsh
|
||||||
|
run: >
|
||||||
|
./sign code azure-key-vault `
|
||||||
|
"**/*.nupkg" `
|
||||||
|
--base-directory "${{ github.workspace }}\nugets" `
|
||||||
|
--publisher-name "MQTTnet" `
|
||||||
|
--description "MQTTnet" `
|
||||||
|
--description-url "https://github.com/dotnet/MQTTnet" `
|
||||||
|
--azure-key-vault-tenant-id "${{ secrets.AZURE_TENANT_ID }}" `
|
||||||
|
--azure-key-vault-client-id "${{ secrets.AZURE_CLIENT_ID }}" `
|
||||||
|
--azure-key-vault-client-secret "${{ secrets.AZURE_CLIENT_SECRET }}" `
|
||||||
|
--azure-key-vault-certificate "${{ secrets.KEY_VAULT_CERTIFICATE_ID }}" `
|
||||||
|
--azure-key-vault-url "${{ secrets.KEY_VAULT_URL }}"
|
||||||
|
|
||||||
|
- name: Upload signed nuget packages
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: signed-nugets
|
||||||
|
path: |
|
||||||
|
${{ github.workspace }}\nugets\**\*.nupkg
|
||||||
|
${{ github.workspace }}\nugets\**\*.snupkg
|
||||||
|
|
||||||
|
publish-myget:
|
||||||
if: ${{ github.event_name == 'push' }}
|
if: ${{ github.event_name == 'push' }}
|
||||||
run: dotnet nuget push **/*.nupkg -k ${{ secrets.MYGET_API_KEY }} -s https://www.myget.org/F/mqttnet/api/v3/index.json --skip-duplicate
|
needs: sign
|
||||||
|
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 myget.com nugets
|
||||||
|
run: dotnet nuget push ${{ github.workspace }}/nugets/**/*.nupkg -k ${{ secrets.MYGET_API_KEY }} -s https://www.myget.org/F/mqttnet/api/v3/index.json --skip-duplicate
|
||||||
|
|
||||||
|
publish-nuget:
|
||||||
|
# Tag schema is 'v5.1.0-rc' etc.
|
||||||
|
if: startsWith(github.ref, 'refs/tags/v')
|
||||||
|
needs: [sign, test]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Download signed nuget packages
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: signed-nugets
|
||||||
|
path: nugets
|
||||||
|
|
||||||
|
- name: Publish nuget.org nugets
|
||||||
|
run: dotnet nuget push ${{ github.workspace }}/nugets/**/*.nupkg -k ${{ secrets.NUGET_API_KEY }} -s https://api.nuget.org/v3/index.json --skip-duplicate
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
<Project>
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFrameworks>net8.0;net10.0</TargetFrameworks>
|
||||||
|
<Product>MQTTnet</Product>
|
||||||
|
<Authors>The contributors of MQTTnet</Authors>
|
||||||
|
<Company>The contributors of MQTTnet</Company>
|
||||||
|
<Copyright>Copyright (c) .NET Foundation and Contributors</Copyright>
|
||||||
|
|
||||||
|
<DelaySign>false</DelaySign>
|
||||||
|
<SignAssembly>false</SignAssembly>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>disable</Nullable>
|
||||||
|
|
||||||
|
<NeutralLanguage>en-US</NeutralLanguage>
|
||||||
|
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||||
|
<NoWarn>1591;NETSDK1138;NU1803;NU1901;NU1902</NoWarn>
|
||||||
|
|
||||||
|
<NuGetAuditMode>all</NuGetAuditMode>
|
||||||
|
<NuGetAudit>true</NuGetAudit>
|
||||||
|
<NuGetAuditLevel>low</NuGetAuditLevel>
|
||||||
|
|
||||||
|
<EnableNETAnalyzers>true</EnableNETAnalyzers>
|
||||||
|
<AnalysisLevel>latest</AnalysisLevel>
|
||||||
|
<AnalysisMode>recommended</AnalysisMode>
|
||||||
|
<AnalysisModeStyle>default</AnalysisModeStyle>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(GITHUB_ACTIONS)'=='true'">
|
||||||
|
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<Project>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(GeneratePackageOnBuild)'=='true'">
|
||||||
|
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
||||||
|
<PackageIcon>nuget.png</PackageIcon>
|
||||||
|
<PackageProjectUrl>https://github.com/dotnet/MQTTnet</PackageProjectUrl>
|
||||||
|
<RepositoryUrl>https://github.com/dotnet/MQTTnet.git</RepositoryUrl>
|
||||||
|
<RepositoryType>git</RepositoryType>
|
||||||
|
<IncludeSymbols>true</IncludeSymbols>
|
||||||
|
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||||
|
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||||
|
<EmbedUntrackedSources>true</EmbedUntrackedSources>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup Condition="'$(GeneratePackageOnBuild)'=='true'">
|
||||||
|
<None Include="README.md" Pack="true" PackagePath="" Condition="Exists('README.md')" />
|
||||||
|
<None Include="..\..\Images\nuget.png" Pack="true" PackagePath="" />
|
||||||
|
<None Include="..\..\LICENSE" Pack="true" PackagePath="" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup Condition="'$(IncludeSymbols)'=='true'">
|
||||||
|
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All"/>
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
-94
@@ -1,94 +0,0 @@
|
|||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
|
||||||
# Visual Studio Version 17
|
|
||||||
VisualStudioVersion = 17.0.31919.166
|
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet", "Source\MQTTnet\MQTTnet.csproj", "{3587E506-55A2-4EB3-99C7-DC01E42D25D2}"
|
|
||||||
EndProject
|
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{B3F60ECB-45BA-4C66-8903-8BB89CA67998}"
|
|
||||||
ProjectSection(SolutionItems) = preProject
|
|
||||||
LICENSE = LICENSE
|
|
||||||
README.md = README.md
|
|
||||||
CODE-OF-CONDUCT.md = CODE-OF-CONDUCT.md
|
|
||||||
EndProjectSection
|
|
||||||
EndProject
|
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.AspNetCore", "Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj", "{F10C4060-F7EE-4A83-919F-FF723E72F94A}"
|
|
||||||
EndProject
|
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Extensions.Rpc", "Source\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj", "{C444E9C8-95FA-430E-9126-274129DE16CD}"
|
|
||||||
EndProject
|
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Extensions.ManagedClient", "Source\MQTTnet.Extensions.ManagedClient\MQTTnet.Extensions.ManagedClient.csproj", "{C400533A-8EBA-4F0B-BF4D-295C3708604B}"
|
|
||||||
EndProject
|
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Extensions.WebSocket4Net", "Source\MQTTnet.Extensions.WebSocket4Net\MQTTnet.Extensions.WebSocket4Net.csproj", "{2BD01D53-4CA5-4142-BE8D-313876395E3E}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MQTTnet.Samples", "Samples\MQTTnet.Samples.csproj", "{71CF35F5-3327-4A91-AAF4-5340F6701771}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MQTTnet.Tests", "Source\MQTTnet.Tests\MQTTnet.Tests.csproj", "{B270F32A-9F3E-42EE-A989-813E35E29ADB}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MQTTnet.AspNetCore.Tests", "Source\MQTTnet.AspNetCore.Tests\MQTTnet.AspNetCore.Tests.csproj", "{A238BBBF-C75F-482D-9CC3-BB34ABA9B675}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MQTTnet.Benchmarks", "Source\MQTTnet.Benchmarks\MQTTnet.Benchmarks.csproj", "{2F516E76-AAC4-4219-B7D1-34CDD3CFF381}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MQTTnet.TestApp", "Source\MQTTnet.TestApp\MQTTnet.TestApp.csproj", "{175D5340-CC5B-4542-939D-4E7D15A0BC8D}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MQTTnet.AspTestApp", "Source\MQTTnet.AspTestApp\MQTTnet.AspTestApp.csproj", "{72867E4C-4E15-4E8E-8FAB-AE9253286BBC}"
|
|
||||||
EndProject
|
|
||||||
Global
|
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
|
||||||
Debug|Any CPU = Debug|Any CPU
|
|
||||||
Release|Any CPU = Release|Any CPU
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
|
||||||
{3587E506-55A2-4EB3-99C7-DC01E42D25D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{3587E506-55A2-4EB3-99C7-DC01E42D25D2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{3587E506-55A2-4EB3-99C7-DC01E42D25D2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{3587E506-55A2-4EB3-99C7-DC01E42D25D2}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{F10C4060-F7EE-4A83-919F-FF723E72F94A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{F10C4060-F7EE-4A83-919F-FF723E72F94A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{F10C4060-F7EE-4A83-919F-FF723E72F94A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{F10C4060-F7EE-4A83-919F-FF723E72F94A}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{C444E9C8-95FA-430E-9126-274129DE16CD}.Debug|Any CPU.ActiveCfg = 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.Build.0 = Release|Any CPU
|
|
||||||
{C400533A-8EBA-4F0B-BF4D-295C3708604B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{C400533A-8EBA-4F0B-BF4D-295C3708604B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{C400533A-8EBA-4F0B-BF4D-295C3708604B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{C400533A-8EBA-4F0B-BF4D-295C3708604B}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{2BD01D53-4CA5-4142-BE8D-313876395E3E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{2BD01D53-4CA5-4142-BE8D-313876395E3E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{2BD01D53-4CA5-4142-BE8D-313876395E3E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{2BD01D53-4CA5-4142-BE8D-313876395E3E}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{71CF35F5-3327-4A91-AAF4-5340F6701771}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{71CF35F5-3327-4A91-AAF4-5340F6701771}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{71CF35F5-3327-4A91-AAF4-5340F6701771}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{71CF35F5-3327-4A91-AAF4-5340F6701771}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{B270F32A-9F3E-42EE-A989-813E35E29ADB}.Debug|Any CPU.ActiveCfg = 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.Build.0 = Release|Any CPU
|
|
||||||
{A238BBBF-C75F-482D-9CC3-BB34ABA9B675}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{A238BBBF-C75F-482D-9CC3-BB34ABA9B675}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{A238BBBF-C75F-482D-9CC3-BB34ABA9B675}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{A238BBBF-C75F-482D-9CC3-BB34ABA9B675}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{2F516E76-AAC4-4219-B7D1-34CDD3CFF381}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{2F516E76-AAC4-4219-B7D1-34CDD3CFF381}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{2F516E76-AAC4-4219-B7D1-34CDD3CFF381}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{2F516E76-AAC4-4219-B7D1-34CDD3CFF381}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{175D5340-CC5B-4542-939D-4E7D15A0BC8D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{175D5340-CC5B-4542-939D-4E7D15A0BC8D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{175D5340-CC5B-4542-939D-4E7D15A0BC8D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{175D5340-CC5B-4542-939D-4E7D15A0BC8D}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{72867E4C-4E15-4E8E-8FAB-AE9253286BBC}.Debug|Any CPU.ActiveCfg = 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.Build.0 = Release|Any CPU
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
|
||||||
HideSolutionNode = FALSE
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(NestedProjects) = preSolution
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
|
||||||
SolutionGuid = {07536672-5CBC-4BE3-ACE0-708A431A7894}
|
|
||||||
EndGlobalSection
|
|
||||||
EndGlobal
|
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<Solution>
|
||||||
|
<Configurations>
|
||||||
|
<Platform Name="Any CPU" />
|
||||||
|
<Platform Name="x64" />
|
||||||
|
<Platform Name="x86" />
|
||||||
|
</Configurations>
|
||||||
|
<Folder Name="/Solution Items/">
|
||||||
|
<File Path=".github/workflows/ci.yml" />
|
||||||
|
<File Path="CODE-OF-CONDUCT.md" />
|
||||||
|
<File Path="Directory.Build.props" />
|
||||||
|
<File Path="Directory.Build.targets" />
|
||||||
|
<File Path="LICENSE" />
|
||||||
|
<File Path="README.md" />
|
||||||
|
<File Path="Source/ReleaseNotes.md" />
|
||||||
|
</Folder>
|
||||||
|
<Project Path="Samples/MQTTnet.Samples.csproj" />
|
||||||
|
<Project Path="Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj" />
|
||||||
|
<Project Path="Source/MQTTnet.AspTestApp/MQTTnet.AspTestApp.csproj" />
|
||||||
|
<Project Path="Source/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj" />
|
||||||
|
<Project Path="Source/MQTTnet.Extensions.Rpc/MQTTnet.Extensions.Rpc.csproj" />
|
||||||
|
<Project Path="Source/MQTTnet.Extensions.Socks5/MQTTnet.Extensions.Socks5.csproj" />
|
||||||
|
<Project Path="Source/MQTTnet.Extensions.TopicTemplate/MQTTnet.Extensions.TopicTemplate.csproj" />
|
||||||
|
<Project Path="Source/MQTTnet.Server/MQTTnet.Server.csproj" />
|
||||||
|
<Project Path="Source/MQTTnet.TestApp/MQTTnet.TestApp.csproj" />
|
||||||
|
<Project Path="Source/MQTTnet.Tests.Extensions.Socks5/MQTTnet.Tests.Extensions.Socks5.csproj" />
|
||||||
|
<Project Path="Source/MQTTnet.Tests/MQTTnet.Tests.csproj" />
|
||||||
|
<Project Path="Source/MQTTnet/MQTTnet.csproj" />
|
||||||
|
</Solution>
|
||||||
@@ -1,4 +1,8 @@
|
|||||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||||
|
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertIfStatementToSwitchStatement/@EntryIndexedValue">DO_NOT_SHOW</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=InconsistentNaming/@EntryIndexedValue">SUGGESTION</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UncatchableException/@EntryIndexedValue">DO_NOT_SHOW</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UnthrowableException/@EntryIndexedValue">DO_NOT_SHOW</s:String>
|
||||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_FOR/@EntryValue">Required</s:String>
|
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_FOR/@EntryValue">Required</s:String>
|
||||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_FOREACH/@EntryValue">Required</s:String>
|
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_FOREACH/@EntryValue">Required</s:String>
|
||||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_IFELSE/@EntryValue">Required</s:String>
|
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_IFELSE/@EntryValue">Required</s:String>
|
||||||
@@ -230,18 +234,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/=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/=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>
|
||||||
@@ -3,16 +3,18 @@
|
|||||||
<br/>
|
<br/>
|
||||||
</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)
|
||||||
[](https://raw.githubusercontent.com/dotnet/MQTTnet/master/LICENSE)
|
[](https://raw.githubusercontent.com/dotnet/MQTTnet/master/LICENSE)
|
||||||
|
|
||||||
# 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. It is compatible with mostly any supported .NET Framework version and CPU architecture.
|
MQTTnet is a high performance .NET library for MQTT based communication.
|
||||||
|
It provides a MQTT client and a MQTT server (broker) and supports the MQTT protocol up to version 5.
|
||||||
|
It is compatible with mostly any supported .NET Framework version and CPU architecture.
|
||||||
|
|
||||||
## Features
|
## 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,9 +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
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
MQTTnet is delivered via <b>NuGet</b> package manager. You can find the packages
|
||||||
|
here: https://www.nuget.org/packages/MQTTnet/
|
||||||
|
|
||||||
|
Use these command in the Package Manager console to install MQTTnet manually:
|
||||||
|
|
||||||
|
```
|
||||||
|
Install-Package MQTTnet
|
||||||
|
```
|
||||||
|
|
||||||
|
Samples for using MQTTnet are part of this repository. For starters these samples are recommended:
|
||||||
|
|
||||||
|
- [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)
|
||||||
|
- [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,10 +75,9 @@ 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();
|
||||||
|
|
||||||
@@ -47,37 +96,74 @@ public static class Client_Connection_Samples
|
|||||||
|
|
||||||
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 mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1").Build();
|
*/
|
||||||
|
|
||||||
try
|
var mqttServerFactory = new MqttServerFactory();
|
||||||
|
|
||||||
|
var serverOptions = mqttServerFactory.CreateServerOptionsBuilder().WithDefaultEndpoint().Build();
|
||||||
|
var server = mqttServerFactory.CreateMqttServer(serverOptions);
|
||||||
|
|
||||||
|
server.ValidatingConnectionAsync += async args =>
|
||||||
{
|
{
|
||||||
using (var timeoutToken = new CancellationTokenSource(TimeSpan.FromSeconds(1)))
|
if (args.AuthenticationMethod == "GS2-KRB5")
|
||||||
{
|
{
|
||||||
await mqttClient.ConnectAsync(mqttClientOptions, timeoutToken.Token);
|
var result = await args.ExchangeEnhancedAuthenticationAsync(new ExchangeEnhancedAuthenticationOptions(), args.CancellationToken);
|
||||||
|
|
||||||
|
Console.WriteLine($"Received AUTH data from client: {Encoding.UTF8.GetString(result.AuthenticationData)}");
|
||||||
|
|
||||||
|
var authOptions = mqttServerFactory.CreateExchangeExtendedAuthenticationOptionsBuilder().WithAuthenticationData("reply context token").Build();
|
||||||
|
|
||||||
|
result = await args.ExchangeEnhancedAuthenticationAsync(authOptions, args.CancellationToken);
|
||||||
|
|
||||||
|
Console.WriteLine($"Received AUTH data from client: {Encoding.UTF8.GetString(result.AuthenticationData)}");
|
||||||
|
|
||||||
|
args.ResponseAuthenticationData = "outcome of authentication"u8.ToArray();
|
||||||
|
|
||||||
|
// Authentication DONE!
|
||||||
|
args.ReasonCode = MqttConnectReasonCode.Success; // Also the default!
|
||||||
}
|
}
|
||||||
}
|
else
|
||||||
catch (OperationCanceledException)
|
|
||||||
{
|
{
|
||||||
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,10 +171,9 @@ 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.
|
||||||
@@ -98,9 +183,8 @@ public static class Client_Connection_Samples
|
|||||||
|
|
||||||
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")
|
||||||
.WithTls(
|
.WithTlsOptions(
|
||||||
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.CertificateValidationHandler = _ => true;
|
o.WithCertificateValidationHandler(_ => 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_WebSocket4Net()
|
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.
|
|
||||||
* 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_Using_WebSockets()
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* This sample creates a simple MQTT client and connects to a public broker using a WebSocket connection.
|
|
||||||
*
|
|
||||||
* This is a modified version of the sample _Connect_Client_! See other sample for more details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var mqttFactory = new MqttFactory();
|
|
||||||
|
|
||||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
|
||||||
{
|
|
||||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithWebSocketServer("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,83 @@ 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)
|
||||||
.WithTls(
|
.WithTlsOptions(
|
||||||
o =>
|
o => o.WithCertificateValidationHandler(
|
||||||
{
|
|
||||||
// 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.CertificateValidationHandler = _ => true;
|
_ => 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);
|
||||||
{
|
|
||||||
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()
|
public static async Task Disconnect_Clean()
|
||||||
@@ -222,29 +309,27 @@ public static class Client_Connection_Samples
|
|||||||
* trigger sending the last will etc.
|
* trigger sending the last will etc.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
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);
|
||||||
|
|
||||||
// Calling _DisconnectAsync_ will send a DISCONNECT packet before closing the connection.
|
// Calling _DisconnectAsync_ will send a DISCONNECT packet before closing the connection.
|
||||||
// Using a reason code requires MQTT version 5.0.0!
|
// Using a reason code requires MQTT version 5.0.0!
|
||||||
await mqttClient.DisconnectAsync(MqttClientDisconnectReason.ImplementationSpecificError);
|
await mqttClient.DisconnectAsync(MqttClientDisconnectOptionsReason.ImplementationSpecificError);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task Disconnect_Non_Clean()
|
public static async Task Disconnect_Non_Clean()
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* This sample disconnects from the server without sending a DISCONNECT packet.
|
* This sample disconnects from the server without sending a DISCONNECT packet.
|
||||||
* This way of disconnecting is treated as a non clean disconnect which will
|
* This way of disconnecting is treated as a non-clean disconnect which will
|
||||||
* trigger sending the last will etc.
|
* trigger sending the last will etc.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var mqttFactory = new MqttFactory();
|
var mqttFactory = new MqttClientFactory();
|
||||||
|
|
||||||
var mqttClient = mqttFactory.CreateMqttClient();
|
var mqttClient = mqttFactory.CreateMqttClient();
|
||||||
|
|
||||||
@@ -264,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)
|
||||||
.WithTls(
|
.WithTlsOptions(
|
||||||
o =>
|
o =>
|
||||||
{
|
{
|
||||||
o.CertificateValidationHandler = eventArgs =>
|
o.WithCertificateValidationHandler(
|
||||||
|
eventArgs =>
|
||||||
{
|
{
|
||||||
eventArgs.Certificate.Subject.DumpToConsole();
|
eventArgs.Certificate.Subject.DumpToConsole();
|
||||||
eventArgs.Certificate.GetExpirationDateString().DumpToConsole();
|
eventArgs.Certificate.GetExpirationDateString().DumpToConsole();
|
||||||
@@ -280,30 +365,26 @@ 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()
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* 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);
|
||||||
@@ -313,20 +394,18 @@ public static class Client_Connection_Samples
|
|||||||
|
|
||||||
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()
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* 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();
|
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||||
|
|
||||||
mqttClient.DisconnectedAsync += async e =>
|
mqttClient.DisconnectedAsync += async e =>
|
||||||
@@ -334,13 +413,12 @@ public static class Client_Connection_Samples
|
|||||||
if (e.ClientWasConnected)
|
if (e.ClientWasConnected)
|
||||||
{
|
{
|
||||||
// Use the current options as the new options.
|
// Use the current options as the new options.
|
||||||
await mqttClient.ConnectAsync(mqttClient.Options);
|
await mqttClient.ConnectAsync(mqttClientOptions);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public static void Reconnect_Using_Timer()
|
public static void Reconnect_Using_Timer()
|
||||||
{
|
{
|
||||||
@@ -350,10 +428,9 @@ 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(
|
||||||
@@ -384,6 +461,62 @@ public static class Client_Connection_Samples
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Console.WriteLine("Press <Enter> to exit");
|
||||||
|
Console.ReadLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task Timeout()
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* This sample creates a simple MQTT client and connects to an invalid broker using a timeout.
|
||||||
|
*
|
||||||
|
* This is a modified version of the sample _Connect_Client_! See other sample for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var mqttFactory = new MqttClientFactory();
|
||||||
|
|
||||||
|
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||||
|
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1").Build();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var timeoutToken = new CancellationTokenSource(TimeSpan.FromSeconds(1));
|
||||||
|
await mqttClient.ConnectAsync(mqttClientOptions, timeoutToken.Token);
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Timeout while connecting.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class SampleClientKerberosAuthenticationHandler : IMqttEnhancedAuthenticationHandler
|
||||||
|
{
|
||||||
|
public async Task HandleEnhancedAuthenticationAsync(MqttEnhancedAuthenticationEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
if (eventArgs.AuthenticationMethod != "GS2-KRB5")
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Wrong authentication method");
|
||||||
|
}
|
||||||
|
|
||||||
|
var sendOptions = new SendMqttEnhancedAuthenticationDataOptions
|
||||||
|
{
|
||||||
|
Data = "initial context token"u8.ToArray()
|
||||||
|
};
|
||||||
|
|
||||||
|
await eventArgs.SendAsync(sendOptions);
|
||||||
|
|
||||||
|
var response = await eventArgs.ReceiveAsync(CancellationToken.None);
|
||||||
|
|
||||||
|
Console.WriteLine($"Received AUTH data from server: {Encoding.UTF8.GetString(response.AuthenticationData)}");
|
||||||
|
|
||||||
|
// No further data is required, but we have to fulfil the exchange.
|
||||||
|
sendOptions = new SendMqttEnhancedAuthenticationDataOptions
|
||||||
|
{
|
||||||
|
Data = []
|
||||||
|
};
|
||||||
|
|
||||||
|
await eventArgs.SendAsync(sendOptions, CancellationToken.None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,8 @@
|
|||||||
// ReSharper disable UnusedMember.Global
|
// ReSharper disable UnusedMember.Global
|
||||||
// ReSharper disable InconsistentNaming
|
// ReSharper disable InconsistentNaming
|
||||||
|
|
||||||
using MQTTnet.Client;
|
using System;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
namespace MQTTnet.Samples.Client;
|
namespace MQTTnet.Samples.Client;
|
||||||
|
|
||||||
@@ -23,10 +24,9 @@ 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();
|
||||||
{
|
|
||||||
var mqttClientOptions = new MqttClientOptionsBuilder()
|
var mqttClientOptions = new MqttClientOptionsBuilder()
|
||||||
.WithTcpServer("broker.hivemq.com")
|
.WithTcpServer("broker.hivemq.com")
|
||||||
.Build();
|
.Build();
|
||||||
@@ -44,7 +44,6 @@ public static class Client_Publish_Samples
|
|||||||
|
|
||||||
Console.WriteLine("MQTT application message is published.");
|
Console.WriteLine("MQTT application message is published.");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task Publish_Multiple_Application_Messages()
|
public static async Task Publish_Multiple_Application_Messages()
|
||||||
{
|
{
|
||||||
@@ -54,10 +53,9 @@ public static class Client_Publish_Samples
|
|||||||
* See sample _Publish_Application_Message_ for more details.
|
* See sample _Publish_Application_Message_ for more details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var mqttFactory = new 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")
|
.WithTcpServer("broker.hivemq.com")
|
||||||
.Build();
|
.Build();
|
||||||
@@ -89,5 +87,21 @@ public static class Client_Publish_Samples
|
|||||||
|
|
||||||
Console.WriteLine("MQTT application message is published.");
|
Console.WriteLine("MQTT application message is published.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static MqttApplicationMessage Create_Message_With_Binary_User_Property()
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* MQTT v5 user properties are encoded as UTF-8 strings. When the UTF-8 payload is already available
|
||||||
|
* as a byte buffer, the builder APIs can avoid creating intermediate strings.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var encodedValue = Encoding.UTF8.GetBytes("sensor-01");
|
||||||
|
|
||||||
|
return new MqttApplicationMessageBuilder()
|
||||||
|
.WithTopic("samples/metadata/binary")
|
||||||
|
.WithUserProperty("client-id", encodedValue.AsMemory())
|
||||||
|
.WithUserProperty("checksum", new ArraySegment<byte>(encodedValue))
|
||||||
|
.WithPayload("metadata")
|
||||||
|
.Build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
// 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.Extensions.Socks5;
|
||||||
|
using MQTTnet.Samples.Helpers;
|
||||||
|
|
||||||
|
namespace MQTTnet.Samples.Client;
|
||||||
|
|
||||||
|
public static class Client_Socks5_Samples
|
||||||
|
{
|
||||||
|
public static async Task Connect_Tcp_Via_Socks5_No_Auth()
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* This sample connects to an MQTT broker through a SOCKS5 proxy that does not
|
||||||
|
* require authentication. The SOCKS5 stream provider performs the proxy handshake
|
||||||
|
* (RFC 1928) and returns a connected stream that the MQTT TCP channel can use as if
|
||||||
|
* it were a direct TCP connection.
|
||||||
|
*
|
||||||
|
* Host name resolution is performed by the proxy by default (ATYP=DOMAINNAME), which
|
||||||
|
* is the desired behavior when the MQTT broker hostname is only reachable from inside
|
||||||
|
* the network the proxy lives in.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var mqttFactory = new MqttClientFactory();
|
||||||
|
|
||||||
|
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||||
|
|
||||||
|
var mqttClientOptions = new MqttClientOptionsBuilder()
|
||||||
|
.WithTcpServer("broker.internal.example", 1883)
|
||||||
|
.WithSocks5Proxy(socks => socks
|
||||||
|
.WithHost("proxy.example.com")
|
||||||
|
.WithPort(1080))
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(15));
|
||||||
|
var response = await mqttClient.ConnectAsync(mqttClientOptions, timeout.Token);
|
||||||
|
|
||||||
|
Console.WriteLine("The MQTT client is connected through SOCKS5.");
|
||||||
|
response.DumpToConsole();
|
||||||
|
|
||||||
|
var disconnectOptions = mqttFactory.CreateClientDisconnectOptionsBuilder().Build();
|
||||||
|
await mqttClient.DisconnectAsync(disconnectOptions, CancellationToken.None);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task Connect_Tcp_With_Tls_Via_Socks5_With_Credentials()
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* This sample connects to a TLS-protected MQTT broker through a SOCKS5 proxy that
|
||||||
|
* requires username/password authentication (RFC 1929).
|
||||||
|
*
|
||||||
|
* Note that the TLS handshake is layered on top of the SOCKS5 tunnel: the SNI used
|
||||||
|
* by SslStream is derived from the broker hostname (or the value passed to
|
||||||
|
* WithTargetHost), not from the proxy hostname.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var mqttFactory = new MqttClientFactory();
|
||||||
|
|
||||||
|
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||||
|
|
||||||
|
var mqttClientOptions = new MqttClientOptionsBuilder()
|
||||||
|
.WithTcpServer("broker.internal.example", 8883)
|
||||||
|
.WithTlsOptions(tls => tls
|
||||||
|
.WithTargetHost("broker.internal.example"))
|
||||||
|
.WithSocks5Proxy(socks => socks
|
||||||
|
.WithHost("proxy.example.com")
|
||||||
|
.WithPort(1080)
|
||||||
|
.WithCredentials("alice", "s3cr3t")
|
||||||
|
.WithResolveDnsRemotely(true)
|
||||||
|
.WithHandshakeTimeout(TimeSpan.FromSeconds(10)))
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(15));
|
||||||
|
var response = await mqttClient.ConnectAsync(mqttClientOptions, timeout.Token);
|
||||||
|
|
||||||
|
Console.WriteLine("The MQTT client is connected through SOCKS5 with TLS.");
|
||||||
|
response.DumpToConsole();
|
||||||
|
|
||||||
|
var disconnectOptions = mqttFactory.CreateClientDisconnectOptionsBuilder().Build();
|
||||||
|
await mqttClient.DisconnectAsync(disconnectOptions, CancellationToken.None);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,8 +5,9 @@
|
|||||||
// 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 System.Text;
|
||||||
using MQTTnet.Packets;
|
using MQTTnet.Packets;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
using MQTTnet.Samples.Helpers;
|
using MQTTnet.Samples.Helpers;
|
||||||
@@ -21,10 +22,9 @@ public static class Client_Subscribe_Samples
|
|||||||
* 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().WithTcpServer("broker.hivemq.com").Build();
|
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||||
|
|
||||||
// Setup message handling before connecting so that queued messages
|
// Setup message handling before connecting so that queued messages
|
||||||
@@ -40,13 +40,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().WithTopicFilter("mqttnet/samples/topic/2").Build();
|
||||||
.WithTopicFilter(
|
|
||||||
f =>
|
|
||||||
{
|
|
||||||
f.WithTopic("mqttnet/samples/topic/2");
|
|
||||||
})
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None);
|
await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None);
|
||||||
|
|
||||||
@@ -55,18 +49,16 @@ public static class Client_Subscribe_Samples
|
|||||||
Console.WriteLine("Press enter to exit.");
|
Console.WriteLine("Press enter to exit.");
|
||||||
Console.ReadLine();
|
Console.ReadLine();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task Send_Responses()
|
public static async Task Send_Responses()
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* This sample subscribes to a topic and sends a response to the broker. This requires at least QoS level 1 to work!
|
* This sample subscribes to a topic and sends a detailed response to the client. This requires at least QoS level 1 to work!
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var mqttFactory = new MqttFactory();
|
var mqttFactory = new MqttClientFactory();
|
||||||
|
|
||||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||||
{
|
|
||||||
mqttClient.ApplicationMessageReceivedAsync += delegate(MqttApplicationMessageReceivedEventArgs args)
|
mqttClient.ApplicationMessageReceivedAsync += delegate(MqttApplicationMessageReceivedEventArgs args)
|
||||||
{
|
{
|
||||||
// Do some work with the message...
|
// Do some work with the message...
|
||||||
@@ -76,7 +68,7 @@ public static class Client_Subscribe_Samples
|
|||||||
args.ResponseReasonString = "That did not work!";
|
args.ResponseReasonString = "That did not work!";
|
||||||
|
|
||||||
// User properties require MQTT v5!
|
// User properties require MQTT v5!
|
||||||
args.ResponseUserProperties.Add(new MqttUserProperty("My", "Data"));
|
args.ResponseUserProperties.Add(new MqttUserProperty("My", Encoding.UTF8.GetBytes("Data")));
|
||||||
|
|
||||||
// Now the broker will resend the message again.
|
// Now the broker will resend the message again.
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
@@ -86,13 +78,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().WithTopicFilter("topic/test").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);
|
||||||
|
|
||||||
@@ -101,7 +87,6 @@ public static class Client_Subscribe_Samples
|
|||||||
// The response contains additional data sent by the server after subscribing.
|
// The response contains additional data sent by the server after subscribing.
|
||||||
response.DumpToConsole();
|
response.DumpToConsole();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task Subscribe_Multiple_Topics()
|
public static async Task Subscribe_Multiple_Topics()
|
||||||
{
|
{
|
||||||
@@ -109,10 +94,9 @@ public static class Client_Subscribe_Samples
|
|||||||
* This sample subscribes to several topics in a single request.
|
* This sample subscribes to several topics in a single request.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var mqttFactory = new 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);
|
||||||
@@ -120,21 +104,10 @@ public static class Client_Subscribe_Samples
|
|||||||
// Create the subscribe options including several topics with different options.
|
// Create the subscribe options including several topics with different options.
|
||||||
// It is also possible to all of these topics using a dedicated call of _SubscribeAsync_ per topic.
|
// It is also possible to all of these topics using a dedicated call of _SubscribeAsync_ per topic.
|
||||||
var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder()
|
var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder()
|
||||||
.WithTopicFilter(
|
.WithTopicFilter(t => t.WithTopic("topic/1").WithAtLeastOnceQoS())
|
||||||
f =>
|
.WithTopicFilter(t => t.WithTopic("topic/2").WithAtMostOnceQoS())
|
||||||
{
|
.WithTopicFilter(t => t.WithTopic("topic/3").WithNoLocal())
|
||||||
f.WithTopic("mqttnet/samples/topic/1");
|
.WithTopicFilter(t => t.WithRetainHandling(MqttRetainHandling.SendAtSubscribe))
|
||||||
})
|
|
||||||
.WithTopicFilter(
|
|
||||||
f =>
|
|
||||||
{
|
|
||||||
f.WithTopic("mqttnet/samples/topic/2").WithNoLocal();
|
|
||||||
})
|
|
||||||
.WithTopicFilter(
|
|
||||||
f =>
|
|
||||||
{
|
|
||||||
f.WithTopic("mqttnet/samples/topic/3").WithRetainHandling(MqttRetainHandling.SendAtSubscribe);
|
|
||||||
})
|
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
var response = await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None);
|
var response = await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None);
|
||||||
@@ -144,7 +117,6 @@ public static class Client_Subscribe_Samples
|
|||||||
// The response contains additional data sent by the server after subscribing.
|
// The response contains additional data sent by the server after subscribing.
|
||||||
response.DumpToConsole();
|
response.DumpToConsole();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task Subscribe_Topic()
|
public static async Task Subscribe_Topic()
|
||||||
{
|
{
|
||||||
@@ -152,21 +124,16 @@ public static class Client_Subscribe_Samples
|
|||||||
* This sample subscribes to a topic.
|
* This sample subscribes to a topic.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
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);
|
||||||
|
|
||||||
var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder()
|
var topicFilter = mqttFactory.CreateTopicFilterBuilder().WithTopic("mqttnet/samples/topic/2").WithAtLeastOnceQoS();
|
||||||
.WithTopicFilter(
|
|
||||||
f =>
|
var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder().WithTopicFilter(topicFilter).Build();
|
||||||
{
|
|
||||||
f.WithTopic("mqttnet/samples/topic/1");
|
|
||||||
})
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
var response = await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None);
|
var response = await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None);
|
||||||
|
|
||||||
@@ -175,5 +142,62 @@ public static class Client_Subscribe_Samples
|
|||||||
// The response contains additional data sent by the server after subscribing.
|
// The response contains additional data sent by the server after subscribing.
|
||||||
response.DumpToConsole();
|
response.DumpToConsole();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void ConcurrentProcessingDisableAutoAcknowledge(IMqttClient mqttClient, CancellationToken shutdownToken)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* This sample shows how to achieve concurrent processing and not have message AutoAcknowledged
|
||||||
|
* This to have a proper QoS1 (at-least-once) experience for what at least MQTT specification can provide
|
||||||
|
*/
|
||||||
|
mqttClient.ApplicationMessageReceivedAsync += 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(IMqttClient mqttClient, CancellationToken shutdownToken)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using MQTTnet.Extensions.TopicTemplate;
|
||||||
|
using MQTTnet.Protocol;
|
||||||
|
using MQTTnet.Samples.Helpers;
|
||||||
|
|
||||||
|
// ReSharper disable UnusedType.Global
|
||||||
|
// ReSharper disable UnusedMember.Global
|
||||||
|
// ReSharper disable InconsistentNaming
|
||||||
|
// ReSharper disable UnusedMember.Local
|
||||||
|
|
||||||
|
namespace MQTTnet.Samples.Client;
|
||||||
|
|
||||||
|
public static class Client_Subscribe_TopicTemplate_Samples
|
||||||
|
{
|
||||||
|
static readonly MqttTopicTemplate SampleTemplate = new("mqttnet/samples/topic/{id}");
|
||||||
|
|
||||||
|
public static async Task Subscribe_Multiple_Topics_With_Template()
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* This sample subscribes to several topics in a single request.
|
||||||
|
*
|
||||||
|
* This sample requires the nuget 'MQTTnet.Extensions.TopicTemplate'.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var mqttFactory = new MqttClientFactory();
|
||||||
|
|
||||||
|
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||||
|
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||||
|
|
||||||
|
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||||
|
|
||||||
|
// Create the subscribe options including several topics with different options.
|
||||||
|
// It is also possible to all of these topics using a dedicated call of _SubscribeAsync_ per topic.
|
||||||
|
var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder()
|
||||||
|
.WithTopicTemplate(SampleTemplate.WithParameter("id", "1"))
|
||||||
|
.WithTopicTemplate(SampleTemplate.WithParameter("id", "2"), noLocal: true)
|
||||||
|
.WithTopicTemplate(SampleTemplate.WithParameter("id", "3"), retainHandling: MqttRetainHandling.SendAtSubscribe)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var response = await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None);
|
||||||
|
|
||||||
|
Console.WriteLine("MQTT client subscribed to topics.");
|
||||||
|
|
||||||
|
// The response contains additional data sent by the server after subscribing.
|
||||||
|
response.DumpToConsole();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task Subscribe_Topic_With_Template()
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* This sample subscribes to a topic.
|
||||||
|
*
|
||||||
|
* This sample requires the nuget 'MQTTnet.Extensions.TopicTemplate'.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var mqttFactory = new MqttClientFactory();
|
||||||
|
|
||||||
|
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||||
|
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||||
|
|
||||||
|
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||||
|
|
||||||
|
var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder().WithTopicTemplate(SampleTemplate.WithParameter("id", "1")).Build();
|
||||||
|
|
||||||
|
var response = await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None);
|
||||||
|
|
||||||
|
Console.WriteLine("MQTT client subscribed to topic.");
|
||||||
|
|
||||||
|
// The response contains additional data sent by the server after subscribing.
|
||||||
|
response.DumpToConsole();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,8 +6,9 @@
|
|||||||
// ReSharper disable UnusedMember.Global
|
// ReSharper disable UnusedMember.Global
|
||||||
// ReSharper disable InconsistentNaming
|
// ReSharper disable InconsistentNaming
|
||||||
|
|
||||||
|
using System.Globalization;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using MQTTnet.Diagnostics;
|
using MQTTnet.Diagnostics.Logger;
|
||||||
|
|
||||||
namespace MQTTnet.Samples.Diagnostics;
|
namespace MQTTnet.Samples.Diagnostics;
|
||||||
|
|
||||||
@@ -20,14 +21,13 @@ 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")
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||||
{
|
|
||||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||||
|
|
||||||
Console.WriteLine("MQTT client is connected.");
|
Console.WriteLine("MQTT client is connected.");
|
||||||
@@ -37,7 +37,6 @@ public static class Logger_Samples
|
|||||||
|
|
||||||
await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None);
|
await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task Use_Event_Logger()
|
public static async Task Use_Event_Logger()
|
||||||
{
|
{
|
||||||
@@ -50,10 +49,10 @@ public static class Logger_Samples
|
|||||||
// The logger ID is optional but can be set do distinguish different logger instances.
|
// The logger ID is optional but can be set do distinguish different logger instances.
|
||||||
var mqttEventLogger = new MqttNetEventLogger("MyCustomLogger");
|
var mqttEventLogger = new MqttNetEventLogger("MyCustomLogger");
|
||||||
|
|
||||||
mqttEventLogger.LogMessagePublished += (sender, args) =>
|
mqttEventLogger.LogMessagePublished += (_, args) =>
|
||||||
{
|
{
|
||||||
var output = new StringBuilder();
|
var output = new StringBuilder();
|
||||||
output.AppendLine($">> [{args.LogMessage.Timestamp:O}] [{args.LogMessage.ThreadId}] [{args.LogMessage.Source}] [{args.LogMessage.Level}]: {args.LogMessage.Message}");
|
output.AppendLine(CultureInfo.InvariantCulture, $">> [{args.LogMessage.Timestamp:O}] [{args.LogMessage.ThreadId}] [{args.LogMessage.Source}] [{args.LogMessage.Level}]: {args.LogMessage.Message}");
|
||||||
if (args.LogMessage.Exception != null)
|
if (args.LogMessage.Exception != null)
|
||||||
{
|
{
|
||||||
output.AppendLine(args.LogMessage.Exception.ToString());
|
output.AppendLine(args.LogMessage.Exception.ToString());
|
||||||
@@ -62,14 +61,13 @@ 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")
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||||
{
|
|
||||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||||
|
|
||||||
Console.WriteLine("MQTT client is connected.");
|
Console.WriteLine("MQTT client is connected.");
|
||||||
@@ -79,7 +77,6 @@ public static class Logger_Samples
|
|||||||
|
|
||||||
await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None);
|
await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
sealed class MyLogger : IMqttNetLogger
|
sealed class MyLogger : IMqttNetLogger
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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,15 +18,14 @@ 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();
|
||||||
{
|
|
||||||
var mqttClientOptions = mqttFactory.CreateClientOptionsBuilder()
|
var mqttClientOptions = mqttFactory.CreateClientOptionsBuilder()
|
||||||
.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);
|
||||||
|
|
||||||
@@ -37,9 +36,8 @@ public static class PackageInspection_Samples
|
|||||||
|
|
||||||
await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None);
|
await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
static Task OnInspectPackage(InspectMqttPacketEventArgs eventArgs)
|
static Task OnInspectPacket(InspectMqttPacketEventArgs eventArgs)
|
||||||
{
|
{
|
||||||
if (eventArgs.Direction == MqttPacketFlowDirection.Inbound)
|
if (eventArgs.Direction == MqttPacketFlowDirection.Inbound)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,15 +8,17 @@ namespace MQTTnet.Samples.Helpers;
|
|||||||
|
|
||||||
internal static class ObjectExtensions
|
internal static class ObjectExtensions
|
||||||
{
|
{
|
||||||
|
static readonly JsonSerializerOptions SerializerOptions = new()
|
||||||
|
{
|
||||||
|
WriteIndented = true
|
||||||
|
};
|
||||||
|
|
||||||
public static TObject DumpToConsole<TObject>(this TObject @object)
|
public static TObject DumpToConsole<TObject>(this TObject @object)
|
||||||
{
|
{
|
||||||
var output = "NULL";
|
var output = "NULL";
|
||||||
if (@object != null)
|
if (@object != null)
|
||||||
{
|
{
|
||||||
output = JsonSerializer.Serialize(@object, new JsonSerializerOptions
|
output = JsonSerializer.Serialize(@object, SerializerOptions);
|
||||||
{
|
|
||||||
WriteIndented = true
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Console.WriteLine($"[{@object?.GetType().Name}]:\r\n{output}");
|
Console.WriteLine($"[{@object?.GetType().Name}]:\r\n{output}");
|
||||||
|
|||||||
@@ -2,25 +2,20 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>net6.0</TargetFramework>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
<NoWarn>$(NoWarn);CA1707</NoWarn>
|
||||||
<EnableNETAnalyzers>false</EnableNETAnalyzers>
|
|
||||||
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
|
|
||||||
<NoWarn>1591</NoWarn>
|
|
||||||
</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.Socks5\MQTTnet.Extensions.Socks5.csproj" />
|
||||||
<ProjectReference Include="..\Source\MQTTnet.Extensions.WebSocket4Net\MQTTnet.Extensions.WebSocket4Net.csproj" />
|
<ProjectReference Include="..\Source\MQTTnet.Extensions.TopicTemplate\MQTTnet.Extensions.TopicTemplate.csproj" />
|
||||||
<ProjectReference Include="..\Source\MQTTnet\MQTTnet.csproj" />
|
<ProjectReference Include="..\Source\MQTTnet.Server\MQTTnet.Server.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}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+21
-3
@@ -2,9 +2,16 @@
|
|||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System.Globalization;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
||||||
Console.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,9 +23,13 @@ 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", CultureInfo.InvariantCulture);
|
||||||
var selectedSampleClass = sampleClasses[selectedIndex];
|
var selectedSampleClass = sampleClasses[selectedIndex];
|
||||||
var sampleMethods = selectedSampleClass.GetMethods(BindingFlags.Static | BindingFlags.Public).OrderBy(m => m.Name).ToList();
|
var sampleMethods = selectedSampleClass.GetMethods(BindingFlags.Static | BindingFlags.Public).OrderBy(m => m.Name).ToList();
|
||||||
|
|
||||||
@@ -29,12 +40,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", CultureInfo.InvariantCulture);
|
||||||
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,13 +21,12 @@ 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.
|
||||||
|
|
||||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||||
{
|
|
||||||
var mqttClientOptions = new MqttClientOptionsBuilder()
|
var mqttClientOptions = new MqttClientOptionsBuilder()
|
||||||
.WithTcpServer("broker.hivemq.com")
|
.WithTcpServer("broker.hivemq.com")
|
||||||
.Build();
|
.Build();
|
||||||
@@ -44,7 +42,6 @@ public static class RpcClient_Samples
|
|||||||
|
|
||||||
Console.WriteLine("The RPC call was successful.");
|
Console.WriteLine("The RPC call was successful.");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The device must respond to the request using the correct topic. The following C code shows how a
|
* The device must respond to the request using the correct topic. The following C code shows how a
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
// ReSharper disable EmptyConstructor
|
// ReSharper disable EmptyConstructor
|
||||||
// ReSharper disable MemberCanBeMadeStatic.Local
|
// ReSharper disable MemberCanBeMadeStatic.Local
|
||||||
|
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
@@ -17,7 +18,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()
|
||||||
{
|
{
|
||||||
@@ -52,14 +53,14 @@ public static class Server_Asp_Net_Samples
|
|||||||
// Inject other services via constructor.
|
// Inject other services via constructor.
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task OnClientConnected(ClientConnectedEventArgs eventArgs)
|
public static Task OnClientConnected(ClientConnectedEventArgs eventArgs)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"Client '{eventArgs.ClientId}' connected.");
|
Console.WriteLine($"Client '{eventArgs.ClientId}' connected.");
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public Task ValidateConnection(ValidatingConnectionEventArgs eventArgs)
|
public static Task ValidateConnection(ValidatingConnectionEventArgs eventArgs)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"Client '{eventArgs.ClientId}' wants to connect. Accepting!");
|
Console.WriteLine($"Client '{eventArgs.ClientId}' wants to connect. Accepting!");
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
@@ -68,6 +69,7 @@ public static class Server_Asp_Net_Samples
|
|||||||
|
|
||||||
sealed class Startup
|
sealed class Startup
|
||||||
{
|
{
|
||||||
|
[SuppressMessage("Performance", "CA1822:Mark members as static")]
|
||||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment environment, MqttController mqttController)
|
public void Configure(IApplicationBuilder app, IWebHostEnvironment environment, MqttController mqttController)
|
||||||
{
|
{
|
||||||
app.UseRouting();
|
app.UseRouting();
|
||||||
@@ -88,11 +90,12 @@ public static class Server_Asp_Net_Samples
|
|||||||
* Attach event handlers etc. if required.
|
* Attach event handlers etc. if required.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
server.ValidatingConnectionAsync += mqttController.ValidateConnection;
|
server.ValidatingConnectionAsync += MqttController.ValidateConnection;
|
||||||
server.ClientConnectedAsync += mqttController.OnClientConnected;
|
server.ClientConnectedAsync += MqttController.OnClientConnected;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[SuppressMessage("Performance", "CA1822:Mark members as static")]
|
||||||
public void ConfigureServices(IServiceCollection services)
|
public void ConfigureServices(IServiceCollection services)
|
||||||
{
|
{
|
||||||
services.AddHostedMqttServer(
|
services.AddHostedMqttServer(
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using 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: {qos2AcknowledgePacket?.ReasonCode}");
|
||||||
|
return CompletedTask.Instance;
|
||||||
|
};
|
||||||
|
|
||||||
|
await mqttServer.StartAsync();
|
||||||
|
|
||||||
|
Console.WriteLine("Press Enter to exit.");
|
||||||
|
Console.ReadLine();
|
||||||
|
|
||||||
|
await mqttServer.StopAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@
|
|||||||
// ReSharper disable MemberCanBeMadeStatic.Local
|
// ReSharper disable MemberCanBeMadeStatic.Local
|
||||||
|
|
||||||
using MQTTnet.Internal;
|
using MQTTnet.Internal;
|
||||||
|
using MQTTnet.Protocol;
|
||||||
using MQTTnet.Server;
|
using MQTTnet.Server;
|
||||||
|
|
||||||
namespace MQTTnet.Samples.Server;
|
namespace MQTTnet.Samples.Server;
|
||||||
@@ -22,11 +23,10 @@ public static class Server_Intercepting_Samples
|
|||||||
* Please see _Server_Simple_Samples_ for more details on how to start a server.
|
* Please see _Server_Simple_Samples_ for more details on how to start a server.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var 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);
|
||||||
{
|
|
||||||
mqttServer.InterceptingPublishAsync += args =>
|
mqttServer.InterceptingPublishAsync += args =>
|
||||||
{
|
{
|
||||||
// Here we only change the topic of the received application message.
|
// Here we only change the topic of the received application message.
|
||||||
@@ -43,5 +43,32 @@ public static class Server_Intercepting_Samples
|
|||||||
Console.ReadLine();
|
Console.ReadLine();
|
||||||
await mqttServer.StopAsync();
|
await mqttServer.StopAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static async Task Intercept_Subscription()
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* This sample starts a simple MQTT server which does not allow to subscribe to '#'
|
||||||
|
* Start the server and try to subscribe to '#' with a client of choice.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var mqttServerFactory = new MqttServerFactory();
|
||||||
|
var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
|
||||||
|
|
||||||
|
using var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions);
|
||||||
|
mqttServer.InterceptingSubscriptionAsync += args =>
|
||||||
|
{
|
||||||
|
if (args.TopicFilter.Topic.Equals("#", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
args.Response.ReasonCode = MqttSubscribeReasonCode.NotAuthorized;
|
||||||
|
}
|
||||||
|
|
||||||
|
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,21 @@ 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 +62,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.");
|
||||||
}
|
}
|
||||||
@@ -78,5 +86,56 @@ public static class Server_Retained_Messages_Samples
|
|||||||
Console.WriteLine("Press Enter to exit.");
|
Console.WriteLine("Press Enter to exit.");
|
||||||
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,8 @@
|
|||||||
// ReSharper disable UnusedMember.Global
|
// ReSharper disable UnusedMember.Global
|
||||||
// ReSharper disable InconsistentNaming
|
// ReSharper disable InconsistentNaming
|
||||||
|
|
||||||
using MQTTnet.Diagnostics;
|
using System.Globalization;
|
||||||
|
using MQTTnet.Diagnostics.Logger;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
using MQTTnet.Server;
|
using MQTTnet.Server;
|
||||||
|
|
||||||
@@ -22,8 +23,7 @@ public static class Server_Simple_Samples
|
|||||||
* See _Run_Minimal_Server_ for more information.
|
* See _Run_Minimal_Server_ for more information.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
using (var mqttServer = await StartMqttServer())
|
using var mqttServer = await StartMqttServer();
|
||||||
{
|
|
||||||
// Let the client connect.
|
// Let the client connect.
|
||||||
await Task.Delay(TimeSpan.FromSeconds(5));
|
await Task.Delay(TimeSpan.FromSeconds(5));
|
||||||
|
|
||||||
@@ -34,7 +34,6 @@ public static class Server_Simple_Samples
|
|||||||
await affectedClient.DisconnectAsync();
|
await affectedClient.DisconnectAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task Publish_Message_From_Broker()
|
public static async Task Publish_Message_From_Broker()
|
||||||
{
|
{
|
||||||
@@ -44,8 +43,7 @@ public static class Server_Simple_Samples
|
|||||||
* See _Run_Minimal_Server_ for more information.
|
* See _Run_Minimal_Server_ for more information.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
using (var mqttServer = await StartMqttServer())
|
using var mqttServer = await StartMqttServer();
|
||||||
{
|
|
||||||
// Create a new message using the builder as usual.
|
// Create a new message using the builder as usual.
|
||||||
var message = new MqttApplicationMessageBuilder().WithTopic("HelloWorld").WithPayload("Test").Build();
|
var message = new MqttApplicationMessageBuilder().WithTopic("HelloWorld").WithPayload("Test").Build();
|
||||||
|
|
||||||
@@ -56,7 +54,6 @@ public static class Server_Simple_Samples
|
|||||||
SenderClientId = "SenderClientId"
|
SenderClientId = "SenderClientId"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task Run_Minimal_Server()
|
public static async Task Run_Minimal_Server()
|
||||||
{
|
{
|
||||||
@@ -64,7 +61,7 @@ public static class Server_Simple_Samples
|
|||||||
* This sample starts a simple MQTT server which will accept any TCP connection.
|
* 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,8 +74,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();
|
||||||
|
|
||||||
Console.WriteLine("Press Enter to exit.");
|
Console.WriteLine("Press Enter to exit.");
|
||||||
@@ -87,7 +83,6 @@ public static class Server_Simple_Samples
|
|||||||
// Stop and dispose the MQTT server if it is no longer needed!
|
// Stop and dispose the MQTT server if it is no longer needed!
|
||||||
await mqttServer.StopAsync();
|
await mqttServer.StopAsync();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task Run_Server_With_Logging()
|
public static async Task Run_Server_With_Logging()
|
||||||
{
|
{
|
||||||
@@ -99,12 +94,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();
|
||||||
|
|
||||||
Console.WriteLine("Press Enter to exit.");
|
Console.WriteLine("Press Enter to exit.");
|
||||||
@@ -113,7 +107,6 @@ public static class Server_Simple_Samples
|
|||||||
// Stop and dispose the MQTT server if it is no longer needed!
|
// Stop and dispose the MQTT server if it is no longer needed!
|
||||||
await mqttServer.StopAsync();
|
await mqttServer.StopAsync();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task Validating_Connections()
|
public static async Task Validating_Connections()
|
||||||
{
|
{
|
||||||
@@ -123,12 +116,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.
|
||||||
mqttServer.ValidatingConnectionAsync += e =>
|
mqttServer.ValidatingConnectionAsync += e =>
|
||||||
@@ -158,20 +150,19 @@ public static class Server_Simple_Samples
|
|||||||
|
|
||||||
await mqttServer.StopAsync();
|
await mqttServer.StopAsync();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ConsoleLogger : IMqttNetLogger
|
sealed class ConsoleLogger : IMqttNetLogger
|
||||||
{
|
{
|
||||||
readonly object _consoleSyncRoot = new();
|
readonly object _consoleSyncRoot = new();
|
||||||
|
|
||||||
@@ -201,7 +192,7 @@ public static class Server_Simple_Samples
|
|||||||
|
|
||||||
if (parameters?.Length > 0)
|
if (parameters?.Length > 0)
|
||||||
{
|
{
|
||||||
message = string.Format(message, parameters);
|
message = string.Format(CultureInfo.InvariantCulture, message, parameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
lock (_consoleSyncRoot)
|
lock (_consoleSyncRoot)
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
// 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));
|
||||||
|
#pragma warning disable SYSLIB0057
|
||||||
|
var pfxCertificate = new X509Certificate2(
|
||||||
|
certificate.Export(X509ContentType.Pfx),
|
||||||
|
(string)null!,
|
||||||
|
X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable);
|
||||||
|
#pragma warning restore SYSLIB0057
|
||||||
|
|
||||||
|
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,50 +0,0 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Connections;
|
|
||||||
using MQTTnet.Adapter;
|
|
||||||
using MQTTnet.Diagnostics;
|
|
||||||
using MQTTnet.Formatter;
|
|
||||||
using MQTTnet.Server;
|
|
||||||
|
|
||||||
namespace MQTTnet.AspNetCore.Tests.Mockups
|
|
||||||
{
|
|
||||||
public sealed class ConnectionHandlerMockup : IMqttServerAdapter
|
|
||||||
{
|
|
||||||
public Func<IMqttChannelAdapter, Task> ClientHandler { get; set; }
|
|
||||||
public TaskCompletionSource<MqttConnectionContext> Context { get; } = new TaskCompletionSource<MqttConnectionContext>();
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task OnConnectedAsync(ConnectionContext connection)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var formatter = new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535));
|
|
||||||
var context = new MqttConnectionContext(formatter, connection);
|
|
||||||
Context.TrySetResult(context);
|
|
||||||
|
|
||||||
await ClientHandler(context);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Context.TrySetException(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task StartAsync(MqttServerOptions options, IMqttNetLogger logger)
|
|
||||||
{
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task StopAsync()
|
|
||||||
{
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
using System.IO.Pipelines;
|
|
||||||
|
|
||||||
namespace MQTTnet.AspNetCore.Tests.Mockups
|
|
||||||
{
|
|
||||||
public sealed class DuplexPipeMockup : IDuplexPipe
|
|
||||||
{
|
|
||||||
public DuplexPipeMockup()
|
|
||||||
{
|
|
||||||
var pool = new LimitedMemoryPool();
|
|
||||||
var pipeOptions = new PipeOptions(pool);
|
|
||||||
Receive = new Pipe(pipeOptions);
|
|
||||||
Send = new Pipe(pipeOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
PipeReader IDuplexPipe.Input => Receive.Reader;
|
|
||||||
|
|
||||||
PipeWriter IDuplexPipe.Output => Send.Writer;
|
|
||||||
|
|
||||||
public Pipe Receive { get; }
|
|
||||||
|
|
||||||
public Pipe Send { get; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
using System.Buffers;
|
|
||||||
|
|
||||||
namespace MQTTnet.AspNetCore.Tests.Mockups
|
|
||||||
{
|
|
||||||
public sealed class LimitedMemoryPool : MemoryPool<byte>
|
|
||||||
{
|
|
||||||
protected override void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public override IMemoryOwner<byte> Rent(int minBufferSize = -1)
|
|
||||||
{
|
|
||||||
return new MemoryOwner(minBufferSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override int MaxBufferSize { get; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Buffers;
|
|
||||||
|
|
||||||
namespace MQTTnet.AspNetCore.Tests.Mockups
|
|
||||||
{
|
|
||||||
public sealed class MemoryOwner : IMemoryOwner<byte>
|
|
||||||
{
|
|
||||||
readonly byte[] _raw;
|
|
||||||
|
|
||||||
public MemoryOwner(int size)
|
|
||||||
{
|
|
||||||
if (size <= 0)
|
|
||||||
{
|
|
||||||
size = 1024;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (size > 4096)
|
|
||||||
{
|
|
||||||
size = 4096;
|
|
||||||
}
|
|
||||||
|
|
||||||
_raw = ArrayPool<byte>.Shared.Rent(size);
|
|
||||||
Memory = _raw;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Memory<byte> Memory { get; }
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
ArrayPool<byte>.Shared.Return(_raw);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,133 +0,0 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Net;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Builder;
|
|
||||||
using Microsoft.AspNetCore.Connections;
|
|
||||||
using Microsoft.AspNetCore.Hosting;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
|
||||||
using MQTTnet.Adapter;
|
|
||||||
using MQTTnet.AspNetCore.Tests.Mockups;
|
|
||||||
using MQTTnet.Client;
|
|
||||||
using MQTTnet.Exceptions;
|
|
||||||
using MQTTnet.Formatter;
|
|
||||||
using MQTTnet.Packets;
|
|
||||||
using MQTTnet.Protocol;
|
|
||||||
using MQTTnet.Tests.Extensions;
|
|
||||||
|
|
||||||
namespace MQTTnet.AspNetCore.Tests
|
|
||||||
{
|
|
||||||
[TestClass]
|
|
||||||
public class MqttConnectionContextTest
|
|
||||||
{
|
|
||||||
[TestMethod]
|
|
||||||
public async Task TestCorruptedConnectPacket()
|
|
||||||
{
|
|
||||||
var writer = new MqttBufferWriter(4096, 65535);
|
|
||||||
var serializer = new MqttPacketFormatterAdapter(writer);
|
|
||||||
var pipe = new DuplexPipeMockup();
|
|
||||||
var connection = new DefaultConnectionContext();
|
|
||||||
connection.Transport = pipe;
|
|
||||||
var ctx = new MqttConnectionContext(serializer, connection);
|
|
||||||
|
|
||||||
await pipe.Receive.Writer.WriteAsync(writer.AddMqttHeader(MqttControlPacketType.Connect, new byte[0]));
|
|
||||||
|
|
||||||
await Assert.ThrowsExceptionAsync<MqttProtocolViolationException>(() => ctx.ReceivePacketAsync(CancellationToken.None));
|
|
||||||
|
|
||||||
// the first exception should complete the pipes so if someone tries to use the connection after that it should throw immidiatly
|
|
||||||
await Assert.ThrowsExceptionAsync<InvalidOperationException>(() => ctx.ReceivePacketAsync(CancellationToken.None));
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public async Task TestEndpoint()
|
|
||||||
{
|
|
||||||
var mockup = new ConnectionHandlerMockup();
|
|
||||||
|
|
||||||
|
|
||||||
using (var host = new WebHostBuilder().UseKestrel(kestrel => kestrel.ListenLocalhost(1883, listener => listener.Use((ctx, next) => mockup.OnConnectedAsync(ctx))))
|
|
||||||
.UseStartup<Startup>()
|
|
||||||
.ConfigureServices(
|
|
||||||
(hostContext, services) =>
|
|
||||||
{
|
|
||||||
services.AddHostedMqttServer(o => o.WithoutDefaultEndpoint());
|
|
||||||
services.AddSingleton<IMqttServerAdapter>(mockup);
|
|
||||||
})
|
|
||||||
.Build())
|
|
||||||
|
|
||||||
using (var client = new MqttFactory().CreateMqttClient())
|
|
||||||
{
|
|
||||||
host.Start();
|
|
||||||
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("localhost").Build(), CancellationToken.None);
|
|
||||||
|
|
||||||
var ctx = await mockup.Context.Task;
|
|
||||||
#if NETCOREAPP3_1
|
|
||||||
var ep = IPEndPoint.Parse(ctx.Endpoint);
|
|
||||||
Assert.IsNotNull(ep);
|
|
||||||
#endif
|
|
||||||
Assert.IsNotNull(ctx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// COMMENTED OUT DUE TO DEAD LOCK? OR VERY VERY SLOW PERFORMANCE ON LOCAL DEV MACHINE. TEST WAS STILL RUNNING AFTER SEVERAL MINUTES!
|
|
||||||
//[TestMethod]
|
|
||||||
//public async Task TestParallelWrites()
|
|
||||||
//{
|
|
||||||
// var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311);
|
|
||||||
// var pipe = new DuplexPipeMockup();
|
|
||||||
// var connection = new DefaultConnectionContext();
|
|
||||||
// connection.Transport = pipe;
|
|
||||||
// var ctx = new MqttConnectionContext(serializer, connection);
|
|
||||||
|
|
||||||
// var tasks = Enumerable.Range(1, 100).Select(_ => Task.Run(async () =>
|
|
||||||
// {
|
|
||||||
// for (int i = 0; i < 100; i++)
|
|
||||||
// {
|
|
||||||
// await ctx.SendPacketAsync(new MqttPublishPacket(), TimeSpan.Zero, CancellationToken.None).ConfigureAwait(false);
|
|
||||||
// }
|
|
||||||
// }));
|
|
||||||
|
|
||||||
// await Task.WhenAll(tasks).ConfigureAwait(false);
|
|
||||||
//}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public async Task TestLargePacket()
|
|
||||||
{
|
|
||||||
var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535));
|
|
||||||
var pipe = new DuplexPipeMockup();
|
|
||||||
var connection = new DefaultConnectionContext();
|
|
||||||
connection.Transport = pipe;
|
|
||||||
var ctx = new MqttConnectionContext(serializer, connection);
|
|
||||||
|
|
||||||
await ctx.SendPacketAsync(new MqttPublishPacket { Payload = new byte[20_000] }, CancellationToken.None).ConfigureAwait(false);
|
|
||||||
|
|
||||||
var readResult = await pipe.Send.Reader.ReadAsync();
|
|
||||||
Assert.IsTrue(readResult.Buffer.Length > 20000);
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public async Task TestReceivePacketAsyncThrowsWhenReaderCompleted()
|
|
||||||
{
|
|
||||||
var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535));
|
|
||||||
var pipe = new DuplexPipeMockup();
|
|
||||||
var connection = new DefaultConnectionContext();
|
|
||||||
connection.Transport = pipe;
|
|
||||||
var ctx = new MqttConnectionContext(serializer, connection);
|
|
||||||
|
|
||||||
pipe.Receive.Writer.Complete();
|
|
||||||
|
|
||||||
await Assert.ThrowsExceptionAsync<MqttCommunicationException>(() => ctx.ReceivePacketAsync(CancellationToken.None));
|
|
||||||
}
|
|
||||||
|
|
||||||
class Startup
|
|
||||||
{
|
|
||||||
public void Configure(IApplicationBuilder app)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
#if NETCOREAPP3_1
|
|
||||||
using System.Buffers;
|
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
|
||||||
using MQTTnet.AspNetCore;
|
|
||||||
using MQTTnet.Formatter;
|
|
||||||
using MQTTnet.Packets;
|
|
||||||
|
|
||||||
namespace MQTTnet.AspNetCore.Tests
|
|
||||||
{
|
|
||||||
[TestClass]
|
|
||||||
public sealed class ReaderExtensionsTest
|
|
||||||
{
|
|
||||||
[TestMethod]
|
|
||||||
public void TestTryDeserialize()
|
|
||||||
{
|
|
||||||
var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535));
|
|
||||||
|
|
||||||
var buffer = serializer.Encode(new MqttPublishPacket {Topic = "a", 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,16 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net6.0</TargetFramework>
|
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
<NoWarn>$(NoWarn);CA1707</NoWarn>
|
||||||
<EnableNETAnalyzers>false</EnableNETAnalyzers>
|
|
||||||
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
|
|
||||||
</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')
|
||||||
|
|||||||
@@ -4,11 +4,11 @@
|
|||||||
|
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
|
|
||||||
namespace MQTTnet.AspTestApp.Pages
|
namespace MQTTnet.AspTestApp.Pages;
|
||||||
|
|
||||||
|
public partial class IndexModel : PageModel
|
||||||
{
|
{
|
||||||
public class IndexModel : PageModel
|
readonly ILogger<IndexModel> _logger;
|
||||||
{
|
|
||||||
private readonly ILogger<IndexModel> _logger;
|
|
||||||
|
|
||||||
public IndexModel(ILogger<IndexModel> logger)
|
public IndexModel(ILogger<IndexModel> logger)
|
||||||
{
|
{
|
||||||
@@ -17,7 +17,9 @@ namespace MQTTnet.AspTestApp.Pages
|
|||||||
|
|
||||||
public void OnGet()
|
public void OnGet()
|
||||||
{
|
{
|
||||||
|
LogOnGet();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
[LoggerMessage(Level = LogLevel.Information, Message = "OnGet")]
|
||||||
}
|
private partial void LogOnGet();
|
||||||
}
|
}
|
||||||
@@ -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();
|
||||||
|
|
||||||
|
|||||||
@@ -2,20 +2,21 @@
|
|||||||
// 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 Microsoft.AspNetCore.Builder;
|
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(...).")]
|
||||||
[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")
|
public static IApplicationBuilder UseMqttEndpoint(this IApplicationBuilder app, string path = "/mqtt")
|
||||||
{
|
{
|
||||||
app.UseWebSockets();
|
app.UseWebSockets();
|
||||||
app.Use(async (context, next) =>
|
app.Use(
|
||||||
|
async (context, next) =>
|
||||||
{
|
{
|
||||||
if (!context.WebSockets.IsWebSocketRequest || context.Request.Path != path)
|
if (!context.WebSockets.IsWebSocketRequest || context.Request.Path != path)
|
||||||
{
|
{
|
||||||
@@ -31,10 +32,8 @@ namespace MQTTnet.AspNetCore
|
|||||||
}
|
}
|
||||||
|
|
||||||
var adapter = app.ApplicationServices.GetRequiredService<MqttWebSocketServerAdapter>();
|
var adapter = app.ApplicationServices.GetRequiredService<MqttWebSocketServerAdapter>();
|
||||||
using (var webSocket = await context.WebSockets.AcceptWebSocketAsync(subProtocol).ConfigureAwait(false))
|
using var webSocket = await context.WebSockets.AcceptWebSocketAsync(subProtocol).ConfigureAwait(false);
|
||||||
{
|
|
||||||
await adapter.RunWebSocketConnectionAsync(webSocket, context);
|
await adapter.RunWebSocketConnectionAsync(webSocket, context);
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return app;
|
return app;
|
||||||
@@ -48,5 +47,4 @@ namespace MQTTnet.AspNetCore
|
|||||||
|
|
||||||
return app;
|
return app;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -3,17 +3,15 @@
|
|||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using MQTTnet.Server;
|
using MQTTnet.Server;
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace MQTTnet.AspNetCore
|
namespace MQTTnet.AspNetCore;
|
||||||
|
|
||||||
|
public sealed class AspNetMqttServerOptionsBuilder : MqttServerOptionsBuilder
|
||||||
{
|
{
|
||||||
public sealed class AspNetMqttServerOptionsBuilder : MqttServerOptionsBuilder
|
|
||||||
{
|
|
||||||
public AspNetMqttServerOptionsBuilder(IServiceProvider serviceProvider)
|
public AspNetMqttServerOptionsBuilder(IServiceProvider serviceProvider)
|
||||||
{
|
{
|
||||||
ServiceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
|
ServiceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
|
||||||
}
|
}
|
||||||
|
|
||||||
public IServiceProvider ServiceProvider { get; }
|
public IServiceProvider ServiceProvider { get; }
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace MQTTnet.AspNetCore;
|
||||||
|
|
||||||
|
public static class BufferExtensions
|
||||||
|
{
|
||||||
|
public static ArraySegment<byte> GetArray(this ReadOnlyMemory<byte> memory)
|
||||||
|
{
|
||||||
|
if (!MemoryMarshal.TryGetArray(memory, out var result))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Buffer backed by array was expected");
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
using MQTTnet.Adapter;
|
|
||||||
using MQTTnet.AspNetCore.Client.Tcp;
|
|
||||||
using MQTTnet.Formatter;
|
|
||||||
using System;
|
|
||||||
using System.Net;
|
|
||||||
using MQTTnet.Client;
|
|
||||||
using MQTTnet.Diagnostics;
|
|
||||||
|
|
||||||
namespace MQTTnet.AspNetCore.Client
|
|
||||||
{
|
|
||||||
public sealed class MqttClientConnectionContextFactory : IMqttClientAdapterFactory
|
|
||||||
{
|
|
||||||
public IMqttChannelAdapter CreateClientAdapter(MqttClientOptions options, MqttPacketInspector packetInspector, IMqttNetLogger logger)
|
|
||||||
{
|
|
||||||
if (options == null) throw new ArgumentNullException(nameof(options));
|
|
||||||
|
|
||||||
switch (options.ChannelOptions)
|
|
||||||
{
|
|
||||||
case MqttClientTcpOptions tcpOptions:
|
|
||||||
{
|
|
||||||
var endpoint = new DnsEndPoint(tcpOptions.Server, tcpOptions.GetPort());
|
|
||||||
var tcpConnection = new TcpConnection(endpoint);
|
|
||||||
|
|
||||||
var formatter = new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(4096, 65535));
|
|
||||||
return new MqttConnectionContext(formatter, tcpConnection);
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
{
|
|
||||||
throw new NotSupportedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace MQTTnet.AspNetCore.Client.Tcp
|
|
||||||
{
|
|
||||||
public static class BufferExtensions
|
|
||||||
{
|
|
||||||
public static ArraySegment<byte> GetArray(this Memory<byte> memory)
|
|
||||||
{
|
|
||||||
return ((ReadOnlyMemory<byte>)memory).GetArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ArraySegment<byte> GetArray(this ReadOnlyMemory<byte> memory)
|
|
||||||
{
|
|
||||||
if (!MemoryMarshal.TryGetArray(memory, out var result))
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("Buffer backed by array was expected");
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
using System.IO.Pipelines;
|
|
||||||
|
|
||||||
namespace MQTTnet.AspNetCore.Client.Tcp
|
|
||||||
{
|
|
||||||
public class DuplexPipe : IDuplexPipe
|
|
||||||
{
|
|
||||||
public DuplexPipe(PipeReader reader, PipeWriter writer)
|
|
||||||
{
|
|
||||||
Input = reader;
|
|
||||||
Output = writer;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PipeReader Input { get; }
|
|
||||||
|
|
||||||
public PipeWriter Output { get; }
|
|
||||||
|
|
||||||
public static DuplexPipePair CreateConnectionPair(PipeOptions inputOptions, PipeOptions outputOptions)
|
|
||||||
{
|
|
||||||
var input = new Pipe(inputOptions);
|
|
||||||
var output = new Pipe(outputOptions);
|
|
||||||
|
|
||||||
var transportToApplication = new DuplexPipe(output.Reader, input.Writer);
|
|
||||||
var applicationToTransport = new DuplexPipe(input.Reader, output.Writer);
|
|
||||||
|
|
||||||
return new DuplexPipePair(applicationToTransport, transportToApplication);
|
|
||||||
}
|
|
||||||
|
|
||||||
// This class exists to work around issues with value tuple on .NET Framework
|
|
||||||
public readonly struct DuplexPipePair
|
|
||||||
{
|
|
||||||
public IDuplexPipe Transport { get; }
|
|
||||||
public IDuplexPipe Application { get; }
|
|
||||||
|
|
||||||
public DuplexPipePair(IDuplexPipe transport, IDuplexPipe application)
|
|
||||||
{
|
|
||||||
Transport = transport;
|
|
||||||
Application = application;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.IO.Pipelines;
|
|
||||||
using System.Net.Sockets;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace MQTTnet.AspNetCore.Client.Tcp
|
|
||||||
{
|
|
||||||
public class SocketAwaitable : ICriticalNotifyCompletion
|
|
||||||
{
|
|
||||||
private static readonly Action _callbackCompleted = () => { };
|
|
||||||
|
|
||||||
private readonly PipeScheduler _ioScheduler;
|
|
||||||
|
|
||||||
private Action _callback;
|
|
||||||
private int _bytesTransferred;
|
|
||||||
private SocketError _error;
|
|
||||||
|
|
||||||
public SocketAwaitable(PipeScheduler ioScheduler)
|
|
||||||
{
|
|
||||||
_ioScheduler = ioScheduler;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsCompleted => ReferenceEquals(_callback, _callbackCompleted);
|
|
||||||
|
|
||||||
public SocketAwaitable GetAwaiter() => this;
|
|
||||||
|
|
||||||
public int GetResult()
|
|
||||||
{
|
|
||||||
Debug.Assert(ReferenceEquals(_callback, _callbackCompleted));
|
|
||||||
|
|
||||||
_callback = null;
|
|
||||||
|
|
||||||
if (_error != SocketError.Success)
|
|
||||||
{
|
|
||||||
throw new SocketException((int)_error);
|
|
||||||
}
|
|
||||||
|
|
||||||
return _bytesTransferred;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnCompleted(Action continuation)
|
|
||||||
{
|
|
||||||
if (ReferenceEquals(_callback, _callbackCompleted) ||
|
|
||||||
ReferenceEquals(Interlocked.CompareExchange(ref _callback, continuation, null), _callbackCompleted))
|
|
||||||
{
|
|
||||||
Task.Run(continuation);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void UnsafeOnCompleted(Action continuation)
|
|
||||||
{
|
|
||||||
OnCompleted(continuation);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Complete(int bytesTransferred, SocketError socketError)
|
|
||||||
{
|
|
||||||
_error = socketError;
|
|
||||||
_bytesTransferred = bytesTransferred;
|
|
||||||
var continuation = Interlocked.Exchange(ref _callback, _callbackCompleted);
|
|
||||||
|
|
||||||
if (continuation != null)
|
|
||||||
{
|
|
||||||
_ioScheduler.Schedule(state => ((Action)state)(), continuation);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.IO.Pipelines;
|
|
||||||
using System.Net.Sockets;
|
|
||||||
|
|
||||||
namespace MQTTnet.AspNetCore.Client.Tcp
|
|
||||||
{
|
|
||||||
public class SocketReceiver
|
|
||||||
{
|
|
||||||
private readonly Socket _socket;
|
|
||||||
private readonly SocketAsyncEventArgs _eventArgs = new SocketAsyncEventArgs();
|
|
||||||
private readonly SocketAwaitable _awaitable;
|
|
||||||
|
|
||||||
public SocketReceiver(Socket socket, PipeScheduler scheduler)
|
|
||||||
{
|
|
||||||
_socket = socket;
|
|
||||||
_awaitable = new SocketAwaitable(scheduler);
|
|
||||||
_eventArgs.UserToken = _awaitable;
|
|
||||||
_eventArgs.Completed += (_, e) => ((SocketAwaitable)e.UserToken).Complete(e.BytesTransferred, e.SocketError);
|
|
||||||
}
|
|
||||||
|
|
||||||
public SocketAwaitable ReceiveAsync(Memory<byte> buffer)
|
|
||||||
{
|
|
||||||
#if NETCOREAPP2_1
|
|
||||||
_eventArgs.SetBuffer(buffer);
|
|
||||||
#else
|
|
||||||
var segment = buffer.GetArray();
|
|
||||||
_eventArgs.SetBuffer(segment.Array, segment.Offset, segment.Count);
|
|
||||||
#endif
|
|
||||||
if (!_socket.ReceiveAsync(_eventArgs))
|
|
||||||
{
|
|
||||||
_awaitable.Complete(_eventArgs.BytesTransferred, _eventArgs.SocketError);
|
|
||||||
}
|
|
||||||
|
|
||||||
return _awaitable;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Buffers;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.IO.Pipelines;
|
|
||||||
using System.Net.Sockets;
|
|
||||||
|
|
||||||
#if NETCOREAPP2_1
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace MQTTnet.AspNetCore.Client.Tcp
|
|
||||||
{
|
|
||||||
public class SocketSender
|
|
||||||
{
|
|
||||||
private readonly Socket _socket;
|
|
||||||
private readonly SocketAsyncEventArgs _eventArgs = new SocketAsyncEventArgs();
|
|
||||||
private readonly SocketAwaitable _awaitable;
|
|
||||||
|
|
||||||
private List<ArraySegment<byte>> _bufferList;
|
|
||||||
|
|
||||||
public SocketSender(Socket socket, PipeScheduler scheduler)
|
|
||||||
{
|
|
||||||
_socket = socket;
|
|
||||||
_awaitable = new SocketAwaitable(scheduler);
|
|
||||||
_eventArgs.UserToken = _awaitable;
|
|
||||||
_eventArgs.Completed += (_, e) => ((SocketAwaitable)e.UserToken).Complete(e.BytesTransferred, e.SocketError);
|
|
||||||
}
|
|
||||||
|
|
||||||
public SocketAwaitable SendAsync(in ReadOnlySequence<byte> buffers)
|
|
||||||
{
|
|
||||||
if (buffers.IsSingleSegment)
|
|
||||||
{
|
|
||||||
return SendAsync(buffers.First);
|
|
||||||
}
|
|
||||||
|
|
||||||
#if NETCOREAPP2_1
|
|
||||||
if (!_eventArgs.MemoryBuffer.Equals(Memory<byte>.Empty))
|
|
||||||
#else
|
|
||||||
if (_eventArgs.Buffer != null)
|
|
||||||
#endif
|
|
||||||
{
|
|
||||||
_eventArgs.SetBuffer(null, 0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
_eventArgs.BufferList = GetBufferList(buffers);
|
|
||||||
|
|
||||||
if (!_socket.SendAsync(_eventArgs))
|
|
||||||
{
|
|
||||||
_awaitable.Complete(_eventArgs.BytesTransferred, _eventArgs.SocketError);
|
|
||||||
}
|
|
||||||
|
|
||||||
return _awaitable;
|
|
||||||
}
|
|
||||||
|
|
||||||
private SocketAwaitable SendAsync(ReadOnlyMemory<byte> memory)
|
|
||||||
{
|
|
||||||
// The BufferList getter is much less expensive then the setter.
|
|
||||||
if (_eventArgs.BufferList != null)
|
|
||||||
{
|
|
||||||
_eventArgs.BufferList = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
#if NETCOREAPP2_1
|
|
||||||
_eventArgs.SetBuffer(MemoryMarshal.AsMemory(memory));
|
|
||||||
#else
|
|
||||||
var segment = memory.GetArray();
|
|
||||||
_eventArgs.SetBuffer(segment.Array, segment.Offset, segment.Count);
|
|
||||||
#endif
|
|
||||||
if (!_socket.SendAsync(_eventArgs))
|
|
||||||
{
|
|
||||||
_awaitable.Complete(_eventArgs.BytesTransferred, _eventArgs.SocketError);
|
|
||||||
}
|
|
||||||
|
|
||||||
return _awaitable;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<ArraySegment<byte>> GetBufferList(in ReadOnlySequence<byte> buffer)
|
|
||||||
{
|
|
||||||
Debug.Assert(!buffer.IsEmpty);
|
|
||||||
Debug.Assert(!buffer.IsSingleSegment);
|
|
||||||
|
|
||||||
if (_bufferList == null)
|
|
||||||
{
|
|
||||||
_bufferList = new List<ArraySegment<byte>>();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Buffers are pooled, so it's OK to root them until the next multi-buffer write.
|
|
||||||
_bufferList.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var b in buffer)
|
|
||||||
{
|
|
||||||
_bufferList.Add(b.GetArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
return _bufferList;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,272 +0,0 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
using Microsoft.AspNetCore.Connections;
|
|
||||||
using Microsoft.AspNetCore.Http.Features;
|
|
||||||
using MQTTnet.Exceptions;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.IO.Pipelines;
|
|
||||||
using System.Net;
|
|
||||||
using System.Net.Sockets;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace MQTTnet.AspNetCore.Client.Tcp
|
|
||||||
{
|
|
||||||
public class TcpConnection : ConnectionContext
|
|
||||||
{
|
|
||||||
private volatile bool _aborted;
|
|
||||||
private readonly EndPoint _endPoint;
|
|
||||||
private SocketSender _sender;
|
|
||||||
private SocketReceiver _receiver;
|
|
||||||
|
|
||||||
private Socket _socket;
|
|
||||||
private IDuplexPipe _application;
|
|
||||||
|
|
||||||
public bool IsConnected { get; private set; }
|
|
||||||
public override string ConnectionId { get; set; }
|
|
||||||
public override IFeatureCollection Features { get; }
|
|
||||||
public override IDictionary<object, object> Items { get; set; }
|
|
||||||
public override IDuplexPipe Transport { get; set; }
|
|
||||||
|
|
||||||
public TcpConnection(EndPoint endPoint)
|
|
||||||
{
|
|
||||||
_endPoint = endPoint;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TcpConnection(Socket socket)
|
|
||||||
{
|
|
||||||
_socket = socket;
|
|
||||||
_endPoint = socket.RemoteEndPoint;
|
|
||||||
|
|
||||||
_sender = new SocketSender(_socket, PipeScheduler.ThreadPool);
|
|
||||||
_receiver = new SocketReceiver(_socket, PipeScheduler.ThreadPool);
|
|
||||||
}
|
|
||||||
#if NETCOREAPP3_1 || 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,13 +4,12 @@
|
|||||||
|
|
||||||
using Microsoft.AspNetCore.Connections;
|
using Microsoft.AspNetCore.Connections;
|
||||||
|
|
||||||
namespace MQTTnet.AspNetCore
|
namespace MQTTnet.AspNetCore;
|
||||||
|
|
||||||
|
public static class ConnectionBuilderExtensions
|
||||||
{
|
{
|
||||||
public static class ConnectionBuilderExtensions
|
|
||||||
{
|
|
||||||
public static IConnectionBuilder UseMqtt(this IConnectionBuilder builder)
|
public static IConnectionBuilder UseMqtt(this IConnectionBuilder builder)
|
||||||
{
|
{
|
||||||
return builder.UseConnectionHandler<MqttConnectionHandler>();
|
return builder.UseConnectionHandler<MqttConnectionHandler>();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
using Microsoft.AspNetCore.Http.Connections;
|
|
||||||
|
|
||||||
#if NETCOREAPP3_1
|
|
||||||
using System;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace MQTTnet.AspNetCore
|
|
||||||
{
|
|
||||||
public static class ConnectionRouteBuilderExtensions
|
|
||||||
{
|
|
||||||
#if NETCOREAPP3_1
|
|
||||||
[Obsolete("This class is obsolete and will be removed in a future version. The recommended alternative is to use MapMqtt inside Microsoft.AspNetCore.Builder.UseEndpoints(...).")]
|
|
||||||
#endif
|
|
||||||
#if NETCOREAPP3_1 || NETCOREAPP2_1 || NETSTANDARD
|
|
||||||
public static void MapMqtt(this ConnectionsRouteBuilder connection, PathString path)
|
|
||||||
{
|
|
||||||
connection.MapConnectionHandler<MqttConnectionHandler>(path, options =>
|
|
||||||
{
|
|
||||||
options.WebSockets.SubProtocolSelector = MqttSubProtocolSelector.SelectSubProtocol;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System.IO.Pipelines;
|
||||||
|
|
||||||
|
namespace MQTTnet.AspNetCore;
|
||||||
|
|
||||||
|
public class DuplexPipe : IDuplexPipe
|
||||||
|
{
|
||||||
|
public DuplexPipe(PipeReader reader, PipeWriter writer)
|
||||||
|
{
|
||||||
|
Input = reader;
|
||||||
|
Output = writer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PipeReader Input { get; }
|
||||||
|
|
||||||
|
public PipeWriter Output { get; }
|
||||||
|
|
||||||
|
public static DuplexPipePair CreateConnectionPair(PipeOptions inputOptions, PipeOptions outputOptions)
|
||||||
|
{
|
||||||
|
var input = new Pipe(inputOptions);
|
||||||
|
var output = new Pipe(outputOptions);
|
||||||
|
|
||||||
|
var transportToApplication = new DuplexPipe(output.Reader, input.Writer);
|
||||||
|
var applicationToTransport = new DuplexPipe(input.Reader, output.Writer);
|
||||||
|
|
||||||
|
return new DuplexPipePair(applicationToTransport, transportToApplication);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This class exists to work around issues with value tuple on .NET Framework
|
||||||
|
public readonly struct DuplexPipePair
|
||||||
|
{
|
||||||
|
public IDuplexPipe Transport { get; }
|
||||||
|
public IDuplexPipe Application { get; }
|
||||||
|
|
||||||
|
public DuplexPipePair(IDuplexPipe transport, IDuplexPipe application)
|
||||||
|
{
|
||||||
|
Transport = transport;
|
||||||
|
Application = application;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,24 +2,20 @@
|
|||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// 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.
|
||||||
|
|
||||||
|
|
||||||
#if NETCOREAPP3_1 || NET5_0_OR_GREATER
|
|
||||||
|
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Routing;
|
using Microsoft.AspNetCore.Routing;
|
||||||
|
|
||||||
namespace MQTTnet.AspNetCore
|
namespace MQTTnet.AspNetCore;
|
||||||
|
|
||||||
|
public static class EndpointRouterExtensions
|
||||||
{
|
{
|
||||||
public static class EndpointRouterExtensions
|
|
||||||
{
|
|
||||||
public static void MapMqtt(this IEndpointRouteBuilder endpoints, string pattern)
|
public static void MapMqtt(this IEndpointRouteBuilder endpoints, string pattern)
|
||||||
{
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(endpoints);
|
||||||
|
|
||||||
endpoints.MapConnectionHandler<MqttConnectionHandler>(pattern, options =>
|
endpoints.MapConnectionHandler<MqttConnectionHandler>(pattern, options =>
|
||||||
{
|
{
|
||||||
options.WebSockets.SubProtocolSelector = MqttSubProtocolSelector.SelectSubProtocol;
|
options.WebSockets.SubProtocolSelector = MqttSubProtocolSelector.SelectSubProtocol;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|||||||
@@ -1,65 +1,26 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>netstandard2.0;netcoreapp2.1;netcoreapp3.1;net5.0;net6.0</TargetFrameworks>
|
|
||||||
|
|
||||||
<AssemblyName>MQTTnet.AspNetCore</AssemblyName>
|
<AssemblyName>MQTTnet.AspNetCore</AssemblyName>
|
||||||
<RootNamespace>MQTTnet.AspNetCore</RootNamespace>
|
<RootNamespace>MQTTnet.AspNetCore</RootNamespace>
|
||||||
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
<IsAotCompatible>true</IsAotCompatible>
|
||||||
<Company>The contributors of MQTTnet</Company>
|
|
||||||
<Product>MQTTnet</Product>
|
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||||
<Description>MQTTnet is a high performance .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker) and supports v3.1.0, v3.1.1 and v5.0.0 of the MQTT protocol.</Description>
|
<Description>MQTTnet is a high performance .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker) and supports v3.1.0, v3.1.1 and v5.0.0 of the MQTT protocol.</Description>
|
||||||
<Authors>The contributors of MQTTnet</Authors>
|
|
||||||
<PackageId>MQTTnet.AspNetCore</PackageId>
|
<PackageId>MQTTnet.AspNetCore</PackageId>
|
||||||
<SignAssembly>false</SignAssembly>
|
|
||||||
<DelaySign>false</DelaySign>
|
|
||||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
|
||||||
<IncludeSymbols>true</IncludeSymbols>
|
|
||||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
|
||||||
<Copyright>Copyright (c) .NET Foundation and Contributors</Copyright>
|
|
||||||
<PackageProjectUrl>https://github.com/dotnet/MQTTnet</PackageProjectUrl>
|
|
||||||
<RepositoryUrl>https://github.com/dotnet/MQTTnet.git</RepositoryUrl>
|
|
||||||
<RepositoryType>git</RepositoryType>
|
|
||||||
<PackageTags>MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin Blazor</PackageTags>
|
<PackageTags>MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin Blazor</PackageTags>
|
||||||
<NeutralLanguage>en-US</NeutralLanguage>
|
|
||||||
<EnableNETAnalyzers>false</EnableNETAnalyzers>
|
|
||||||
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
|
|
||||||
<PackageIcon>nuget.png</PackageIcon>
|
|
||||||
<EmbedUntrackedSources>true</EmbedUntrackedSources>
|
|
||||||
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
|
|
||||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
|
||||||
<PackageReleaseNotes>For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).</PackageReleaseNotes>
|
<PackageReleaseNotes>For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).</PackageReleaseNotes>
|
||||||
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
<NoWarn>1591</NoWarn>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="..\..\Images\nuget.png">
|
<FrameworkReference Include="Microsoft.AspNetCore.App"/>
|
||||||
<Pack>True</Pack>
|
|
||||||
<PackagePath>\</PackagePath>
|
|
||||||
</None>
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
|
<ProjectReference Include="..\MQTTnet.Server\MQTTnet.Server.csproj"/>
|
||||||
</ItemGroup>
|
<ProjectReference Include="..\MQTTnet\MQTTnet.csproj"/>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
|
||||||
<DefineConstants>RELEASE;NETSTANDARD2_0</DefineConstants>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp3.1' or '$(TargetFramework)' == 'net5.0' or '$(TargetFramework)' == 'net6.0' ">
|
|
||||||
<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>
|
|
||||||
<ProjectReference Include="..\MQTTnet\MQTTnet.csproj" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using MQTTnet.Adapter;
|
||||||
|
using MQTTnet.Diagnostics.Logger;
|
||||||
|
using MQTTnet.Formatter;
|
||||||
|
|
||||||
|
namespace MQTTnet.AspNetCore;
|
||||||
|
|
||||||
|
public sealed class MqttClientConnectionContextFactory : IMqttClientAdapterFactory
|
||||||
|
{
|
||||||
|
public IMqttChannelAdapter CreateClientAdapter(MqttClientOptions options, MqttPacketInspector packetInspector, IMqttNetLogger logger)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(options);
|
||||||
|
|
||||||
|
switch (options.ChannelOptions)
|
||||||
|
{
|
||||||
|
case MqttClientTcpOptions tcpOptions:
|
||||||
|
{
|
||||||
|
var tcpConnection = new SocketConnection(tcpOptions.RemoteEndpoint);
|
||||||
|
|
||||||
|
var formatter = new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(4096, 65535));
|
||||||
|
return new MqttConnectionContext(formatter, tcpConnection);
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,25 +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 System.Buffers;
|
||||||
|
using System.IO.Pipelines;
|
||||||
|
using System.Net;
|
||||||
|
using System.Security.Cryptography.X509Certificates;
|
||||||
using Microsoft.AspNetCore.Connections;
|
using Microsoft.AspNetCore.Connections;
|
||||||
using Microsoft.AspNetCore.Http.Connections.Features;
|
using Microsoft.AspNetCore.Http.Connections.Features;
|
||||||
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
using MQTTnet.Adapter;
|
using MQTTnet.Adapter;
|
||||||
using MQTTnet.AspNetCore.Client.Tcp;
|
|
||||||
using MQTTnet.Exceptions;
|
using MQTTnet.Exceptions;
|
||||||
using MQTTnet.Formatter;
|
using MQTTnet.Formatter;
|
||||||
using MQTTnet.Packets;
|
|
||||||
using System;
|
|
||||||
using System.IO.Pipelines;
|
|
||||||
using System.Security.Cryptography.X509Certificates;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
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();
|
||||||
readonly AsyncLock _writerLock = new AsyncLock();
|
|
||||||
|
|
||||||
PipeReader _input;
|
PipeReader _input;
|
||||||
PipeWriter _output;
|
PipeWriter _output;
|
||||||
@@ -28,60 +28,111 @@ namespace MQTTnet.AspNetCore
|
|||||||
public MqttConnectionContext(MqttPacketFormatterAdapter packetFormatterAdapter, ConnectionContext connection)
|
public MqttConnectionContext(MqttPacketFormatterAdapter packetFormatterAdapter, ConnectionContext connection)
|
||||||
{
|
{
|
||||||
PacketFormatterAdapter = packetFormatterAdapter ?? throw new ArgumentNullException(nameof(packetFormatterAdapter));
|
PacketFormatterAdapter = packetFormatterAdapter ?? throw new ArgumentNullException(nameof(packetFormatterAdapter));
|
||||||
Connection = connection ?? throw new ArgumentNullException(nameof(connection));
|
_connection = connection ?? throw new ArgumentNullException(nameof(connection));
|
||||||
|
|
||||||
if (Connection.Transport != null)
|
if (!(_connection is SocketConnection tcp) || tcp.IsConnected)
|
||||||
{
|
{
|
||||||
_input = Connection.Transport.Input;
|
_input = connection.Transport.Input;
|
||||||
_output = Connection.Transport.Output;
|
_output = connection.Transport.Output;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Endpoint
|
public long BytesReceived { get; private set; }
|
||||||
|
|
||||||
|
public long BytesSent { get; private set; }
|
||||||
|
|
||||||
|
public X509Certificate2 ClientCertificate
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
#if NETCOREAPP3_1
|
// mqtt over tcp
|
||||||
if (Connection?.RemoteEndPoint != null)
|
var tlsFeature = _connection.Features.Get<ITlsConnectionFeature>();
|
||||||
|
if (tlsFeature != null)
|
||||||
{
|
{
|
||||||
return Connection.RemoteEndPoint.ToString();
|
return tlsFeature.ClientCertificate;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
var connection = Http?.HttpContext?.Connection;
|
// mqtt over websocket
|
||||||
if (connection == null)
|
var httpFeature = _connection.Features.Get<IHttpContextFeature>();
|
||||||
|
return httpFeature?.HttpContext?.Connection.ClientCertificate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsSecureConnection
|
||||||
{
|
{
|
||||||
return Connection.ConnectionId;
|
get
|
||||||
|
{
|
||||||
|
// mqtt over tcp
|
||||||
|
var tlsFeature = _connection.Features.Get<ITlsConnectionFeature>();
|
||||||
|
if (tlsFeature != null)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $"{connection.RemoteIpAddress}:{connection.RemotePort}";
|
// mqtt over websocket
|
||||||
|
var httpFeature = _connection.Features.Get<IHttpContextFeature>();
|
||||||
|
if (httpFeature?.HttpContext != null)
|
||||||
|
{
|
||||||
|
return httpFeature.HttpContext.Request.IsHttps;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsSecureConnection => Http?.HttpContext?.Request?.IsHttps ?? false;
|
public EndPoint LocalEndPoint
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
// mqtt over tcp
|
||||||
|
if (_connection.LocalEndPoint != null)
|
||||||
|
{
|
||||||
|
return _connection.LocalEndPoint;
|
||||||
|
}
|
||||||
|
|
||||||
public X509Certificate2 ClientCertificate => Http?.HttpContext?.Connection?.ClientCertificate;
|
// mqtt over websocket
|
||||||
|
var httpFeature = _connection.Features.Get<IHttpConnectionFeature>();
|
||||||
|
if (httpFeature?.LocalIpAddress != null)
|
||||||
|
{
|
||||||
|
return new IPEndPoint(httpFeature.LocalIpAddress, httpFeature.LocalPort);
|
||||||
|
}
|
||||||
|
|
||||||
public ConnectionContext Connection { get; }
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public MqttPacketFormatterAdapter PacketFormatterAdapter { get; }
|
public MqttPacketFormatterAdapter PacketFormatterAdapter { get; }
|
||||||
|
|
||||||
public long BytesSent { get; set; }
|
public EndPoint RemoteEndPoint
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
// mqtt over tcp
|
||||||
|
if (_connection.RemoteEndPoint != null)
|
||||||
|
{
|
||||||
|
return _connection.RemoteEndPoint;
|
||||||
|
}
|
||||||
|
|
||||||
public long BytesReceived { get; set; }
|
// mqtt over websocket
|
||||||
|
var httpFeature = _connection.Features.Get<IHttpConnectionFeature>();
|
||||||
|
if (httpFeature?.RemoteIpAddress != null)
|
||||||
|
{
|
||||||
|
return new IPEndPoint(httpFeature.RemoteIpAddress, httpFeature.RemotePort);
|
||||||
|
}
|
||||||
|
|
||||||
public bool IsReadingPacket { get; private set; }
|
return null;
|
||||||
|
}
|
||||||
IHttpContextFeature Http => Connection.Features.Get<IHttpContextFeature>();
|
}
|
||||||
|
|
||||||
public async Task ConnectAsync(CancellationToken cancellationToken)
|
public async Task ConnectAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (Connection is TcpConnection tcp && !tcp.IsConnected)
|
if (_connection is SocketConnection tcp && !tcp.IsConnected)
|
||||||
{
|
{
|
||||||
await tcp.StartAsync().ConfigureAwait(false);
|
await tcp.StartAsync().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
_input = Connection.Transport.Input;
|
_input = _connection.Transport.Input;
|
||||||
_output = Connection.Transport.Output;
|
_output = _connection.Transport.Output;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task DisconnectAsync(CancellationToken cancellationToken)
|
public Task DisconnectAsync(CancellationToken cancellationToken)
|
||||||
@@ -92,16 +143,19 @@ namespace MQTTnet.AspNetCore
|
|||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_writerLock.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<MqttPacket> ReceivePacketAsync(CancellationToken cancellationToken)
|
public async Task<MqttPacket> ReceivePacketAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var input = Connection.Transport.Input;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
while (!cancellationToken.IsCancellationRequested)
|
while (!cancellationToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
ReadResult readResult;
|
ReadResult readResult;
|
||||||
var readTask = input.ReadAsync(cancellationToken);
|
var readTask = _input.ReadAsync(cancellationToken);
|
||||||
if (readTask.IsCompleted)
|
if (readTask.IsCompleted)
|
||||||
{
|
{
|
||||||
readResult = readTask.Result;
|
readResult = readTask.Result;
|
||||||
@@ -125,11 +179,6 @@ namespace MQTTnet.AspNetCore
|
|||||||
BytesReceived += received;
|
BytesReceived += received;
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
// we did receive something but the message is not yet complete
|
|
||||||
IsReadingPacket = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (readResult.IsCompleted)
|
else if (readResult.IsCompleted)
|
||||||
{
|
{
|
||||||
@@ -141,21 +190,18 @@ namespace MQTTnet.AspNetCore
|
|||||||
// The buffer was sliced up to where it was consumed, so we can just advance to the start.
|
// 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
|
// 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.
|
// before yielding the read again.
|
||||||
input.AdvanceTo(consumed, observed);
|
_input.AdvanceTo(consumed, observed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
// completing the channel makes sure that there is no more data read after a protocol error
|
// completing the channel makes sure that there is no more data read after a protocol error
|
||||||
_input?.Complete(e);
|
_input?.Complete(exception);
|
||||||
_output?.Complete(e);
|
_output?.Complete(exception);
|
||||||
|
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
{
|
|
||||||
IsReadingPacket = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
return null;
|
return null;
|
||||||
@@ -169,25 +215,43 @@ namespace MQTTnet.AspNetCore
|
|||||||
|
|
||||||
public async Task SendPacketAsync(MqttPacket packet, CancellationToken cancellationToken)
|
public async Task SendPacketAsync(MqttPacket packet, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var formatter = PacketFormatterAdapter;
|
|
||||||
|
|
||||||
using (await _writerLock.EnterAsync(cancellationToken).ConfigureAwait(false))
|
using (await _writerLock.EnterAsync(cancellationToken).ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
var buffer = formatter.Encode(packet);
|
try
|
||||||
var msg = buffer.Join().AsMemory();
|
|
||||||
var output = _output;
|
|
||||||
var result = await output.WriteAsync(msg, cancellationToken).ConfigureAwait(false);
|
|
||||||
if (result.IsCompleted)
|
|
||||||
{
|
{
|
||||||
BytesSent += msg.Length;
|
var buffer = PacketFormatterAdapter.Encode(packet);
|
||||||
|
|
||||||
|
if (buffer.Payload.Length == 0)
|
||||||
|
{
|
||||||
|
// zero copy
|
||||||
|
// https://github.com/dotnet/runtime/blob/e31ddfdc4f574b26231233dc10c9a9c402f40590/src/libraries/System.IO.Pipelines/src/System/IO/Pipelines/StreamPipeWriter.cs#L279
|
||||||
|
await _output.WriteAsync(buffer.Packet, cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
WritePacketBuffer(_output, buffer);
|
||||||
|
await _output.FlushAsync(cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BytesSent += buffer.Length;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
PacketFormatterAdapter.Cleanup();
|
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);
|
||||||
|
var offset = buffer.Packet.Count;
|
||||||
|
buffer.Payload.CopyTo(span.Slice(offset));
|
||||||
|
output.Advance(buffer.Length);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,20 +5,22 @@
|
|||||||
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;
|
MqttServerOptions _serverOptions;
|
||||||
|
|
||||||
public Func<IMqttChannelAdapter, Task> ClientHandler { get; set; }
|
public Func<IMqttChannelAdapter, Task> ClientHandler { get; set; }
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public override async Task OnConnectedAsync(ConnectionContext connection)
|
public override async Task OnConnectedAsync(ConnectionContext connection)
|
||||||
{
|
{
|
||||||
// required for websocket transport to work
|
// required for websocket transport to work
|
||||||
@@ -29,15 +31,13 @@ namespace MQTTnet.AspNetCore
|
|||||||
}
|
}
|
||||||
|
|
||||||
var formatter = new MqttPacketFormatterAdapter(new MqttBufferWriter(_serverOptions.WriterBufferSize, _serverOptions.WriterBufferSizeMax));
|
var formatter = new MqttPacketFormatterAdapter(new MqttBufferWriter(_serverOptions.WriterBufferSize, _serverOptions.WriterBufferSizeMax));
|
||||||
using (var adapter = new MqttConnectionContext(formatter, connection))
|
using var adapter = new MqttConnectionContext(formatter, connection);
|
||||||
{
|
|
||||||
var clientHandler = ClientHandler;
|
var clientHandler = ClientHandler;
|
||||||
if (clientHandler != null)
|
if (clientHandler != null)
|
||||||
{
|
{
|
||||||
await clientHandler(adapter).ConfigureAwait(false);
|
await clientHandler(adapter).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public Task StartAsync(MqttServerOptions options, IMqttNetLogger logger)
|
public Task StartAsync(MqttServerOptions options, IMqttNetLogger logger)
|
||||||
{
|
{
|
||||||
@@ -50,9 +50,4 @@ namespace MQTTnet.AspNetCore
|
|||||||
{
|
{
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -2,32 +2,43 @@
|
|||||||
// 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.Collections.Generic;
|
|
||||||
using System.Threading;
|
|
||||||
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(MqttServerOptions options, IEnumerable<IMqttServerAdapter> adapters, IMqttNetLogger logger)
|
|
||||||
: base(options, adapters, logger)
|
public MqttHostedServer(
|
||||||
|
IHostApplicationLifetime hostApplicationLifetime,
|
||||||
|
MqttServerFactory mqttFactory,
|
||||||
|
MqttServerOptions options,
|
||||||
|
IEnumerable<IMqttServerAdapter> adapters,
|
||||||
|
IMqttNetLogger logger) : base(options, adapters, logger)
|
||||||
{
|
{
|
||||||
|
_mqttFactory = mqttFactory ?? throw new ArgumentNullException(nameof(mqttFactory));
|
||||||
|
_hostApplicationLifetime = hostApplicationLifetime;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task StartAsync(CancellationToken cancellationToken)
|
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();
|
||||||
|
|
||||||
|
_hostApplicationLifetime.ApplicationStarted.Register(OnStarted);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task StopAsync(CancellationToken cancellationToken)
|
public Task StopAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
return StopAsync();
|
return StopAsync(_mqttFactory.CreateMqttServerStopOptionsBuilder().Build());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void OnStarted()
|
||||||
|
{
|
||||||
|
_ = StartAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,18 +2,15 @@
|
|||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using 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)
|
||||||
{
|
{
|
||||||
if (request == null) throw new ArgumentNullException(nameof(request));
|
ArgumentNullException.ThrowIfNull(request);
|
||||||
|
|
||||||
string subProtocol = null;
|
string subProtocol = null;
|
||||||
if (request.Headers.TryGetValue("Sec-WebSocket-Protocol", out var requestedSubProtocolValues))
|
if (request.Headers.TryGetValue("Sec-WebSocket-Protocol", out var requestedSubProtocolValues))
|
||||||
@@ -26,12 +23,9 @@ namespace MQTTnet.AspNetCore
|
|||||||
|
|
||||||
public static string SelectSubProtocol(IList<string> requestedSubProtocolValues)
|
public static string SelectSubProtocol(IList<string> requestedSubProtocolValues)
|
||||||
{
|
{
|
||||||
if (requestedSubProtocolValues == null) throw new ArgumentNullException(nameof(requestedSubProtocolValues));
|
ArgumentNullException.ThrowIfNull(requestedSubProtocolValues);
|
||||||
|
|
||||||
// Order the protocols to also match "mqtt", "mqttv-3.1", "mqttv-3.11" etc.
|
// Order the protocols to also match "mqtt", "mqttv-3.1", "mqttv-3.11" etc.
|
||||||
return requestedSubProtocolValues
|
return requestedSubProtocolValues.OrderByDescending(p => p.Length).FirstOrDefault(p => p.ToLowerInvariant().StartsWith("mqtt", StringComparison.InvariantCulture));
|
||||||
.OrderByDescending(p => p.Length)
|
|
||||||
.FirstOrDefault(p => p.ToLower().StartsWith("mqtt"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,24 +2,55 @@
|
|||||||
// 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.Net;
|
||||||
|
using System.Net.WebSockets;
|
||||||
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;
|
||||||
{
|
|
||||||
IMqttNetLogger _logger = new MqttNetNullLogger();
|
|
||||||
|
|
||||||
public Func<IMqttChannelAdapter, Task> ClientHandler { get; set; }
|
public Func<IMqttChannelAdapter, Task> ClientHandler { get; set; }
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task RunWebSocketConnectionAsync(WebSocket webSocket, HttpContext httpContext)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(webSocket);
|
||||||
|
|
||||||
|
var remoteAddress = httpContext.Connection.RemoteIpAddress;
|
||||||
|
var remoteEndPoint = remoteAddress == null ? null : new IPEndPoint(remoteAddress, httpContext.Connection.RemotePort);
|
||||||
|
|
||||||
|
var clientCertificate = await httpContext.Connection.GetClientCertificateAsync().ConfigureAwait(false);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var isSecureConnection = clientCertificate != null;
|
||||||
|
|
||||||
|
var clientHandler = ClientHandler;
|
||||||
|
if (clientHandler != null)
|
||||||
|
{
|
||||||
|
var formatter = new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535));
|
||||||
|
var channel = new MqttWebSocketChannel(webSocket, remoteEndPoint, isSecureConnection, clientCertificate);
|
||||||
|
|
||||||
|
using var channelAdapter = new MqttChannelAdapter(channel, formatter, _logger);
|
||||||
|
await clientHandler(channelAdapter).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
clientCertificate?.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public Task StartAsync(MqttServerOptions options, IMqttNetLogger logger)
|
public Task StartAsync(MqttServerOptions options, IMqttNetLogger logger)
|
||||||
{
|
{
|
||||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||||
@@ -30,38 +61,4 @@ namespace MQTTnet.AspNetCore
|
|||||||
{
|
{
|
||||||
return Task.CompletedTask;
|
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 clientHandler = ClientHandler;
|
|
||||||
if (clientHandler != null)
|
|
||||||
{
|
|
||||||
var formatter = new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535));
|
|
||||||
var channel = new MqttWebSocketChannel(webSocket, endpoint, isSecureConnection, clientCertificate);
|
|
||||||
|
|
||||||
using (var channelAdapter = new MqttChannelAdapter(channel, formatter, null, _logger))
|
|
||||||
{
|
|
||||||
await clientHandler(channelAdapter).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
clientCertificate?.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -2,25 +2,26 @@
|
|||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
|
using System.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,
|
||||||
public static bool TryDecode(this MqttPacketFormatterAdapter formatter,
|
|
||||||
in ReadOnlySequence<byte> input,
|
in ReadOnlySequence<byte> input,
|
||||||
out MqttPacket packet,
|
out MqttPacket packet,
|
||||||
out SequencePosition consumed,
|
out SequencePosition consumed,
|
||||||
out SequencePosition observed,
|
out SequencePosition observed,
|
||||||
out int bytesRead)
|
out int bytesRead)
|
||||||
{
|
{
|
||||||
if (formatter == null) throw new ArgumentNullException(nameof(formatter));
|
ArgumentNullException.ThrowIfNull(formatter);
|
||||||
|
|
||||||
packet = null;
|
packet = null;
|
||||||
consumed = input.Start;
|
consumed = input.Start;
|
||||||
@@ -33,22 +34,22 @@ namespace MQTTnet.AspNetCore
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var fixedHeader = copy.First.Span[0];
|
if (!TryReadBodyLength(ref copy, out var headerLength, out var bodyLength))
|
||||||
if (!TryReadBodyLength(ref copy, out int headerLength, out var bodyLength))
|
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var fixedHeader = copy.First.Span[0];
|
||||||
|
copy = copy.Slice(headerLength);
|
||||||
if (copy.Length < bodyLength)
|
if (copy.Length < bodyLength)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var bodySlice = copy.Slice(0, bodyLength);
|
var bodySlice = copy.Slice(0, bodyLength);
|
||||||
var buffer = bodySlice.GetMemory().ToArray();
|
var bodySegment = GetArraySegment(ref bodySlice);
|
||||||
|
|
||||||
var receivedMqttPacket = new ReceivedMqttPacket(fixedHeader, new ArraySegment<byte>(buffer, 0, buffer.Length), buffer.Length + 2);
|
|
||||||
|
|
||||||
|
var receivedMqttPacket = new ReceivedMqttPacket(fixedHeader, bodySegment, headerLength + bodyLength);
|
||||||
if (formatter.ProtocolVersion == MqttProtocolVersion.Unknown)
|
if (formatter.ProtocolVersion == MqttProtocolVersion.Unknown)
|
||||||
{
|
{
|
||||||
formatter.DetectProtocolVersion(receivedMqttPacket);
|
formatter.DetectProtocolVersion(receivedMqttPacket);
|
||||||
@@ -61,18 +62,43 @@ namespace MQTTnet.AspNetCore
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static ReadOnlyMemory<byte> GetMemory(this in ReadOnlySequence<byte> input)
|
static ArraySegment<byte> GetArraySegment(ref ReadOnlySequence<byte> input)
|
||||||
{
|
{
|
||||||
if (input.IsSingleSegment)
|
if (input.IsSingleSegment && MemoryMarshal.TryGetArray(input.First, out var segment))
|
||||||
{
|
{
|
||||||
return input.First;
|
return segment;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Should be rare
|
// Should be rare
|
||||||
return input.ToArray();
|
var array = input.ToArray();
|
||||||
|
return new ArraySegment<byte>(array);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void ThrowProtocolViolationException(ReadOnlySpan<byte> valueSpan, int index)
|
||||||
|
{
|
||||||
|
throw new MqttProtocolViolationException($"Remaining length is invalid (Data={string.Join(",", valueSpan.Slice(1, index).ToArray())}).");
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool TryReadBodyLength(ref ReadOnlySequence<byte> input, out int headerLength, out int bodyLength)
|
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.
|
// 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 multiplier = 1;
|
||||||
@@ -82,32 +108,27 @@ namespace MQTTnet.AspNetCore
|
|||||||
headerLength = 0;
|
headerLength = 0;
|
||||||
bodyLength = 0;
|
bodyLength = 0;
|
||||||
|
|
||||||
var temp = input.Slice(0, Math.Min(5, input.Length)).GetMemory().Span;
|
|
||||||
|
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
if (index == temp.Length)
|
if (index == span.Length)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
encodedByte = temp[index];
|
encodedByte = span[index];
|
||||||
index++;
|
index++;
|
||||||
|
|
||||||
value += (byte)(encodedByte & 127) * multiplier;
|
value += (byte)(encodedByte & 127) * multiplier;
|
||||||
if (multiplier > 128 * 128 * 128)
|
if (multiplier > 128 * 128 * 128)
|
||||||
{
|
{
|
||||||
throw new MqttProtocolViolationException($"Remaining length is invalid (Data={string.Join(",", temp.Slice(1, index).ToArray())}).");
|
ThrowProtocolViolationException(span, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
multiplier *= 128;
|
multiplier *= 128;
|
||||||
} while ((encodedByte & 128) != 0);
|
} while ((encodedByte & 128) != 0);
|
||||||
|
|
||||||
input = input.Slice(index);
|
|
||||||
|
|
||||||
headerLength = index;
|
headerLength = index;
|
||||||
bodyLength = value;
|
bodyLength = value;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -2,59 +2,58 @@
|
|||||||
// 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 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 AddMqttServer(this IServiceCollection serviceCollection, Action<MqttServerOptionsBuilder> configure = null)
|
|
||||||
{
|
|
||||||
if (serviceCollection is null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(serviceCollection));
|
|
||||||
}
|
|
||||||
|
|
||||||
serviceCollection.AddMqttConnectionHandler();
|
|
||||||
serviceCollection.AddHostedMqttServer(configure);
|
|
||||||
|
|
||||||
return serviceCollection;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IServiceCollection AddHostedMqttServer(this IServiceCollection services, MqttServerOptions options)
|
public static IServiceCollection AddHostedMqttServer(this IServiceCollection services, MqttServerOptions options)
|
||||||
{
|
{
|
||||||
if (options == null) throw new ArgumentNullException(nameof(options));
|
ArgumentNullException.ThrowIfNull(services);
|
||||||
|
ArgumentNullException.ThrowIfNull(options);
|
||||||
|
|
||||||
services.AddSingleton(options);
|
services.AddSingleton(options);
|
||||||
|
|
||||||
services.AddHostedMqttServer();
|
services.AddHostedMqttServer();
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IServiceCollection AddHostedMqttServer(this IServiceCollection services, Action<MqttServerOptionsBuilder> configure = null)
|
public static IServiceCollection AddHostedMqttServer(this IServiceCollection services, Action<MqttServerOptionsBuilder> configure)
|
||||||
{
|
|
||||||
services.AddSingleton(s =>
|
|
||||||
{
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(services);
|
||||||
|
|
||||||
var serverOptionsBuilder = new MqttServerOptionsBuilder();
|
var serverOptionsBuilder = new MqttServerOptionsBuilder();
|
||||||
|
|
||||||
configure?.Invoke(serverOptionsBuilder);
|
configure?.Invoke(serverOptionsBuilder);
|
||||||
return serverOptionsBuilder.Build();
|
|
||||||
});
|
|
||||||
|
|
||||||
services.AddHostedMqttServer();
|
var options = serverOptionsBuilder.Build();
|
||||||
|
|
||||||
return services;
|
return AddHostedMqttServer(services, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddHostedMqttServer(this IServiceCollection services)
|
||||||
|
{
|
||||||
|
// The user may have these services already registered.
|
||||||
|
services.TryAddSingleton<IMqttNetLogger>(MqttNetNullLogger.Instance);
|
||||||
|
services.TryAddSingleton(new MqttServerFactory());
|
||||||
|
|
||||||
|
services.AddSingleton<MqttHostedServer>();
|
||||||
|
services.AddSingleton<IHostedService>(s => s.GetService<MqttHostedServer>());
|
||||||
|
services.AddSingleton<MqttServer>(s => s.GetService<MqttHostedServer>());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IServiceCollection AddHostedMqttServerWithServices(this IServiceCollection services, Action<AspNetMqttServerOptionsBuilder> configure)
|
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);
|
||||||
@@ -66,14 +65,35 @@ namespace MQTTnet.AspNetCore
|
|||||||
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>());
|
public static void AddMqttLogger(this IServiceCollection services, IMqttNetLogger logger)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(services);
|
||||||
|
|
||||||
|
services.AddSingleton(logger);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IServiceCollection AddMqttServer(this IServiceCollection serviceCollection, Action<MqttServerOptionsBuilder> configure = null)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(serviceCollection);
|
||||||
|
|
||||||
|
serviceCollection.AddMqttConnectionHandler();
|
||||||
|
serviceCollection.AddHostedMqttServer(configure);
|
||||||
|
|
||||||
|
return serviceCollection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IServiceCollection AddMqttTcpServerAdapter(this IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddSingleton<MqttTcpServerAdapter>();
|
||||||
|
services.AddSingleton<IMqttServerAdapter>(s => s.GetService<MqttTcpServerAdapter>());
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
@@ -85,21 +105,4 @@ namespace MQTTnet.AspNetCore
|
|||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IServiceCollection AddMqttTcpServerAdapter(this IServiceCollection services)
|
|
||||||
{
|
|
||||||
services.AddSingleton<MqttTcpServerAdapter>();
|
|
||||||
services.AddSingleton<IMqttServerAdapter>(s => s.GetService<MqttTcpServerAdapter>());
|
|
||||||
|
|
||||||
return services;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IServiceCollection AddMqttConnectionHandler(this IServiceCollection services)
|
|
||||||
{
|
|
||||||
services.AddSingleton<MqttConnectionHandler>();
|
|
||||||
services.AddSingleton<IMqttServerAdapter>(s => s.GetService<MqttConnectionHandler>());
|
|
||||||
|
|
||||||
return services;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO.Pipelines;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace MQTTnet.AspNetCore;
|
||||||
|
|
||||||
|
public class SocketAwaitable(PipeScheduler ioScheduler) : ICriticalNotifyCompletion
|
||||||
|
{
|
||||||
|
static readonly Action CallbackCompleted = () =>
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
|
int _bytesTransferred;
|
||||||
|
|
||||||
|
Action _callback;
|
||||||
|
SocketError _error;
|
||||||
|
|
||||||
|
public bool IsCompleted => ReferenceEquals(_callback, CallbackCompleted);
|
||||||
|
|
||||||
|
public void Complete(int bytesTransferred, SocketError socketError)
|
||||||
|
{
|
||||||
|
_error = socketError;
|
||||||
|
_bytesTransferred = bytesTransferred;
|
||||||
|
var continuation = Interlocked.Exchange(ref _callback, CallbackCompleted);
|
||||||
|
|
||||||
|
if (continuation != null)
|
||||||
|
{
|
||||||
|
ioScheduler.Schedule(state => ((Action)state)(), continuation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public SocketAwaitable GetAwaiter()
|
||||||
|
{
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetResult()
|
||||||
|
{
|
||||||
|
Debug.Assert(ReferenceEquals(_callback, CallbackCompleted));
|
||||||
|
|
||||||
|
_callback = null;
|
||||||
|
|
||||||
|
if (_error != SocketError.Success)
|
||||||
|
{
|
||||||
|
throw new SocketException((int)_error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _bytesTransferred;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnCompleted(Action continuation)
|
||||||
|
{
|
||||||
|
if (ReferenceEquals(_callback, CallbackCompleted) || ReferenceEquals(Interlocked.CompareExchange(ref _callback, continuation, null), CallbackCompleted))
|
||||||
|
{
|
||||||
|
Task.Run(continuation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UnsafeOnCompleted(Action continuation)
|
||||||
|
{
|
||||||
|
OnCompleted(continuation);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,263 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System.IO.Pipelines;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
using Microsoft.AspNetCore.Connections;
|
||||||
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
|
using MQTTnet.Exceptions;
|
||||||
|
|
||||||
|
namespace MQTTnet.AspNetCore;
|
||||||
|
|
||||||
|
public sealed class SocketConnection : ConnectionContext
|
||||||
|
{
|
||||||
|
readonly EndPoint _endPoint;
|
||||||
|
|
||||||
|
volatile bool _aborted;
|
||||||
|
IDuplexPipe _application;
|
||||||
|
SocketReceiver _receiver;
|
||||||
|
SocketSender _sender;
|
||||||
|
Socket _socket;
|
||||||
|
|
||||||
|
public SocketConnection(EndPoint endPoint)
|
||||||
|
{
|
||||||
|
_endPoint = endPoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SocketConnection(Socket socket)
|
||||||
|
{
|
||||||
|
_socket = socket;
|
||||||
|
_endPoint = socket.RemoteEndPoint;
|
||||||
|
|
||||||
|
_sender = new SocketSender(_socket, PipeScheduler.ThreadPool);
|
||||||
|
_receiver = new SocketReceiver(_socket, PipeScheduler.ThreadPool);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ConnectionId { get; set; }
|
||||||
|
|
||||||
|
public override IFeatureCollection Features { get; }
|
||||||
|
|
||||||
|
public bool IsConnected { get; private set; }
|
||||||
|
|
||||||
|
public override IDictionary<object, object> Items { get; set; }
|
||||||
|
|
||||||
|
public override IDuplexPipe Transport { get; set; }
|
||||||
|
|
||||||
|
public override ValueTask DisposeAsync()
|
||||||
|
{
|
||||||
|
IsConnected = false;
|
||||||
|
|
||||||
|
Transport?.Output.Complete();
|
||||||
|
Transport?.Input.Complete();
|
||||||
|
|
||||||
|
_socket?.Dispose();
|
||||||
|
|
||||||
|
return base.DisposeAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task StartAsync()
|
||||||
|
{
|
||||||
|
if (_socket == null)
|
||||||
|
{
|
||||||
|
_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
|
||||||
|
_sender = new SocketSender(_socket, PipeScheduler.ThreadPool);
|
||||||
|
_receiver = new SocketReceiver(_socket, PipeScheduler.ThreadPool);
|
||||||
|
await _socket.ConnectAsync(_endPoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
var pair = DuplexPipe.CreateConnectionPair(PipeOptions.Default, PipeOptions.Default);
|
||||||
|
|
||||||
|
Transport = pair.Transport;
|
||||||
|
_application = pair.Application;
|
||||||
|
|
||||||
|
_ = ExecuteAsync();
|
||||||
|
|
||||||
|
IsConnected = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static MqttCommunicationException ConnectionAborted()
|
||||||
|
{
|
||||||
|
return new MqttCommunicationException("Connection Aborted");
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task DoReceive()
|
||||||
|
{
|
||||||
|
Exception error = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await ProcessReceives();
|
||||||
|
}
|
||||||
|
catch (SocketException ex) when (ex.SocketErrorCode == SocketError.ConnectionReset)
|
||||||
|
{
|
||||||
|
error = new MqttCommunicationException(ex);
|
||||||
|
}
|
||||||
|
catch (SocketException ex) when (ex.SocketErrorCode is SocketError.OperationAborted or SocketError.ConnectionAborted or SocketError.Interrupted or SocketError.InvalidArgument)
|
||||||
|
{
|
||||||
|
if (!_aborted)
|
||||||
|
{
|
||||||
|
// Calling Dispose after ReceiveAsync can cause an "InvalidArgument" error on *nix.
|
||||||
|
error = ConnectionAborted();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (ObjectDisposedException)
|
||||||
|
{
|
||||||
|
if (!_aborted)
|
||||||
|
{
|
||||||
|
error = ConnectionAborted();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException ex)
|
||||||
|
{
|
||||||
|
error = ex;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
error = new IOException(ex.Message, ex);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (_aborted)
|
||||||
|
{
|
||||||
|
error ??= ConnectionAborted();
|
||||||
|
}
|
||||||
|
|
||||||
|
await _application.Output.CompleteAsync(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task<Exception> DoSend()
|
||||||
|
{
|
||||||
|
Exception error = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await ProcessSends();
|
||||||
|
}
|
||||||
|
catch (SocketException ex) when (ex.SocketErrorCode == SocketError.OperationAborted)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
catch (ObjectDisposedException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
catch (IOException ex)
|
||||||
|
{
|
||||||
|
error = ex;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
error = new IOException(ex.Message, ex);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_aborted = true;
|
||||||
|
_socket.Shutdown(SocketShutdown.Both);
|
||||||
|
}
|
||||||
|
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task ExecuteAsync()
|
||||||
|
{
|
||||||
|
Exception sendError = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Spawn send and receive logic
|
||||||
|
var receiveTask = DoReceive();
|
||||||
|
var sendTask = DoSend();
|
||||||
|
|
||||||
|
// If the sending task completes then close the receive
|
||||||
|
// We don't need to do this in the other direction because the kestrel
|
||||||
|
// will trigger the output closing once the input is complete.
|
||||||
|
if (await Task.WhenAny(receiveTask, sendTask).ConfigureAwait(false) == sendTask)
|
||||||
|
{
|
||||||
|
// Tell the reader it's being aborted
|
||||||
|
_socket.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now wait for both to complete
|
||||||
|
await receiveTask;
|
||||||
|
sendError = await sendTask;
|
||||||
|
|
||||||
|
// Dispose the socket(should noop if already called)
|
||||||
|
_socket.Dispose();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Unexpected exception in {nameof(SocketConnection)}.{nameof(StartAsync)}: " + ex);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
// Complete the output after disposing the socket
|
||||||
|
await _application.Input.CompleteAsync(sendError).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task ProcessReceives()
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
// Ensure we have some reasonable amount of buffer space
|
||||||
|
var buffer = _application.Output.GetMemory();
|
||||||
|
|
||||||
|
var bytesReceived = await _receiver.ReceiveAsync(buffer);
|
||||||
|
|
||||||
|
if (bytesReceived == 0)
|
||||||
|
{
|
||||||
|
// FIN
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
_application.Output.Advance(bytesReceived);
|
||||||
|
|
||||||
|
var flushTask = _application.Output.FlushAsync();
|
||||||
|
|
||||||
|
FlushResult result;
|
||||||
|
if (!flushTask.IsCompleted)
|
||||||
|
{
|
||||||
|
result = await flushTask;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = flushTask.GetAwaiter().GetResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.IsCompleted)
|
||||||
|
{
|
||||||
|
// Pipe consumer is shut down, do we stop writing
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task ProcessSends()
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
// Wait for data to write from the pipe producer
|
||||||
|
var result = await _application.Input.ReadAsync();
|
||||||
|
var buffer = result.Buffer;
|
||||||
|
|
||||||
|
if (result.IsCanceled)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var end = buffer.End;
|
||||||
|
var isCompleted = result.IsCompleted;
|
||||||
|
if (!buffer.IsEmpty)
|
||||||
|
{
|
||||||
|
await _sender.SendAsync(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
_application.Input.AdvanceTo(end);
|
||||||
|
|
||||||
|
if (isCompleted)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System.IO.Pipelines;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
|
||||||
|
namespace MQTTnet.AspNetCore;
|
||||||
|
|
||||||
|
public sealed class SocketReceiver : IDisposable
|
||||||
|
{
|
||||||
|
readonly SocketAwaitable _awaitable;
|
||||||
|
readonly SocketAsyncEventArgs _eventArgs = new();
|
||||||
|
readonly Socket _socket;
|
||||||
|
|
||||||
|
public SocketReceiver(Socket socket, PipeScheduler scheduler)
|
||||||
|
{
|
||||||
|
_socket = socket;
|
||||||
|
_awaitable = new SocketAwaitable(scheduler);
|
||||||
|
_eventArgs.UserToken = _awaitable;
|
||||||
|
_eventArgs.Completed += (_, e) => ((SocketAwaitable)e.UserToken).Complete(e.BytesTransferred, e.SocketError);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SocketAwaitable ReceiveAsync(Memory<byte> buffer)
|
||||||
|
{
|
||||||
|
_eventArgs.SetBuffer(buffer);
|
||||||
|
|
||||||
|
if (!_socket.ReceiveAsync(_eventArgs))
|
||||||
|
{
|
||||||
|
_awaitable.Complete(_eventArgs.BytesTransferred, _eventArgs.SocketError);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _awaitable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_eventArgs.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System.Buffers;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO.Pipelines;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace MQTTnet.AspNetCore;
|
||||||
|
|
||||||
|
public sealed class SocketSender : IDisposable
|
||||||
|
{
|
||||||
|
readonly SocketAwaitable _awaitable;
|
||||||
|
readonly SocketAsyncEventArgs _eventArgs = new();
|
||||||
|
readonly Socket _socket;
|
||||||
|
|
||||||
|
List<ArraySegment<byte>> _bufferList;
|
||||||
|
|
||||||
|
public SocketSender(Socket socket, PipeScheduler scheduler)
|
||||||
|
{
|
||||||
|
_socket = socket;
|
||||||
|
_awaitable = new SocketAwaitable(scheduler);
|
||||||
|
_eventArgs.UserToken = _awaitable;
|
||||||
|
_eventArgs.Completed += (_, e) => ((SocketAwaitable)e.UserToken).Complete(e.BytesTransferred, e.SocketError);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_eventArgs.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public SocketAwaitable SendAsync(in ReadOnlySequence<byte> buffers)
|
||||||
|
{
|
||||||
|
if (buffers.IsSingleSegment)
|
||||||
|
{
|
||||||
|
return SendAsync(buffers.First);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_eventArgs.MemoryBuffer.Equals(Memory<byte>.Empty))
|
||||||
|
{
|
||||||
|
_eventArgs.SetBuffer(null, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
_eventArgs.BufferList = GetBufferList(buffers);
|
||||||
|
|
||||||
|
if (!_socket.SendAsync(_eventArgs))
|
||||||
|
{
|
||||||
|
_awaitable.Complete(_eventArgs.BytesTransferred, _eventArgs.SocketError);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _awaitable;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<ArraySegment<byte>> GetBufferList(in ReadOnlySequence<byte> buffer)
|
||||||
|
{
|
||||||
|
Debug.Assert(!buffer.IsEmpty);
|
||||||
|
Debug.Assert(!buffer.IsSingleSegment);
|
||||||
|
|
||||||
|
if (_bufferList == null)
|
||||||
|
{
|
||||||
|
_bufferList = new List<ArraySegment<byte>>();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Buffers are pooled, so it's OK to root them until the next multi-buffer write.
|
||||||
|
_bufferList.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var b in buffer)
|
||||||
|
{
|
||||||
|
_bufferList.Add(b.GetArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
return _bufferList;
|
||||||
|
}
|
||||||
|
|
||||||
|
SocketAwaitable SendAsync(ReadOnlyMemory<byte> memory)
|
||||||
|
{
|
||||||
|
// The BufferList getter is much less expensive then the setter.
|
||||||
|
if (_eventArgs.BufferList != null)
|
||||||
|
{
|
||||||
|
_eventArgs.BufferList = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_eventArgs.SetBuffer(MemoryMarshal.AsMemory(memory));
|
||||||
|
|
||||||
|
if (!_socket.SendAsync(_eventArgs))
|
||||||
|
{
|
||||||
|
_awaitable.Complete(_eventArgs.BytesTransferred, _eventArgs.SocketError);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _awaitable;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
[assembly: DoNotParallelize]
|
||||||
@@ -2,19 +2,16 @@
|
|||||||
// 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;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using BenchmarkDotNet.Jobs;
|
using BenchmarkDotNet.Jobs;
|
||||||
using MQTTnet.Internal;
|
using MQTTnet.Internal;
|
||||||
|
|
||||||
namespace MQTTnet.Benchmarks
|
namespace MQTTnet.Benchmarks;
|
||||||
|
|
||||||
|
[SimpleJob(RuntimeMoniker.Net60)]
|
||||||
|
[MemoryDiagnoser]
|
||||||
|
public class AsyncLockBenchmark : BaseBenchmark
|
||||||
{
|
{
|
||||||
[SimpleJob(RuntimeMoniker.Net60)]
|
|
||||||
[MemoryDiagnoser]
|
|
||||||
public class AsyncLockBenchmark : BaseBenchmark
|
|
||||||
{
|
|
||||||
[Benchmark]
|
[Benchmark]
|
||||||
public async Task Synchronize_100_Tasks()
|
public async Task Synchronize_100_Tasks()
|
||||||
{
|
{
|
||||||
@@ -43,7 +40,7 @@ namespace MQTTnet.Benchmarks
|
|||||||
|
|
||||||
if (globalI != tasksCount)
|
if (globalI != tasksCount)
|
||||||
{
|
{
|
||||||
throw new Exception($"Code is broken ({globalI})!");
|
throw new InvalidOperationException($"Code is broken ({globalI})!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,8 +49,7 @@ namespace MQTTnet.Benchmarks
|
|||||||
{
|
{
|
||||||
var asyncLock = new AsyncLock();
|
var asyncLock = new AsyncLock();
|
||||||
|
|
||||||
using (var cancellationToken = new CancellationTokenSource())
|
using var cancellationToken = new CancellationTokenSource();
|
||||||
{
|
|
||||||
for (var i = 0; i < 100000; i++)
|
for (var i = 0; i < 100000; i++)
|
||||||
{
|
{
|
||||||
using (await asyncLock.EnterAsync(cancellationToken.Token).ConfigureAwait(false))
|
using (await asyncLock.EnterAsync(cancellationToken.Token).ConfigureAwait(false))
|
||||||
@@ -61,6 +57,4 @@ namespace MQTTnet.Benchmarks
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
namespace MQTTnet.Benchmarks
|
// 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;
|
||||||
|
|
||||||
|
public abstract class BaseBenchmark
|
||||||
{
|
{
|
||||||
public abstract class BaseBenchmark
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -4,52 +4,20 @@
|
|||||||
|
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using MQTTnet.Adapter;
|
using MQTTnet.Adapter;
|
||||||
using MQTTnet.Internal;
|
using MQTTnet.Diagnostics.Logger;
|
||||||
using MQTTnet.Packets;
|
|
||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using System.Threading;
|
|
||||||
using MQTTnet.Diagnostics;
|
|
||||||
using MQTTnet.Formatter;
|
using MQTTnet.Formatter;
|
||||||
|
using MQTTnet.Packets;
|
||||||
using MQTTnet.Tests.Mockups;
|
using MQTTnet.Tests.Mockups;
|
||||||
|
|
||||||
namespace MQTTnet.Benchmarks
|
namespace MQTTnet.Benchmarks;
|
||||||
|
|
||||||
|
[MemoryDiagnoser]
|
||||||
|
public sealed class ChannelAdapterBenchmark : BaseBenchmark, IDisposable
|
||||||
{
|
{
|
||||||
[MemoryDiagnoser]
|
|
||||||
public sealed class ChannelAdapterBenchmark
|
|
||||||
{
|
|
||||||
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 +45,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();
|
||||||
@@ -87,5 +83,10 @@ namespace MQTTnet.Benchmarks
|
|||||||
|
|
||||||
return buffer.ToArray();
|
return buffer.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_channelAdapter?.Dispose();
|
||||||
|
_stream?.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,17 +2,18 @@
|
|||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System.Diagnostics;
|
||||||
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)]
|
||||||
|
[RPlotExporter]
|
||||||
|
[MemoryDiagnoser]
|
||||||
|
public class LoggerBenchmark : BaseBenchmark
|
||||||
{
|
{
|
||||||
[SimpleJob(RuntimeMoniker.Net60)]
|
|
||||||
[RPlotExporter]
|
|
||||||
[MemoryDiagnoser]
|
|
||||||
public class LoggerBenchmark
|
|
||||||
{
|
|
||||||
MqttNetNullLogger _nullLogger;
|
MqttNetNullLogger _nullLogger;
|
||||||
MqttNetSourceLogger _sourceNullLogger;
|
MqttNetSourceLogger _sourceNullLogger;
|
||||||
|
|
||||||
@@ -42,7 +43,7 @@ namespace MQTTnet.Benchmarks
|
|||||||
{
|
{
|
||||||
if (_useHandler)
|
if (_useHandler)
|
||||||
{
|
{
|
||||||
eventArgs.LogMessage.ToString();
|
Debug.WriteLine(eventArgs.LogMessage.ToString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,5 +88,4 @@ namespace MQTTnet.Benchmarks
|
|||||||
_sourceNullLogger.Verbose("test log message {0}", "parameter");
|
_sourceNullLogger.Verbose("test log message {0}", "parameter");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,35 +1,22 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<DebugType>Full</DebugType>
|
<DebugType>Full</DebugType>
|
||||||
<TargetFrameworks>net5.0;net6.0</TargetFrameworks>
|
|
||||||
<LangVersion>7.2</LangVersion>
|
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
<EnableNETAnalyzers>false</EnableNETAnalyzers>
|
<NoWarn>$(NoWarn);CS8892;CA1707;CA1051;CA1822</NoWarn>
|
||||||
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
|
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
||||||
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="BenchmarkDotNet" Version="0.13.2" />
|
<PackageReference Include="BenchmarkDotNet" Version="0.15.4" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore" Version="2.2.0" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup Condition="'$(TargetFramework)' == 'net5.0'">
|
|
||||||
<PackageReference Include="System.IO.Pipelines" Version="5.0.0" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup Condition="'$(TargetFramework)' == 'net6.0'">
|
|
||||||
<PackageReference Include="System.IO.Pipelines" Version="5.0.0" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<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,39 @@
|
|||||||
|
using BenchmarkDotNet.Attributes;
|
||||||
|
using BenchmarkDotNet.Jobs;
|
||||||
|
|
||||||
|
|
||||||
|
namespace MQTTnet.Benchmarks;
|
||||||
|
|
||||||
|
[SimpleJob(RuntimeMoniker.Net60)]
|
||||||
|
[RPlotExporter, RankColumn]
|
||||||
|
[MemoryDiagnoser]
|
||||||
|
public class MemoryCopyBenchmark
|
||||||
|
{
|
||||||
|
const int MaxLength = 1024 * 8;
|
||||||
|
|
||||||
|
byte[] _source;
|
||||||
|
byte[] _target;
|
||||||
|
|
||||||
|
[Params(64 - 1, 128 - 1, 256 - 1, 512 - 1, 1024 - 1, 2048 - 1, 5096 - 1)]
|
||||||
|
public int Length { get; set; }
|
||||||
|
|
||||||
|
[GlobalSetup]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
_source = new byte[MaxLength];
|
||||||
|
_target = new byte[MaxLength];
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark(Baseline = true)]
|
||||||
|
public void Array_Copy()
|
||||||
|
{
|
||||||
|
Array.Copy(_source, 0, _target, 0, Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public void Memory_Copy()
|
||||||
|
{
|
||||||
|
Internal.MqttMemoryHelper.Copy(_source, 0, _target, 0, Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,170 +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 BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using BenchmarkDotNet.Jobs;
|
using MQTTnet.Packets;
|
||||||
using MQTTnet.Client;
|
|
||||||
using MQTTnet.Server;
|
using MQTTnet.Server;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
|
namespace MQTTnet.Benchmarks;
|
||||||
|
|
||||||
namespace MQTTnet.Benchmarks
|
[MemoryDiagnoser]
|
||||||
|
public sealed class MessageDeliveryBenchmark : BaseBenchmark, IDisposable
|
||||||
{
|
{
|
||||||
/// <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
|
|
||||||
/// </summary>
|
|
||||||
[MemoryDiagnoser]
|
|
||||||
public class MessageDeliveryBenchmark
|
|
||||||
{
|
|
||||||
List<MqttApplicationMessage> _topicPublishMessages;
|
|
||||||
|
|
||||||
[Params(1, 5)]
|
|
||||||
public int NumTopicsPerPublisher;
|
|
||||||
|
|
||||||
[Params(1000, 10000)]
|
|
||||||
public int NumPublishers;
|
|
||||||
|
|
||||||
[Params(10)]
|
|
||||||
public int NumSubscribers;
|
|
||||||
|
|
||||||
[Params(5, 10, 20, 50)]
|
|
||||||
public int NumSubscribedTopicsPerSubscriber;
|
|
||||||
|
|
||||||
object _lockMsgCount;
|
|
||||||
int _messagesReceivedCount;
|
|
||||||
int _messagesExpectedCount;
|
|
||||||
CancellationTokenSource _cancellationTokenSource;
|
CancellationTokenSource _cancellationTokenSource;
|
||||||
|
|
||||||
|
object _lockMsgCount;
|
||||||
|
int _messagesExpectedCount;
|
||||||
|
int _messagesReceivedCount;
|
||||||
|
Dictionary<string, IMqttClient> _mqttPublisherClientsByPublisherName;
|
||||||
MqttServer _mqttServer;
|
MqttServer _mqttServer;
|
||||||
List<IMqttClient> _mqttSubscriberClients;
|
List<IMqttClient> _mqttSubscriberClients;
|
||||||
Dictionary<string, IMqttClient> _mqttPublisherClientsByPublisherName;
|
|
||||||
|
|
||||||
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;
|
||||||
Dictionary<string, string> _publisherByTopic;
|
Dictionary<string, string> _publisherByTopic;
|
||||||
List<string> _allSubscribedTopics; // Keep track of the subset of topics that are subscribed
|
Dictionary<string, List<string>> _topicsByPublisher;
|
||||||
|
|
||||||
|
[GlobalCleanup]
|
||||||
[GlobalSetup]
|
public void Cleanup()
|
||||||
public void Setup()
|
|
||||||
{
|
{
|
||||||
_lockMsgCount = new object();
|
foreach (var mp in _mqttPublisherClientsByPublisherName)
|
||||||
|
|
||||||
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()
|
var mqttPublisherClient = mp.Value;
|
||||||
.WithTopic(topic)
|
mqttPublisherClient.DisconnectAsync().GetAwaiter().GetResult();
|
||||||
.WithPayload(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 })
|
mqttPublisherClient.Dispose();
|
||||||
.Build();
|
|
||||||
_topicPublishMessages.Add(message);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create server
|
_mqttPublisherClientsByPublisherName.Clear();
|
||||||
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)
|
foreach (var mqttSubscriber in _mqttSubscriberClients)
|
||||||
{
|
{
|
||||||
for (var i = 0; i < NumSubscribedTopicsPerSubscriber; ++i, topicIndex += topicIndexStep)
|
mqttSubscriber.DisconnectAsync().GetAwaiter().GetResult();
|
||||||
{
|
mqttSubscriber.Dispose();
|
||||||
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();
|
_mqttSubscriberClients.Clear();
|
||||||
|
|
||||||
|
_mqttServer.StopAsync().GetAwaiter().GetResult();
|
||||||
|
_mqttServer.Dispose();
|
||||||
|
_mqttServer = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Publish messages and wait for messages sent to subscribers
|
|
||||||
/// </summary>
|
|
||||||
[Benchmark]
|
[Benchmark]
|
||||||
public void DeliverMessages()
|
public void DeliverMessages()
|
||||||
{
|
{
|
||||||
// There should be one message received per publish for each subscribed topic
|
// There should be one message received per publish for each subscribed topic
|
||||||
_messagesExpectedCount = NumSubscribedTopicsPerSubscriber * NumSubscribers;
|
_messagesExpectedCount = _numSubscribedTopicsPerSubscriber * _numSubscribers;
|
||||||
|
|
||||||
// Loop for a while and exchange messages
|
// Loop for a while and exchange messages
|
||||||
|
|
||||||
@@ -175,15 +71,10 @@ namespace MQTTnet.Benchmarks
|
|||||||
// same payload for all messages
|
// same payload for all messages
|
||||||
var payload = new byte[] { 1, 2, 3, 4 };
|
var payload = new byte[] { 1, 2, 3, 4 };
|
||||||
|
|
||||||
var tasks = new List<Task>();
|
|
||||||
|
|
||||||
// publish a message for each subscribed topic
|
// publish a message for each subscribed topic
|
||||||
foreach (var topic in _allSubscribedTopics)
|
foreach (var topic in _allSubscribedTopics)
|
||||||
{
|
{
|
||||||
var message = new MqttApplicationMessageBuilder()
|
var message = new MqttApplicationMessageBuilder().WithTopic(topic).WithPayload(payload).Build();
|
||||||
.WithTopic(topic)
|
|
||||||
.WithPayload(payload)
|
|
||||||
.Build();
|
|
||||||
// pick the correct publisher
|
// pick the correct publisher
|
||||||
var publisherName = _publisherByTopic[topic];
|
var publisherName = _publisherByTopic[topic];
|
||||||
var publisherClient = _mqttPublisherClientsByPublisherName[publisherName];
|
var publisherClient = _mqttPublisherClientsByPublisherName[publisherName];
|
||||||
@@ -197,38 +88,109 @@ namespace MQTTnet.Benchmarks
|
|||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
// Ignore all errors.
|
||||||
}
|
}
|
||||||
|
|
||||||
_cancellationTokenSource.Dispose();
|
_cancellationTokenSource.Dispose();
|
||||||
|
|
||||||
if (_messagesReceivedCount < _messagesExpectedCount)
|
if (_messagesReceivedCount < _messagesExpectedCount)
|
||||||
{
|
{
|
||||||
throw new Exception(string.Format("Messages Received Count mismatch, expected {0}, received {1}", _messagesExpectedCount, _messagesReceivedCount));
|
throw new InvalidOperationException($"Messages Received Count mismatch, expected {_messagesExpectedCount}, received {_messagesReceivedCount}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[GlobalCleanup]
|
public void Dispose()
|
||||||
public void Cleanup()
|
|
||||||
{
|
{
|
||||||
foreach (var mp in _mqttPublisherClientsByPublisherName)
|
_cancellationTokenSource?.Dispose();
|
||||||
{
|
_mqttServer?.Dispose();
|
||||||
var mqttPublisherClient = mp.Value;
|
|
||||||
mqttPublisherClient.DisconnectAsync().GetAwaiter().GetResult();
|
|
||||||
mqttPublisherClient.Dispose();
|
|
||||||
}
|
}
|
||||||
_mqttPublisherClientsByPublisherName.Clear();
|
|
||||||
|
|
||||||
|
[GlobalSetup]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
_lockMsgCount = new object();
|
||||||
|
|
||||||
|
TopicGenerator.Generate(_numPublishers, _numTopicsPerPublisher, out _topicsByPublisher, out _, out _);
|
||||||
|
|
||||||
|
// Create server
|
||||||
|
var serverFactory = new MqttServerFactory();
|
||||||
|
var clientFactory = new MqttClientFactory();
|
||||||
|
var serverOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
|
||||||
|
_mqttServer = serverFactory.CreateMqttServer(serverOptions);
|
||||||
|
_mqttServer.StartAsync().GetAwaiter().GetResult();
|
||||||
|
|
||||||
|
// Create publisher clients
|
||||||
|
_mqttPublisherClientsByPublisherName = [];
|
||||||
|
foreach (var pt in _topicsByPublisher)
|
||||||
|
{
|
||||||
|
var publisherName = pt.Key;
|
||||||
|
var mqttClient = clientFactory.CreateMqttClient();
|
||||||
|
var publisherOptions = new MqttClientOptionsBuilder().WithTcpServer("localhost").WithClientId(publisherName).WithKeepAlivePeriod(TimeSpan.FromSeconds(30)).Build();
|
||||||
|
mqttClient.ConnectAsync(publisherOptions).GetAwaiter().GetResult();
|
||||||
|
_mqttPublisherClientsByPublisherName.Add(publisherName, mqttClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create subscriber clients
|
||||||
|
_mqttSubscriberClients = new List<IMqttClient>();
|
||||||
|
for (var i = 0; i < _numSubscribers; i++)
|
||||||
|
{
|
||||||
|
var mqttSubscriberClient = clientFactory.CreateMqttClient();
|
||||||
|
_mqttSubscriberClients.Add(mqttSubscriberClient);
|
||||||
|
|
||||||
|
var subscriberOptions = new MqttClientOptionsBuilder().WithTcpServer("localhost").WithClientId("subscriber" + i).Build();
|
||||||
|
mqttSubscriberClient.ApplicationMessageReceivedAsync += _ =>
|
||||||
|
{
|
||||||
|
// count messages and signal cancellation when expected message count is reached
|
||||||
|
lock (_lockMsgCount)
|
||||||
|
{
|
||||||
|
++_messagesReceivedCount;
|
||||||
|
if (_messagesReceivedCount == _messagesExpectedCount)
|
||||||
|
{
|
||||||
|
_cancellationTokenSource.Cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
};
|
||||||
|
|
||||||
|
mqttSubscriberClient.ConnectAsync(subscriberOptions).GetAwaiter().GetResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var allTopics = new List<string>();
|
||||||
|
_publisherByTopic = [];
|
||||||
|
foreach (var t in _topicsByPublisher)
|
||||||
|
{
|
||||||
|
foreach (var topic in t.Value)
|
||||||
|
{
|
||||||
|
_publisherByTopic.Add(topic, t.Key);
|
||||||
|
allTopics.Add(topic);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subscribe to NumSubscribedTopics topics spread across all topics
|
||||||
|
_allSubscribedTopics = new List<string>();
|
||||||
|
|
||||||
|
var totalNumTopics = _numPublishers * _numTopicsPerPublisher;
|
||||||
|
var topicIndexStep = totalNumTopics / (_numSubscribedTopicsPerSubscriber * _numSubscribers);
|
||||||
|
if (topicIndexStep * _numSubscribedTopicsPerSubscriber * _numSubscribers != totalNumTopics)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"The total number of topics must be divisible by the number of subscribed topics across all subscribers. Total number of topics: {totalNumTopics}, topic step: {topicIndexStep}");
|
||||||
|
}
|
||||||
|
|
||||||
|
var topicIndex = 0;
|
||||||
foreach (var mqttSubscriber in _mqttSubscriberClients)
|
foreach (var mqttSubscriber in _mqttSubscriberClients)
|
||||||
{
|
{
|
||||||
mqttSubscriber.DisconnectAsync().GetAwaiter().GetResult();
|
for (var i = 0; i < _numSubscribedTopicsPerSubscriber; ++i, topicIndex += topicIndexStep)
|
||||||
mqttSubscriber.Dispose();
|
{
|
||||||
|
var topic = allTopics[topicIndex];
|
||||||
|
_allSubscribedTopics.Add(topic);
|
||||||
|
var subOptions = new MqttClientSubscribeOptionsBuilder().WithTopicFilter(new MqttTopicFilter { Topic = topic }).Build();
|
||||||
|
mqttSubscriber.SubscribeAsync(subOptions).GetAwaiter().GetResult();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_mqttSubscriberClients.Clear();
|
|
||||||
|
|
||||||
_mqttServer.StopAsync().GetAwaiter().GetResult();
|
Task.Delay(1000).GetAwaiter().GetResult();
|
||||||
_mqttServer.Dispose();
|
|
||||||
_mqttServer = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,40 +4,19 @@
|
|||||||
|
|
||||||
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)]
|
|
||||||
[RPlotExporter, RankColumn]
|
|
||||||
[MemoryDiagnoser]
|
|
||||||
public class MessageProcessingBenchmark
|
|
||||||
{
|
|
||||||
MqttServer _mqttServer;
|
|
||||||
IMqttClient _mqttClient;
|
|
||||||
MqttApplicationMessage _message;
|
MqttApplicationMessage _message;
|
||||||
|
IMqttClient _mqttClient;
|
||||||
[GlobalSetup]
|
MqttServer _mqttServer;
|
||||||
public void Setup()
|
|
||||||
{
|
|
||||||
var serverOptions = new MqttServerOptionsBuilder().Build();
|
|
||||||
|
|
||||||
var factory = new MqttFactory();
|
|
||||||
_mqttServer = factory.CreateMqttServer(serverOptions);
|
|
||||||
_mqttClient = factory.CreateMqttClient();
|
|
||||||
|
|
||||||
_mqttServer.StartAsync().GetAwaiter().GetResult();
|
|
||||||
|
|
||||||
var clientOptions = new MqttClientOptionsBuilder()
|
|
||||||
.WithTcpServer("localhost").Build();
|
|
||||||
|
|
||||||
_mqttClient.ConnectAsync(clientOptions).GetAwaiter().GetResult();
|
|
||||||
|
|
||||||
_message = new MqttApplicationMessageBuilder()
|
|
||||||
.WithTopic("A")
|
|
||||||
.Build();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Benchmark]
|
[Benchmark]
|
||||||
public void Send_10000_Messages()
|
public void Send_10000_Messages()
|
||||||
@@ -47,5 +26,23 @@ namespace MQTTnet.Benchmarks
|
|||||||
_mqttClient.PublishAsync(_message).GetAwaiter().GetResult();
|
_mqttClient.PublishAsync(_message).GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[GlobalSetup]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
var serverOptions = new MqttServerOptionsBuilder().Build();
|
||||||
|
|
||||||
|
var serverFactory = new MqttServerFactory();
|
||||||
|
_mqttServer = serverFactory.CreateMqttServer(serverOptions);
|
||||||
|
var clientFactory = new MqttClientFactory();
|
||||||
|
_mqttClient = clientFactory.CreateMqttClient();
|
||||||
|
|
||||||
|
_mqttServer.StartAsync().GetAwaiter().GetResult();
|
||||||
|
|
||||||
|
var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("localhost").Build();
|
||||||
|
|
||||||
|
_mqttClient.ConnectAsync(clientOptions).GetAwaiter().GetResult();
|
||||||
|
|
||||||
|
_message = new MqttApplicationMessageBuilder().WithTopic("A").Build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,19 +4,17 @@
|
|||||||
|
|
||||||
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)]
|
||||||
|
[MemoryDiagnoser]
|
||||||
|
public class MessageProcessingMqttConnectionContextBenchmark : BaseBenchmark
|
||||||
{
|
{
|
||||||
[SimpleJob(RuntimeMoniker.Net60)]
|
|
||||||
[MemoryDiagnoser]
|
|
||||||
public class MessageProcessingMqttConnectionContextBenchmark
|
|
||||||
{
|
|
||||||
IWebHost _host;
|
IWebHost _host;
|
||||||
IMqttClient _mqttClient;
|
IMqttClient _mqttClient;
|
||||||
MqttApplicationMessage _message;
|
MqttApplicationMessage _message;
|
||||||
@@ -32,13 +30,13 @@ namespace MQTTnet.Benchmarks
|
|||||||
.AddMqttConnectionHandler();
|
.AddMqttConnectionHandler();
|
||||||
})
|
})
|
||||||
.Configure(app => {
|
.Configure(app => {
|
||||||
app.UseMqttServer(s => {
|
app.UseMqttServer(_ => {
|
||||||
|
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.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();
|
||||||
@@ -71,5 +69,4 @@ namespace MQTTnet.Benchmarks
|
|||||||
_mqttClient.PublishAsync(_message).GetAwaiter().GetResult();
|
_mqttClient.PublishAsync(_message).GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System.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,16 +1,18 @@
|
|||||||
using System;
|
// 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.AspNetCore;
|
|
||||||
using MQTTnet.Formatter;
|
using MQTTnet.Formatter;
|
||||||
using MQTTnet.Tests.Mockups;
|
using MQTTnet.Tests.Mockups;
|
||||||
|
|
||||||
namespace MQTTnet.Benchmarks
|
namespace MQTTnet.Benchmarks;
|
||||||
|
|
||||||
|
[SimpleJob(RuntimeMoniker.Net60)]
|
||||||
|
[MemoryDiagnoser]
|
||||||
|
public class MqttPacketReaderWriterBenchmark : BaseBenchmark
|
||||||
{
|
{
|
||||||
[SimpleJob(RuntimeMoniker.Net60)]
|
|
||||||
[MemoryDiagnoser]
|
|
||||||
public class MqttPacketReaderWriterBenchmark
|
|
||||||
{
|
|
||||||
readonly byte[] _demoPayload = new byte[1024];
|
readonly byte[] _demoPayload = new byte[1024];
|
||||||
|
|
||||||
byte[] _readPayload;
|
byte[] _readPayload;
|
||||||
@@ -27,17 +29,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,14 +82,13 @@ 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,56 +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 System;
|
using System.Buffers;
|
||||||
|
using System.Reflection;
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
|
using BenchmarkDotNet.Jobs;
|
||||||
using MQTTnet.Channel;
|
using MQTTnet.Channel;
|
||||||
|
using MQTTnet.Diagnostics.Logger;
|
||||||
using MQTTnet.Implementations;
|
using MQTTnet.Implementations;
|
||||||
using MQTTnet.Server;
|
using MQTTnet.Server;
|
||||||
using System.Threading;
|
using MQTTnet.Server.Internal.Adapter;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using BenchmarkDotNet.Jobs;
|
|
||||||
using MQTTnet.Client;
|
|
||||||
using MQTTnet.Diagnostics;
|
|
||||||
|
|
||||||
namespace MQTTnet.Benchmarks
|
namespace MQTTnet.Benchmarks;
|
||||||
|
|
||||||
|
[SimpleJob(RuntimeMoniker.Net60)]
|
||||||
|
[MemoryDiagnoser]
|
||||||
|
public sealed class MqttTcpChannelBenchmark : BaseBenchmark, IDisposable
|
||||||
{
|
{
|
||||||
[SimpleJob(RuntimeMoniker.Net60)]
|
MqttTcpChannel _clientChannel;
|
||||||
[MemoryDiagnoser]
|
|
||||||
public sealed class MqttTcpChannelBenchmark
|
|
||||||
{
|
|
||||||
MqttServer _mqttServer;
|
MqttServer _mqttServer;
|
||||||
IMqttChannel _serverChannel;
|
IMqttChannel _serverChannel;
|
||||||
IMqttChannel _clientChannel;
|
|
||||||
|
|
||||||
[GlobalSetup]
|
|
||||||
public void Setup()
|
|
||||||
{
|
|
||||||
var factory = new MqttFactory();
|
|
||||||
var tcpServer = new MqttTcpServerAdapter();
|
|
||||||
tcpServer.ClientHandler += args =>
|
|
||||||
{
|
|
||||||
_serverChannel =
|
|
||||||
(IMqttChannel)args.GetType().GetField("_channel",
|
|
||||||
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)
|
|
||||||
.GetValue(args);
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
};
|
|
||||||
|
|
||||||
var serverOptions = new MqttServerOptionsBuilder().Build();
|
|
||||||
_mqttServer = factory.CreateMqttServer(serverOptions, new[] { tcpServer }, new MqttNetEventLogger());
|
|
||||||
|
|
||||||
|
|
||||||
_mqttServer.StartAsync().GetAwaiter().GetResult();
|
|
||||||
|
|
||||||
var clientOptions = new MqttClientOptionsBuilder()
|
|
||||||
.WithTcpServer("localhost").Build();
|
|
||||||
|
|
||||||
var tcpOptions = (MqttClientTcpOptions)clientOptions.ChannelOptions;
|
|
||||||
_clientChannel = new MqttTcpChannel(new MqttClientOptions { ChannelOptions = tcpOptions });
|
|
||||||
|
|
||||||
_clientChannel.ConnectAsync(CancellationToken.None).GetAwaiter().GetResult();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Benchmark]
|
[Benchmark]
|
||||||
public async Task Send_10000_Chunks()
|
public async Task Send_10000_Chunks()
|
||||||
@@ -62,6 +31,32 @@ namespace MQTTnet.Benchmarks
|
|||||||
await Task.WhenAll(WriteAsync(iterations, size), ReadAsync(iterations, size));
|
await Task.WhenAll(WriteAsync(iterations, size), ReadAsync(iterations, size));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[GlobalSetup]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
var serverFactory = new MqttServerFactory();
|
||||||
|
var tcpServer = new MqttTcpServerAdapter();
|
||||||
|
tcpServer.ClientHandler += args =>
|
||||||
|
{
|
||||||
|
_serverChannel = (IMqttChannel)args.GetType().GetField("_channel", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(args);
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
};
|
||||||
|
|
||||||
|
var serverOptions = new MqttServerOptionsBuilder().Build();
|
||||||
|
_mqttServer = serverFactory.CreateMqttServer(serverOptions, new[] { tcpServer }, new MqttNetEventLogger());
|
||||||
|
|
||||||
|
|
||||||
|
_mqttServer.StartAsync().GetAwaiter().GetResult();
|
||||||
|
|
||||||
|
var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("localhost").Build();
|
||||||
|
|
||||||
|
var tcpOptions = (MqttClientTcpOptions)clientOptions.ChannelOptions;
|
||||||
|
_clientChannel = new MqttTcpChannel(new MqttClientOptions { ChannelOptions = tcpOptions });
|
||||||
|
|
||||||
|
_clientChannel.ConnectAsync(CancellationToken.None).GetAwaiter().GetResult();
|
||||||
|
}
|
||||||
|
|
||||||
async Task ReadAsync(int iterations, int size)
|
async Task ReadAsync(int iterations, int size)
|
||||||
{
|
{
|
||||||
await Task.Yield();
|
await Task.Yield();
|
||||||
@@ -80,12 +75,18 @@ namespace MQTTnet.Benchmarks
|
|||||||
{
|
{
|
||||||
await Task.Yield();
|
await Task.Yield();
|
||||||
|
|
||||||
var buffer = new ArraySegment<byte>(new byte[size]);
|
var buffer = new ReadOnlySequence<byte>(new byte[size]);
|
||||||
|
|
||||||
for (var i = 0; i < iterations; i++)
|
for (var i = 0; i < iterations; i++)
|
||||||
{
|
{
|
||||||
await _serverChannel.WriteAsync(buffer, true, CancellationToken.None).ConfigureAwait(false);
|
await _serverChannel.WriteAsync(buffer, true, CancellationToken.None).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_clientChannel?.Dispose();
|
||||||
|
_mqttServer?.Dispose();
|
||||||
|
_serverChannel?.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,86 +2,135 @@
|
|||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System;
|
|
||||||
using BenchmarkDotNet.Running;
|
using BenchmarkDotNet.Running;
|
||||||
using MQTTnet.Benchmarks.Configurations;
|
|
||||||
using MQTTnet.Diagnostics;
|
|
||||||
|
|
||||||
namespace MQTTnet.Benchmarks
|
namespace MQTTnet.Benchmarks;
|
||||||
|
|
||||||
|
public static class Program
|
||||||
{
|
{
|
||||||
public static class Program
|
static int _selectedBenchmarkIndex;
|
||||||
{
|
static List<Type> _benchmarks;
|
||||||
public static void Main(string[] args)
|
|
||||||
{
|
|
||||||
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");
|
|
||||||
Console.WriteLine("f = AsyncLockBenchmark");
|
|
||||||
|
|
||||||
var pressedKey = Console.ReadKey(true);
|
public static void Main(string[] arguments)
|
||||||
switch (pressedKey.KeyChar)
|
{
|
||||||
|
_benchmarks = CollectBenchmarks();
|
||||||
|
HandleArguments(arguments);
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
RenderMenu();
|
||||||
|
|
||||||
|
var key = Console.ReadKey(true);
|
||||||
|
|
||||||
|
if (key.Key == ConsoleKey.DownArrow)
|
||||||
|
{
|
||||||
|
_selectedBenchmarkIndex++;
|
||||||
|
if (_selectedBenchmarkIndex > _benchmarks.Count - 1)
|
||||||
|
{
|
||||||
|
_selectedBenchmarkIndex = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (key.Key == ConsoleKey.UpArrow)
|
||||||
|
{
|
||||||
|
_selectedBenchmarkIndex--;
|
||||||
|
if (_selectedBenchmarkIndex < 0)
|
||||||
|
{
|
||||||
|
_selectedBenchmarkIndex = _benchmarks.Count - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (key.Key == ConsoleKey.Escape)
|
||||||
|
{
|
||||||
|
Environment.Exit(0);
|
||||||
|
}
|
||||||
|
else if (key.Key == ConsoleKey.Enter)
|
||||||
{
|
{
|
||||||
case '1':
|
|
||||||
BenchmarkRunner.Run<MessageProcessingBenchmark>();
|
|
||||||
break;
|
|
||||||
case '2':
|
|
||||||
BenchmarkRunner.Run<SerializerBenchmark>();
|
|
||||||
break;
|
|
||||||
case '3':
|
|
||||||
BenchmarkRunner.Run<LoggerBenchmark>();
|
|
||||||
break;
|
|
||||||
case '4':
|
|
||||||
BenchmarkRunner.Run<TopicFilterComparerBenchmark>();
|
|
||||||
break;
|
|
||||||
case '5':
|
|
||||||
BenchmarkRunner.Run<ChannelAdapterBenchmark>();
|
|
||||||
break;
|
|
||||||
case '6':
|
|
||||||
BenchmarkRunner.Run<MqttTcpChannelBenchmark>();
|
|
||||||
break;
|
|
||||||
case '7':
|
|
||||||
BenchmarkRunner.Run<TcpPipesBenchmark>();
|
|
||||||
break;
|
|
||||||
case '8':
|
|
||||||
BenchmarkRunner.Run<MessageProcessingMqttConnectionContextBenchmark>(new RuntimeCompareConfig());
|
|
||||||
break;
|
|
||||||
case '9':
|
|
||||||
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;
|
|
||||||
case 'f':
|
|
||||||
BenchmarkRunner.Run<AsyncLockBenchmark>();
|
|
||||||
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], StringComparison.Ordinal));
|
||||||
|
|
||||||
|
if (_selectedBenchmarkIndex < 0)
|
||||||
|
{
|
||||||
|
_selectedBenchmarkIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_selectedBenchmarkIndex > _benchmarks.Count - 1)
|
||||||
|
{
|
||||||
|
_selectedBenchmarkIndex = _benchmarks.Count - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void RenderMenu()
|
||||||
|
{
|
||||||
|
Console.Clear();
|
||||||
|
|
||||||
|
Console.WriteLine($"MQTTnet - Benchmarks");
|
||||||
|
Console.WriteLine("-----------------------------------------------");
|
||||||
|
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||||
|
Console.WriteLine("Press arrow keys for benchmark selection");
|
||||||
|
Console.WriteLine("Press Enter for benchmark execution");
|
||||||
|
Console.WriteLine("Press Esc for exit");
|
||||||
|
Console.ForegroundColor = ConsoleColor.White;
|
||||||
|
Console.WriteLine();
|
||||||
|
|
||||||
|
for (var index = 0; index < _benchmarks.Count; index++)
|
||||||
|
{
|
||||||
|
var benchmark = _benchmarks[index];
|
||||||
|
|
||||||
|
if (_selectedBenchmarkIndex == index)
|
||||||
|
{
|
||||||
|
Console.Write("-> ");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.Write(" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine(benchmark.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
using BenchmarkDotNet.Attributes;
|
||||||
|
using BenchmarkDotNet.Jobs;
|
||||||
|
using MQTTnet.AspNetCore;
|
||||||
|
using MQTTnet.Exceptions;
|
||||||
|
using MQTTnet.Formatter;
|
||||||
|
using System.Buffers;
|
||||||
|
using System.IO.Pipelines;
|
||||||
|
|
||||||
|
namespace MQTTnet.Benchmarks;
|
||||||
|
|
||||||
|
[SimpleJob(RuntimeMoniker.Net60)]
|
||||||
|
[RPlotExporter, RankColumn]
|
||||||
|
[MemoryDiagnoser]
|
||||||
|
public sealed class ReaderExtensionsBenchmark : IDisposable, IAsyncDisposable
|
||||||
|
{
|
||||||
|
MqttPacketFormatterAdapter _mqttPacketFormatter;
|
||||||
|
MemoryStream _stream;
|
||||||
|
|
||||||
|
[GlobalSetup]
|
||||||
|
public void GlobalSetup()
|
||||||
|
{
|
||||||
|
_mqttPacketFormatter = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535));
|
||||||
|
var mqttMessage = new MqttApplicationMessageBuilder()
|
||||||
|
.WithTopic("topic")
|
||||||
|
.WithPayload(new byte[10 * 1024])
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var packet = MqttPublishPacketFactory.Create(mqttMessage);
|
||||||
|
|
||||||
|
var buffer = _mqttPacketFormatter.Encode(packet);
|
||||||
|
_stream = new MemoryStream();
|
||||||
|
_stream.Write(buffer.Packet);
|
||||||
|
_stream.Write(buffer.Payload.ToArray());
|
||||||
|
_mqttPacketFormatter.Cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public async Task After()
|
||||||
|
{
|
||||||
|
_stream.Position = 0;
|
||||||
|
var input = PipeReader.Create(_stream);
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
ReadResult readResult;
|
||||||
|
var readTask = input.ReadAsync();
|
||||||
|
if (readTask.IsCompleted)
|
||||||
|
{
|
||||||
|
readResult = readTask.Result;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
readResult = await readTask.ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
var buffer = readResult.Buffer;
|
||||||
|
|
||||||
|
var consumed = buffer.Start;
|
||||||
|
var observed = buffer.Start;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!buffer.IsEmpty)
|
||||||
|
{
|
||||||
|
if (_mqttPacketFormatter.TryDecode(buffer, out _, out consumed, out observed, out _))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (readResult.IsCompleted)
|
||||||
|
{
|
||||||
|
throw new MqttCommunicationException("Connection Aborted");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
// The buffer was sliced up to where it was consumed, so we can just advance to the start.
|
||||||
|
// We mark examined as buffer.End so that if we didn't receive a full frame, we'll wait for more data
|
||||||
|
// before yielding the read again.
|
||||||
|
input.AdvanceTo(consumed, observed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_stream?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask DisposeAsync()
|
||||||
|
{
|
||||||
|
if (_stream != null)
|
||||||
|
{
|
||||||
|
await _stream.DisposeAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,15 +1,14 @@
|
|||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using BenchmarkDotNet.Jobs;
|
using BenchmarkDotNet.Jobs;
|
||||||
using MQTTnet.Tests.Mockups;
|
using MQTTnet.Tests.Mockups;
|
||||||
using MQTTnet.Tests.Server;
|
|
||||||
|
|
||||||
namespace MQTTnet.Benchmarks
|
namespace MQTTnet.Benchmarks;
|
||||||
|
|
||||||
|
[SimpleJob(RuntimeMoniker.Net60)]
|
||||||
|
[RPlotExporter, RankColumn]
|
||||||
|
[MemoryDiagnoser]
|
||||||
|
public class RoundtripProcessingBenchmark : BaseBenchmark
|
||||||
{
|
{
|
||||||
[SimpleJob(RuntimeMoniker.Net60)]
|
|
||||||
[RPlotExporter, RankColumn]
|
|
||||||
[MemoryDiagnoser]
|
|
||||||
public class RoundtripProcessingBenchmark
|
|
||||||
{
|
|
||||||
[GlobalSetup]
|
[GlobalSetup]
|
||||||
public void GlobalSetup()
|
public void GlobalSetup()
|
||||||
{
|
{
|
||||||
@@ -24,7 +23,6 @@ namespace MQTTnet.Benchmarks
|
|||||||
[Benchmark]
|
[Benchmark]
|
||||||
public void Handle_100_000_Messages_In_Receiving_Client()
|
public void Handle_100_000_Messages_In_Receiving_Client()
|
||||||
{
|
{
|
||||||
new Load_Tests().Handle_100_000_Messages_In_Receiving_Client().GetAwaiter().GetResult();
|
//new Load_Tests().Handle_100_000_Messages_In_Receiving_Client().GetAwaiter().GetResult();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
using System.Buffers;
|
||||||
|
using System.IO.Pipelines;
|
||||||
|
using BenchmarkDotNet.Attributes;
|
||||||
|
using BenchmarkDotNet.Jobs;
|
||||||
|
using MQTTnet.Formatter;
|
||||||
|
|
||||||
|
namespace MQTTnet.Benchmarks;
|
||||||
|
|
||||||
|
[SimpleJob(RuntimeMoniker.Net60)]
|
||||||
|
[RPlotExporter]
|
||||||
|
[RankColumn]
|
||||||
|
[MemoryDiagnoser]
|
||||||
|
public sealed class SendPacketAsyncBenchmark : BaseBenchmark, IDisposable, IAsyncDisposable
|
||||||
|
{
|
||||||
|
MqttPacketBuffer _buffer;
|
||||||
|
MemoryStream _stream;
|
||||||
|
|
||||||
|
[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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark(Baseline = true)]
|
||||||
|
public async ValueTask Before()
|
||||||
|
{
|
||||||
|
_stream.Position = 0;
|
||||||
|
var output = PipeWriter.Create(_stream);
|
||||||
|
|
||||||
|
WritePacketBuffer(output, _buffer);
|
||||||
|
await output.FlushAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_stream?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask DisposeAsync()
|
||||||
|
{
|
||||||
|
if (_stream != null)
|
||||||
|
{
|
||||||
|
await _stream.DisposeAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[GlobalSetup]
|
||||||
|
public void GlobalSetup()
|
||||||
|
{
|
||||||
|
_stream = new MemoryStream(1024);
|
||||||
|
var packet = new ArraySegment<byte>(new byte[10]);
|
||||||
|
_buffer = new MqttPacketBuffer(packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void WritePacketBuffer(PipeWriter output, MqttPacketBuffer buffer)
|
||||||
|
{
|
||||||
|
// copy MqttPacketBuffer's Packet and Payload to the same buffer block of PipeWriter
|
||||||
|
// MqttPacket will be transmitted within the bounds of a WebSocket frame after PipeWriter.FlushAsync
|
||||||
|
|
||||||
|
var span = output.GetSpan(buffer.Length);
|
||||||
|
|
||||||
|
buffer.Packet.AsSpan().CopyTo(span);
|
||||||
|
buffer.Payload.CopyTo(span[buffer.Packet.Count..]);
|
||||||
|
|
||||||
|
output.Advance(buffer.Length);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,27 +4,26 @@
|
|||||||
|
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using MQTTnet.Packets;
|
using MQTTnet.Packets;
|
||||||
using System;
|
|
||||||
using System.Security.Cryptography.X509Certificates;
|
using System.Security.Cryptography.X509Certificates;
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using MQTTnet.Adapter;
|
using MQTTnet.Adapter;
|
||||||
using MQTTnet.Channel;
|
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;
|
||||||
|
|
||||||
|
[SimpleJob(RuntimeMoniker.Net60)]
|
||||||
|
[RPlotExporter]
|
||||||
|
[MemoryDiagnoser]
|
||||||
|
public class SerializerBenchmark : BaseBenchmark
|
||||||
{
|
{
|
||||||
[SimpleJob(RuntimeMoniker.Net60)]
|
|
||||||
[RPlotExporter]
|
|
||||||
[MemoryDiagnoser]
|
|
||||||
public class SerializerBenchmark : BaseBenchmark
|
|
||||||
{
|
|
||||||
MqttPacket _packet;
|
MqttPacket _packet;
|
||||||
ArraySegment<byte> _serializedPacket;
|
ArraySegment<byte> _serializedPacket;
|
||||||
IMqttPacketFormatter _serializer;
|
MqttV3PacketFormatter _serializer;
|
||||||
MqttBufferWriter _bufferWriter;
|
MqttBufferWriter _bufferWriter;
|
||||||
|
|
||||||
[GlobalSetup]
|
[GlobalSetup]
|
||||||
@@ -41,7 +40,7 @@ namespace MQTTnet.Benchmarks
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Benchmark]
|
[Benchmark]
|
||||||
public void Serialize_10000_Messages()
|
public void Serialize10000Messages()
|
||||||
{
|
{
|
||||||
for (var i = 0; i < 10000; i++)
|
for (var i = 0; i < 10000; i++)
|
||||||
{
|
{
|
||||||
@@ -51,20 +50,20 @@ namespace MQTTnet.Benchmarks
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Benchmark]
|
[Benchmark]
|
||||||
public void Deserialize_10000_Messages()
|
public void Deserialize10000Messages()
|
||||||
{
|
{
|
||||||
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++)
|
||||||
{
|
{
|
||||||
channel.Reset();
|
channel.Reset();
|
||||||
|
|
||||||
var header = reader.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
|
reader.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class BenchmarkMqttChannel : IMqttChannel
|
sealed class BenchmarkMqttChannel : IMqttChannel
|
||||||
{
|
{
|
||||||
readonly ArraySegment<byte> _buffer;
|
readonly ArraySegment<byte> _buffer;
|
||||||
int _position;
|
int _position;
|
||||||
@@ -75,11 +74,13 @@ 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 EndPoint LocalEndPoint { get; set; }
|
||||||
|
|
||||||
public X509Certificate2 ClientCertificate { get; }
|
public bool IsSecureConnection { get; }
|
||||||
|
|
||||||
|
public X509Certificate2 ClientCertificate { get; set; }
|
||||||
|
|
||||||
public void Reset()
|
public void Reset()
|
||||||
{
|
{
|
||||||
@@ -98,13 +99,13 @@ namespace MQTTnet.Benchmarks
|
|||||||
|
|
||||||
public Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
public Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
Array.Copy(_buffer.Array, _position, buffer, offset, count);
|
Array.Copy(_buffer!.Array!, _position, buffer, offset, count);
|
||||||
_position += count;
|
_position += count;
|
||||||
|
|
||||||
return Task.FromResult(count);
|
return Task.FromResult(count);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task WriteAsync(ArraySegment<byte> buffer, bool isEndOfPacket, CancellationToken cancellationToken)
|
public Task WriteAsync(ReadOnlySequence<byte> buffer, bool isEndOfPacket, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
throw new NotSupportedException();
|
throw new NotSupportedException();
|
||||||
}
|
}
|
||||||
@@ -113,5 +114,4 @@ namespace MQTTnet.Benchmarks
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,15 +1,18 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using BenchmarkDotNet.Jobs;
|
using BenchmarkDotNet.Jobs;
|
||||||
using MQTTnet.Tests.Mockups;
|
using MQTTnet.Tests.Mockups;
|
||||||
using MQTTnet.Tests.Server;
|
|
||||||
|
|
||||||
namespace MQTTnet.Benchmarks
|
namespace MQTTnet.Benchmarks;
|
||||||
|
|
||||||
|
[SimpleJob(RuntimeMoniker.Net60)]
|
||||||
|
[RPlotExporter, RankColumn]
|
||||||
|
[MemoryDiagnoser]
|
||||||
|
public class ServerProcessingBenchmark : BaseBenchmark
|
||||||
{
|
{
|
||||||
[SimpleJob(RuntimeMoniker.Net60)]
|
|
||||||
[RPlotExporter, RankColumn]
|
|
||||||
[MemoryDiagnoser]
|
|
||||||
public class ServerProcessingBenchmark
|
|
||||||
{
|
|
||||||
[GlobalSetup]
|
[GlobalSetup]
|
||||||
public void GlobalSetup()
|
public void GlobalSetup()
|
||||||
{
|
{
|
||||||
@@ -24,13 +27,12 @@ namespace MQTTnet.Benchmarks
|
|||||||
[Benchmark]
|
[Benchmark]
|
||||||
public void Handle_100_000_Messages_In_Server_MqttClient()
|
public void Handle_100_000_Messages_In_Server_MqttClient()
|
||||||
{
|
{
|
||||||
new Load_Tests().Handle_100_000_Messages_In_Server().GetAwaiter().GetResult();
|
//new Load_Tests().Handle_100_000_Messages_In_Server().GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
//[Benchmark]
|
[Benchmark]
|
||||||
public void Handle_100_000_Messages_In_Server_LowLevelMqttClient()
|
public void Handle_100_000_Messages_In_Server_LowLevelMqttClient()
|
||||||
{
|
{
|
||||||
new Load_Tests().Handle_100_000_Messages_In_Low_Level_Client().GetAwaiter().GetResult();
|
//new Load_Tests().Handle_100_000_Messages_In_Low_Level_Client().GetAwaiter().GetResult();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,43 +1,22 @@
|
|||||||
|
// 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 MQTTnet.Client;
|
|
||||||
using MQTTnet.Server;
|
using MQTTnet.Server;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace MQTTnet.Benchmarks
|
namespace MQTTnet.Benchmarks;
|
||||||
|
|
||||||
|
[MemoryDiagnoser]
|
||||||
|
public class SubscribeBenchmark : BaseBenchmark
|
||||||
{
|
{
|
||||||
[MemoryDiagnoser]
|
|
||||||
public class SubscribeBenchmark
|
|
||||||
{
|
|
||||||
MqttServer _mqttServer;
|
|
||||||
IMqttClient _mqttClient;
|
|
||||||
|
|
||||||
const int NumPublishers = 1;
|
const int NumPublishers = 1;
|
||||||
const int NumTopicsPerPublisher = 10000;
|
const int NumTopicsPerPublisher = 10000;
|
||||||
|
IMqttClient _mqttClient;
|
||||||
|
MqttServer _mqttServer;
|
||||||
|
|
||||||
List<string> _topics;
|
List<string> _topics;
|
||||||
|
|
||||||
[GlobalSetup]
|
|
||||||
public void Setup()
|
|
||||||
{
|
|
||||||
TopicGenerator.Generate(NumPublishers, NumTopicsPerPublisher, out var topicsByPublisher, out var singleWildcardTopicsByPublisher, out var multiWildcardTopicsByPublisher);
|
|
||||||
_topics = topicsByPublisher.Values.First();
|
|
||||||
|
|
||||||
var serverOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
|
|
||||||
|
|
||||||
var factory = new MqttFactory();
|
|
||||||
_mqttServer = factory.CreateMqttServer(serverOptions);
|
|
||||||
_mqttClient = factory.CreateMqttClient();
|
|
||||||
|
|
||||||
_mqttServer.StartAsync().GetAwaiter().GetResult();
|
|
||||||
|
|
||||||
var clientOptions = new MqttClientOptionsBuilder()
|
|
||||||
.WithTcpServer("localhost").Build();
|
|
||||||
|
|
||||||
_mqttClient.ConnectAsync(clientOptions).GetAwaiter().GetResult();
|
|
||||||
}
|
|
||||||
|
|
||||||
[GlobalCleanup]
|
[GlobalCleanup]
|
||||||
public void Cleanup()
|
public void Cleanup()
|
||||||
{
|
{
|
||||||
@@ -46,17 +25,34 @@ namespace MQTTnet.Benchmarks
|
|||||||
_mqttServer.Dispose();
|
_mqttServer.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[GlobalSetup]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
TopicGenerator.Generate(NumPublishers, NumTopicsPerPublisher, out var topicsByPublisher, out _, out _);
|
||||||
|
_topics = topicsByPublisher.Values.First();
|
||||||
|
|
||||||
|
var serverOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().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();
|
||||||
|
}
|
||||||
|
|
||||||
[Benchmark]
|
[Benchmark]
|
||||||
public void Subscribe_10000_Topics()
|
public void Subscribe_10000_Topics()
|
||||||
{
|
{
|
||||||
foreach (var topic in _topics)
|
foreach (var topic in _topics)
|
||||||
{
|
{
|
||||||
var subscribeOptions = new MqttClientSubscribeOptionsBuilder()
|
var subscribeOptions = new MqttClientSubscribeOptionsBuilder().WithTopicFilter(topic).Build();
|
||||||
.WithTopicFilter(topic, Protocol.MqttQualityOfServiceLevel.AtMostOnce)
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
_mqttClient.SubscribeAsync(subscribeOptions).GetAwaiter().GetResult();
|
_mqttClient.SubscribeAsync(subscribeOptions).GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -5,18 +5,16 @@
|
|||||||
using System.IO.Pipelines;
|
using System.IO.Pipelines;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
using System.Threading;
|
|
||||||
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)]
|
||||||
|
[MemoryDiagnoser]
|
||||||
|
public class TcpPipesBenchmark : BaseBenchmark
|
||||||
{
|
{
|
||||||
[SimpleJob(RuntimeMoniker.Net60)]
|
|
||||||
[MemoryDiagnoser]
|
|
||||||
public class TcpPipesBenchmark
|
|
||||||
{
|
|
||||||
IDuplexPipe _client;
|
IDuplexPipe _client;
|
||||||
IDuplexPipe _server;
|
IDuplexPipe _server;
|
||||||
|
|
||||||
@@ -29,12 +27,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;
|
||||||
}
|
}
|
||||||
@@ -76,5 +74,4 @@ namespace MQTTnet.Benchmarks
|
|||||||
await output.WriteAsync(new byte[size], CancellationToken.None).ConfigureAwait(false);
|
await output.WriteAsync(new byte[size], CancellationToken.None).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -2,19 +2,16 @@
|
|||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System;
|
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using BenchmarkDotNet.Jobs;
|
using BenchmarkDotNet.Jobs;
|
||||||
|
|
||||||
namespace MQTTnet.Benchmarks
|
namespace MQTTnet.Benchmarks;
|
||||||
{
|
|
||||||
[SimpleJob(RuntimeMoniker.Net60)]
|
|
||||||
[RPlotExporter]
|
|
||||||
[MemoryDiagnoser]
|
|
||||||
public class TopicFilterComparerBenchmark
|
|
||||||
{
|
|
||||||
static readonly char[] TopicLevelSeparator = { '/' };
|
|
||||||
|
|
||||||
|
[SimpleJob(RuntimeMoniker.Net60)]
|
||||||
|
[RPlotExporter]
|
||||||
|
[MemoryDiagnoser]
|
||||||
|
public class TopicFilterComparerBenchmark : BaseBenchmark
|
||||||
|
{
|
||||||
readonly string _longTopic =
|
readonly string _longTopic =
|
||||||
"AAAAAAAAAAAAAssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssshhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC";
|
"AAAAAAAAAAAAAssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssshhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC";
|
||||||
|
|
||||||
@@ -33,245 +30,4 @@ namespace MQTTnet.Benchmarks
|
|||||||
MqttTopicFilterComparer.Compare(_longTopic, _longTopic);
|
MqttTopicFilterComparer.Compare(_longTopic, _longTopic);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Benchmark]
|
|
||||||
public void MqttTopicFilterComparer_10000_LoopMethod_Without_Pointer()
|
|
||||||
{
|
|
||||||
for (var i = 0; i < 100000; i++)
|
|
||||||
{
|
|
||||||
MqttTopicFilterComparerWithoutPointer.Compare("sport/tennis/player1", "sport/#");
|
|
||||||
MqttTopicFilterComparerWithoutPointer.Compare("sport/tennis/player1/ranking", "sport/#/ranking");
|
|
||||||
MqttTopicFilterComparerWithoutPointer.Compare("sport/tennis/player1/score/wimbledon", "sport/+/player1/#");
|
|
||||||
MqttTopicFilterComparerWithoutPointer.Compare("sport/tennis/player1", "sport/tennis/+");
|
|
||||||
MqttTopicFilterComparerWithoutPointer.Compare("/finance", "+/+");
|
|
||||||
MqttTopicFilterComparerWithoutPointer.Compare("/finance", "/+");
|
|
||||||
MqttTopicFilterComparerWithoutPointer.Compare("/finance", "+");
|
|
||||||
MqttTopicFilterComparerWithoutPointer.Compare(_longTopic, _longTopic);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Benchmark]
|
|
||||||
public void MqttTopicFilterComparer_10000_StringSplitMethod()
|
|
||||||
{
|
|
||||||
for (var i = 0; i < 100000; i++)
|
|
||||||
{
|
|
||||||
LegacyMethodByStringSplit("sport/tennis/player1", "sport/#");
|
|
||||||
LegacyMethodByStringSplit("sport/tennis/player1/ranking", "sport/#/ranking");
|
|
||||||
LegacyMethodByStringSplit("sport/tennis/player1/score/wimbledon", "sport/+/player1/#");
|
|
||||||
LegacyMethodByStringSplit("sport/tennis/player1", "sport/tennis/+");
|
|
||||||
LegacyMethodByStringSplit("/finance", "+/+");
|
|
||||||
LegacyMethodByStringSplit("/finance", "/+");
|
|
||||||
LegacyMethodByStringSplit("/finance", "+");
|
|
||||||
MqttTopicFilterComparer.Compare(_longTopic, _longTopic);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[GlobalSetup]
|
|
||||||
public void Setup()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool LegacyMethodByStringSplit(string topic, string filter)
|
|
||||||
{
|
|
||||||
if (topic == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(topic));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filter == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(filter));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.Equals(topic, filter, StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
var fragmentsTopic = topic.Split(TopicLevelSeparator, StringSplitOptions.None);
|
|
||||||
var fragmentsFilter = filter.Split(TopicLevelSeparator, StringSplitOptions.None);
|
|
||||||
|
|
||||||
// # > In either case it MUST be the last character specified in the Topic Filter [MQTT-4.7.1-2].
|
|
||||||
for (var i = 0; i < fragmentsFilter.Length; i++)
|
|
||||||
{
|
|
||||||
if (fragmentsFilter[i] == "+")
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fragmentsFilter[i] == "#")
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i >= fragmentsTopic.Length)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.Equals(fragmentsFilter[i], fragmentsTopic[i], StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return fragmentsTopic.Length == fragmentsFilter.Length;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class MqttTopicFilterComparerWithoutPointer
|
|
||||||
{
|
|
||||||
public const char LevelSeparator = '/';
|
|
||||||
public const char MultiLevelWildcard = '#';
|
|
||||||
public const char SingleLevelWildcard = '+';
|
|
||||||
public const char ReservedTopicPrefix = '$';
|
|
||||||
|
|
||||||
public static MqttTopicFilterCompareResult Compare(string topic, string filter)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(topic))
|
|
||||||
{
|
|
||||||
return MqttTopicFilterCompareResult.TopicInvalid;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(filter))
|
|
||||||
{
|
|
||||||
return MqttTopicFilterCompareResult.FilterInvalid;
|
|
||||||
}
|
|
||||||
|
|
||||||
var filterOffset = 0;
|
|
||||||
var filterLength = filter.Length;
|
|
||||||
|
|
||||||
var topicOffset = 0;
|
|
||||||
var topicLength = topic.Length;
|
|
||||||
|
|
||||||
var topicPointer = topic;
|
|
||||||
var filterPointer = filter;
|
|
||||||
|
|
||||||
var isMultiLevelFilter = filterPointer[filterLength - 1] == MultiLevelWildcard;
|
|
||||||
var isReservedTopic = topicPointer[0] == ReservedTopicPrefix;
|
|
||||||
|
|
||||||
if (isReservedTopic && filterLength == 1 && isMultiLevelFilter)
|
|
||||||
{
|
|
||||||
// It is not allowed to receive i.e. '$foo/bar' with filter '#'.
|
|
||||||
return MqttTopicFilterCompareResult.NoMatch;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isReservedTopic && filterPointer[0] == SingleLevelWildcard)
|
|
||||||
{
|
|
||||||
// It is not allowed to receive i.e. '$SYS/monitor/Clients' with filter '+/monitor/Clients'.
|
|
||||||
return MqttTopicFilterCompareResult.NoMatch;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filterLength == 1 && isMultiLevelFilter)
|
|
||||||
{
|
|
||||||
// Filter '#' matches basically everything.
|
|
||||||
return MqttTopicFilterCompareResult.IsMatch;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Go through the filter char by char.
|
|
||||||
while (filterOffset < filterLength && topicOffset < topicLength)
|
|
||||||
{
|
|
||||||
// Check if the current char is a multi level wildcard. The char is only allowed
|
|
||||||
// at the very las position.
|
|
||||||
if (filterPointer[filterOffset] == MultiLevelWildcard && filterOffset != filterLength - 1)
|
|
||||||
{
|
|
||||||
return MqttTopicFilterCompareResult.FilterInvalid;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filterPointer[filterOffset] == topicPointer[topicOffset])
|
|
||||||
{
|
|
||||||
if (topicOffset == topicLength - 1)
|
|
||||||
{
|
|
||||||
// Check for e.g. "foo" matching "foo/#"
|
|
||||||
if (filterOffset == filterLength - 3 && filterPointer[filterOffset + 1] == LevelSeparator && isMultiLevelFilter)
|
|
||||||
{
|
|
||||||
return MqttTopicFilterCompareResult.IsMatch;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for e.g. "foo/" matching "foo/#"
|
|
||||||
if (filterOffset == filterLength - 2 && filterPointer[filterOffset] == LevelSeparator && isMultiLevelFilter)
|
|
||||||
{
|
|
||||||
return MqttTopicFilterCompareResult.IsMatch;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
filterOffset++;
|
|
||||||
topicOffset++;
|
|
||||||
|
|
||||||
// Check if the end was reached and i.e. "foo/bar" matches "foo/bar"
|
|
||||||
if (filterOffset == filterLength && topicOffset == topicLength)
|
|
||||||
{
|
|
||||||
return MqttTopicFilterCompareResult.IsMatch;
|
|
||||||
}
|
|
||||||
|
|
||||||
var endOfTopic = topicOffset == topicLength;
|
|
||||||
|
|
||||||
if (endOfTopic && filterOffset == filterLength - 1 && filterPointer[filterOffset] == SingleLevelWildcard)
|
|
||||||
{
|
|
||||||
if (filterOffset > 0 && filterPointer[filterOffset - 1] != LevelSeparator)
|
|
||||||
{
|
|
||||||
return MqttTopicFilterCompareResult.FilterInvalid;
|
|
||||||
}
|
|
||||||
|
|
||||||
return MqttTopicFilterCompareResult.IsMatch;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (filterPointer[filterOffset] == SingleLevelWildcard)
|
|
||||||
{
|
|
||||||
// Check for invalid "+foo" or "a/+foo" subscription
|
|
||||||
if (filterOffset > 0 && filterPointer[filterOffset - 1] != LevelSeparator)
|
|
||||||
{
|
|
||||||
return MqttTopicFilterCompareResult.FilterInvalid;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for bad "foo+" or "foo+/a" subscription
|
|
||||||
if (filterOffset < filterLength - 1 && filterPointer[filterOffset + 1] != LevelSeparator)
|
|
||||||
{
|
|
||||||
return MqttTopicFilterCompareResult.FilterInvalid;
|
|
||||||
}
|
|
||||||
|
|
||||||
filterOffset++;
|
|
||||||
while (topicOffset < topicLength && topicPointer[topicOffset] != LevelSeparator)
|
|
||||||
{
|
|
||||||
topicOffset++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (topicOffset == topicLength && filterOffset == filterLength)
|
|
||||||
{
|
|
||||||
return MqttTopicFilterCompareResult.IsMatch;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (filterPointer[filterOffset] == MultiLevelWildcard)
|
|
||||||
{
|
|
||||||
if (filterOffset > 0 && filterPointer[filterOffset - 1] != LevelSeparator)
|
|
||||||
{
|
|
||||||
return MqttTopicFilterCompareResult.FilterInvalid;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filterOffset + 1 != filterLength)
|
|
||||||
{
|
|
||||||
return MqttTopicFilterCompareResult.FilterInvalid;
|
|
||||||
}
|
|
||||||
|
|
||||||
return MqttTopicFilterCompareResult.IsMatch;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Check for e.g. "foo/bar" matching "foo/+/#".
|
|
||||||
if (filterOffset > 0 && filterOffset + 2 == filterLength && topicOffset == topicLength && filterPointer[filterOffset - 1] == SingleLevelWildcard &&
|
|
||||||
filterPointer[filterOffset] == LevelSeparator && isMultiLevelFilter)
|
|
||||||
{
|
|
||||||
return MqttTopicFilterCompareResult.IsMatch;
|
|
||||||
}
|
|
||||||
|
|
||||||
return MqttTopicFilterCompareResult.NoMatch;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return MqttTopicFilterCompareResult.NoMatch;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,31 +1,27 @@
|
|||||||
using System;
|
namespace MQTTnet.Benchmarks;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace MQTTnet.Benchmarks
|
public class TopicGenerator
|
||||||
{
|
{
|
||||||
public class TopicGenerator
|
|
||||||
{
|
|
||||||
public static void Generate(
|
public static void Generate(
|
||||||
int numPublishers, int numTopicsPerPublisher,
|
int numPublishers,
|
||||||
|
int numTopicsPerPublisher,
|
||||||
out Dictionary<string, List<string>> topicsByPublisher,
|
out Dictionary<string, List<string>> topicsByPublisher,
|
||||||
out Dictionary<string, List<string>> singleWildcardTopicsByPublisher,
|
out Dictionary<string, List<string>> singleWildcardTopicsByPublisher,
|
||||||
out Dictionary<string, List<string>> multiWildcardTopicsByPublisher
|
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 +31,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 +40,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,7 +74,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,46 +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 BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using MQTTnet.Client;
|
|
||||||
using MQTTnet.Server;
|
using MQTTnet.Server;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace MQTTnet.Benchmarks
|
namespace MQTTnet.Benchmarks;
|
||||||
|
|
||||||
|
[MemoryDiagnoser]
|
||||||
|
public class UnsubscribeBenchmark : BaseBenchmark
|
||||||
{
|
{
|
||||||
[MemoryDiagnoser]
|
|
||||||
public class UnsubscribeBenchmark
|
|
||||||
{
|
|
||||||
MqttServer _mqttServer;
|
|
||||||
IMqttClient _mqttClient;
|
|
||||||
|
|
||||||
const int NumPublishers = 1;
|
const int NumPublishers = 1;
|
||||||
const int NumTopicsPerPublisher = 10000;
|
const int NumTopicsPerPublisher = 10000;
|
||||||
|
|
||||||
|
IMqttClient _mqttClient;
|
||||||
|
MqttServer _mqttServer;
|
||||||
List<string> _topics;
|
List<string> _topics;
|
||||||
|
|
||||||
[GlobalSetup]
|
[GlobalSetup]
|
||||||
public void Setup()
|
public void Setup()
|
||||||
{
|
{
|
||||||
TopicGenerator.Generate(NumPublishers, NumTopicsPerPublisher, out var topicsByPublisher, out var singleWildcardTopicsByPublisher, out var multiWildcardTopicsByPublisher);
|
TopicGenerator.Generate(NumPublishers, NumTopicsPerPublisher, out var topicsByPublisher, out _, out _);
|
||||||
_topics = topicsByPublisher.Values.First();
|
_topics = topicsByPublisher.Values.First();
|
||||||
|
|
||||||
var serverOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
|
var serverOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
|
||||||
|
|
||||||
var factory = new MqttFactory();
|
var serverFactory = new MqttServerFactory();
|
||||||
_mqttServer = factory.CreateMqttServer(serverOptions);
|
_mqttServer = serverFactory.CreateMqttServer(serverOptions);
|
||||||
_mqttClient = factory.CreateMqttClient();
|
var clientFactory = new MqttClientFactory();
|
||||||
|
_mqttClient = clientFactory.CreateMqttClient();
|
||||||
|
|
||||||
_mqttServer.StartAsync().GetAwaiter().GetResult();
|
_mqttServer.StartAsync().GetAwaiter().GetResult();
|
||||||
|
|
||||||
var clientOptions = new MqttClientOptionsBuilder()
|
var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("localhost").Build();
|
||||||
.WithTcpServer("localhost").Build();
|
|
||||||
|
|
||||||
_mqttClient.ConnectAsync(clientOptions).GetAwaiter().GetResult();
|
_mqttClient.ConnectAsync(clientOptions).GetAwaiter().GetResult();
|
||||||
|
|
||||||
foreach (var topic in _topics)
|
foreach (var topic in _topics)
|
||||||
{
|
{
|
||||||
var subscribeOptions = new MqttClientSubscribeOptionsBuilder()
|
var subscribeOptions = new MqttClientSubscribeOptionsBuilder().WithTopicFilter(topic).Build();
|
||||||
.WithTopicFilter(topic)
|
|
||||||
.Build();
|
|
||||||
_mqttClient.SubscribeAsync(subscribeOptions).GetAwaiter().GetResult();
|
_mqttClient.SubscribeAsync(subscribeOptions).GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -50,11 +48,8 @@ namespace MQTTnet.Benchmarks
|
|||||||
{
|
{
|
||||||
foreach (var topic in _topics)
|
foreach (var topic in _topics)
|
||||||
{
|
{
|
||||||
var unsubscribeOptions = new MqttClientUnsubscribeOptionsBuilder()
|
var unsubscribeOptions = new MqttClientUnsubscribeOptionsBuilder().WithTopicFilter(topic).Build();
|
||||||
.WithTopicFilter(topic)
|
|
||||||
.Build();
|
|
||||||
_mqttClient.UnsubscribeAsync(unsubscribeOptions).GetAwaiter().GetResult();
|
_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(bool cleanDisconnect = true);
|
|
||||||
|
|
||||||
Task SubscribeAsync(ICollection<MqttTopicFilter> topicFilters);
|
|
||||||
|
|
||||||
Task UnsubscribeAsync(ICollection<string> topics);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace MQTTnet.Extensions.ManagedClient
|
|
||||||
{
|
|
||||||
public interface IManagedMqttClientStorage
|
|
||||||
{
|
|
||||||
Task SaveQueuedMessagesAsync(IList<ManagedMqttApplicationMessage> messages);
|
|
||||||
|
|
||||||
Task<IList<ManagedMqttApplicationMessage>> LoadQueuedMessagesAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0</TargetFrameworks>
|
|
||||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks>
|
|
||||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
|
|
||||||
|
|
||||||
<AssemblyName>MQTTnet.Extensions.ManagedClient</AssemblyName>
|
|
||||||
<RootNamespace>MQTTnet.Extensions.ManagedClient</RootNamespace>
|
|
||||||
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
|
||||||
<Company>The contributors of MQTTnet</Company>
|
|
||||||
<Product>MQTTnet</Product>
|
|
||||||
<Description>This is an extension library which provides a managed MQTT client with additional features using MQTTnet.</Description>
|
|
||||||
<Authors>The contributors of MQTTnet</Authors>
|
|
||||||
<PackageId>MQTTnet.Extensions.ManagedClient</PackageId>
|
|
||||||
<SignAssembly>false</SignAssembly>
|
|
||||||
<DelaySign>false</DelaySign>
|
|
||||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
|
||||||
<IncludeSymbols>true</IncludeSymbols>
|
|
||||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
|
||||||
<Copyright>Copyright (c) .NET Foundation and Contributors</Copyright>
|
|
||||||
<PackageProjectUrl>https://github.com/dotnet/MQTTnet</PackageProjectUrl>
|
|
||||||
<RepositoryUrl>https://github.com/dotnet/MQTTnet.git</RepositoryUrl>
|
|
||||||
<RepositoryType>git</RepositoryType>
|
|
||||||
<PackageTags>MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin Blazor</PackageTags>
|
|
||||||
<NeutralLanguage>en-US</NeutralLanguage>
|
|
||||||
<EnableNETAnalyzers>false</EnableNETAnalyzers>
|
|
||||||
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
|
|
||||||
<PackageIcon>nuget.png</PackageIcon>
|
|
||||||
<EmbedUntrackedSources>true</EmbedUntrackedSources>
|
|
||||||
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
|
|
||||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
|
||||||
<PackageReleaseNotes>For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).</PackageReleaseNotes>
|
|
||||||
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
|
||||||
<NoWarn>1591</NoWarn>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0'">
|
|
||||||
<CopyLocalLockFileAssemblies>false</CopyLocalLockFileAssemblies>
|
|
||||||
<NugetTargetMoniker>UAP,Version=v10.0</NugetTargetMoniker>
|
|
||||||
<TargetPlatformIdentifier>UAP</TargetPlatformIdentifier>
|
|
||||||
<TargetPlatformVersion>10.0.18362.0</TargetPlatformVersion>
|
|
||||||
<TargetPlatformMinVersion>10.0.10240.0</TargetPlatformMinVersion>
|
|
||||||
<TargetFrameworkIdentifier>.NETCore</TargetFrameworkIdentifier>
|
|
||||||
<TargetFrameworkVersion>v5.0</TargetFrameworkVersion>
|
|
||||||
<DefineConstants>$(DefineConstants);WINDOWS_UWP</DefineConstants>
|
|
||||||
<DefaultLanguage>en</DefaultLanguage>
|
|
||||||
<LanguageTargets>$(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets</LanguageTargets>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<None Include="..\..\Images\nuget.png">
|
|
||||||
<Pack>True</Pack>
|
|
||||||
<PackagePath>\</PackagePath>
|
|
||||||
</None>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\MQTTnet\MQTTnet.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace MQTTnet.Extensions.ManagedClient
|
|
||||||
{
|
|
||||||
public class ManagedMqttApplicationMessage
|
|
||||||
{
|
|
||||||
public Guid Id { get; set; } = Guid.NewGuid();
|
|
||||||
|
|
||||||
public MqttApplicationMessage ApplicationMessage { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user