mirror of
https://github.com/dotnet/MQTTnet.git
synced 2026-10-15 00:15:15 +08:00
Compare commits
141
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
25b42df85c | ||
|
|
f8d614e71f | ||
|
|
2aa4219838 | ||
|
|
b98417ab39 | ||
|
|
7bb29d3c92 | ||
|
|
4d81d3f3d9 | ||
|
|
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 | ||
|
|
3937aa76e3 | ||
|
|
2090b08547 | ||
|
|
e88b280eb0 | ||
|
|
e4b07f2b8c | ||
|
|
4eb6e77eb5 | ||
|
|
db1fc7625d | ||
|
|
bfec9dc5ad | ||
|
|
56f0a2c31c | ||
|
|
5ee0bb4b65 | ||
|
|
5c74728387 | ||
|
|
97c46bea0f | ||
|
|
e6c1ab4e4c | ||
|
|
6e1ec266a7 | ||
|
|
3fc6982e9a | ||
|
|
5b1cc7d713 | ||
|
|
2842f27522 | ||
|
|
7ef137e26e | ||
|
|
b1a336eb62 | ||
|
|
d481e93832 | ||
|
|
8cf5537043 | ||
|
|
d2ce9d607a | ||
|
|
36736914bc | ||
|
|
7823d8342e | ||
|
|
431142471f | ||
|
|
a3227a444a | ||
|
|
f74af45fba | ||
|
|
7c3fad6147 | ||
|
|
e050e2d61e | ||
|
|
ef85ef676a | ||
|
|
e977546297 | ||
|
|
ae92b0ed33 | ||
|
|
7e686d9aa5 | ||
|
|
d23bbf7bf3 | ||
|
|
d4dad4860a | ||
|
|
c009cbef76 | ||
|
|
37e60c73ae | ||
|
|
3ab095a568 | ||
|
|
f5d9b65139 | ||
|
|
31e5b40459 | ||
|
|
8fb55d75fe | ||
|
|
420222511b | ||
|
|
c5cec60809 | ||
|
|
12dd815421 | ||
|
|
f36f49cb3e | ||
|
|
9152fe637e | ||
|
|
b976e9c177 | ||
|
|
262a4681ad | ||
|
|
ad546adafe | ||
|
|
29a9941d86 | ||
|
|
5a21939e29 | ||
|
|
469caf656e | ||
|
|
42402fba04 | ||
|
|
a0dc8ab0d7 | ||
|
|
02ede5a2d8 | ||
|
|
cef61900df | ||
|
|
09385b1b36 | ||
|
|
cbf413f04e | ||
|
|
11249d006f | ||
|
|
0d7b020994 | ||
|
|
d71ff3d615 | ||
|
|
57745e8324 | ||
|
|
a94776eeea | ||
|
|
d3848d8cb0 | ||
|
|
6c29ea56fc | ||
|
|
ed7c150cc5 | ||
|
|
13762eedd4 | ||
|
|
e33495010e | ||
|
|
b0d1fcd161 | ||
|
|
f7a739e162 | ||
|
|
422945929e | ||
|
|
76e54dc49b | ||
|
|
b10a69a0ae | ||
|
|
4b1c7cf772 | ||
|
|
3f67df007b | ||
|
|
36b1ed06f0 | ||
|
|
4a982ea228 | ||
|
|
9fe5073186 | ||
|
|
305c552474 | ||
|
|
f632fbb447 | ||
|
|
8231d62f9f | ||
|
|
a52a717965 | ||
|
|
2b653f2379 | ||
|
|
725cfb643f | ||
|
|
29ec6edbf4 | ||
|
|
f3ef2dbf7c | ||
|
|
14eb202873 |
@@ -14,6 +14,5 @@ A clear and concise description of what you want to know.
|
||||
<!-- Remove the items which don't apply from the following list -->
|
||||
- Client
|
||||
- ManagedClient
|
||||
- MQTTnet.Server standalone
|
||||
- Server
|
||||
- Generic
|
||||
|
||||
@@ -15,7 +15,6 @@ A clear and concise description of what the problem is. Example. I'm am trying t
|
||||
<!-- Remove the items which don't apply from the following list -->
|
||||
- Client
|
||||
- ManagedClient
|
||||
- MQTTnet.Server standalone
|
||||
- Server
|
||||
- Generic
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
* [Client] Added the flag _try_private_ (MQTT 3.1.1 Bridge) to client options (#1413).
|
||||
* [Client] Fixed MQTTv5 protocol violation in PUBLISH packets (#1423).
|
||||
* [Client] Added missing "WithWill..." methods in _MqttClientOptionsBuilder_.
|
||||
* [ManagedClient] Fixed wrong event args type for connected and disconnected events (#1432).
|
||||
* [Server] Fixed wrong handling of retained messages with QoS > 0 (#1434).
|
||||
* [Server] Connections with _try_private_ flag (MQTT 3.1.1 Bridge) are now accepted (#1413).
|
||||
* [Core] Added net7.0 builds (#1635, thanks to @YAJeff).
|
||||
* [Client] Added support for passing MQTT v5 options (User properties etc.) for disconnects.
|
||||
* [Client] An internal exception is no longer caught silently when calling _DisconnectAsync_ to indicate that the disconnect is not clean (BREAKING CHANGE).
|
||||
* [Client] MQTTv5 features are now checked and an exception is thrown if they are used when using protocol version 3.1.1 and lower. These checks can be disabled in client options. (BREAKING CHANGE!).
|
||||
* [Server] Exposed MQTT v5 sent properties from the affected client in _ClientDisconnectedAsync_ event.
|
||||
* [Server] Fixed wrong client ID passed to _InterceptingUnsubscriptionEventArgs_ (#1631, thanks to @ghord).
|
||||
* [Server] Exposed socket settings for TCP keep alive in TCP options (#1544).
|
||||
* [Server] Exposed server session items at server level and allow custom session items for injected application messages (#1638).
|
||||
|
||||
@@ -3,7 +3,7 @@ name: CI
|
||||
on: [push, pull_request]
|
||||
|
||||
env:
|
||||
VERSION: "4.0.1.${{github.run_number}}"
|
||||
VERSION: "4.2.0.${{github.run_number}}"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -22,6 +22,7 @@ jobs:
|
||||
dotnet-version: |
|
||||
3.1.x
|
||||
6.0.x
|
||||
7.0.x
|
||||
|
||||
- name: Setup MSBuild
|
||||
uses: microsoft/setup-msbuild@v1.1
|
||||
@@ -60,8 +61,4 @@ jobs:
|
||||
|
||||
- name: Publish MyGet nugets
|
||||
if: ${{ github.event_name == 'push' }}
|
||||
run: dotnet nuget push **/*.nupkg -k ${{ secrets.MYGET_API_KEY }} -s https://www.myget.org/F/mqttnet/api/v3/index.json --skip-duplicate
|
||||
|
||||
# - name: Publish nugets
|
||||
# if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
|
||||
# run: dotnet nuget push **/*.nupkg -k ${{ secrets.NUGET_API_KEY }} -s https://api.nuget.org/v3/index.json --skip-duplicate
|
||||
run: dotnet nuget push **/*.nupkg -k ${{ secrets.MYGET_API_KEY }} -s https://www.myget.org/F/mqttnet/api/v3/index.json --skip-duplicate
|
||||
+83
-3
@@ -1,4 +1,67 @@
|
||||
<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/CodeStyle/CodeCleanup/Profiles/=MQTTnet/@EntryIndexedValue"><?xml version="1.0" encoding="utf-16"?><Profile name="MQTTnet"><CppReformatCode>True</CppReformatCode><ShaderLabReformatCode>True</ShaderLabReformatCode><XMLReformatCode>True</XMLReformatCode><HtmlReformatCode>True</HtmlReformatCode><VBReformatCode>True</VBReformatCode><CSReformatCode>True</CSReformatCode><CSCodeStyleAttributes ArrangeVarStyle="True" ArrangeTypeAccessModifier="True" ArrangeTypeMemberAccessModifier="True" SortModifiers="True" ArrangeArgumentsStyle="True" RemoveRedundantParentheses="True" AddMissingParentheses="True" ArrangeBraces="True" ArrangeAttributes="True" ArrangeCodeBodyStyle="True" ArrangeTrailingCommas="True" ArrangeObjectCreation="True" ArrangeDefaultValue="True" ArrangeNamespaces="True" /><CSArrangeQualifiers>True</CSArrangeQualifiers><CSFixBuiltinTypeReferences>True</CSFixBuiltinTypeReferences><CppCodeStyleCleanupDescriptor ArrangeBraces="True" ArrangeAuto="True" ArrangeFunctionDeclarations="True" ArrangeNestedNamespaces="True" ArrangeTypeAliases="True" ArrangeCVQualifiers="True" ArrangeSlashesInIncludeDirectives="True" ArrangeOverridingFunctions="True" SortIncludeDirectives="True" SortMemberInitializers="True" /><FormatAttributeQuoteDescriptor>True</FormatAttributeQuoteDescriptor><CSUpdateFileHeader>True</CSUpdateFileHeader><CppUpdateFileHeader>True</CppUpdateFileHeader><IDEA_SETTINGS>&lt;profile version="1.0"&gt;
|
||||
&lt;option name="myName" value="MQTTnet" /&gt;
|
||||
&lt;inspection_tool class="ConditionalExpressionWithIdenticalBranchesJS" enabled="true" level="WARNING" enabled_by_default="true" /&gt;
|
||||
&lt;inspection_tool class="ES6ShorthandObjectProperty" enabled="true" level="WARNING" enabled_by_default="true" /&gt;
|
||||
&lt;inspection_tool class="JSArrowFunctionBracesCanBeRemoved" enabled="true" level="WARNING" enabled_by_default="true" /&gt;
|
||||
&lt;inspection_tool class="JSRemoveUnnecessaryParentheses" enabled="true" level="WARNING" enabled_by_default="true" /&gt;
|
||||
&lt;inspection_tool class="TypeScriptExplicitMemberType" enabled="true" level="WARNING" enabled_by_default="true" /&gt;
|
||||
&lt;inspection_tool class="UnterminatedStatementJS" enabled="true" level="WARNING" enabled_by_default="true" /&gt;
|
||||
&lt;inspection_tool class="WrongPropertyKeyValueDelimiter" enabled="true" level="WARNING" enabled_by_default="true" /&gt;
|
||||
&lt;/profile&gt;</IDEA_SETTINGS><RIDER_SETTINGS>&lt;profile&gt;
|
||||
&lt;Language id="CSS"&gt;
|
||||
&lt;Reformat&gt;true&lt;/Reformat&gt;
|
||||
&lt;Rearrange&gt;true&lt;/Rearrange&gt;
|
||||
&lt;/Language&gt;
|
||||
&lt;Language id="EditorConfig"&gt;
|
||||
&lt;Reformat&gt;true&lt;/Reformat&gt;
|
||||
&lt;/Language&gt;
|
||||
&lt;Language id="HTML"&gt;
|
||||
&lt;Reformat&gt;true&lt;/Reformat&gt;
|
||||
&lt;Rearrange&gt;true&lt;/Rearrange&gt;
|
||||
&lt;OptimizeImports&gt;true&lt;/OptimizeImports&gt;
|
||||
&lt;/Language&gt;
|
||||
&lt;Language id="HTTP Request"&gt;
|
||||
&lt;Reformat&gt;true&lt;/Reformat&gt;
|
||||
&lt;/Language&gt;
|
||||
&lt;Language id="Handlebars"&gt;
|
||||
&lt;Reformat&gt;true&lt;/Reformat&gt;
|
||||
&lt;/Language&gt;
|
||||
&lt;Language id="Ini"&gt;
|
||||
&lt;Reformat&gt;true&lt;/Reformat&gt;
|
||||
&lt;/Language&gt;
|
||||
&lt;Language id="JSON"&gt;
|
||||
&lt;Reformat&gt;true&lt;/Reformat&gt;
|
||||
&lt;/Language&gt;
|
||||
&lt;Language id="Jade"&gt;
|
||||
&lt;Reformat&gt;true&lt;/Reformat&gt;
|
||||
&lt;/Language&gt;
|
||||
&lt;Language id="JavaScript"&gt;
|
||||
&lt;Reformat&gt;true&lt;/Reformat&gt;
|
||||
&lt;Rearrange&gt;true&lt;/Rearrange&gt;
|
||||
&lt;OptimizeImports&gt;true&lt;/OptimizeImports&gt;
|
||||
&lt;/Language&gt;
|
||||
&lt;Language id="Markdown"&gt;
|
||||
&lt;Reformat&gt;true&lt;/Reformat&gt;
|
||||
&lt;/Language&gt;
|
||||
&lt;Language id="Properties"&gt;
|
||||
&lt;Reformat&gt;true&lt;/Reformat&gt;
|
||||
&lt;/Language&gt;
|
||||
&lt;Language id="RELAX-NG"&gt;
|
||||
&lt;Reformat&gt;true&lt;/Reformat&gt;
|
||||
&lt;/Language&gt;
|
||||
&lt;Language id="SQL"&gt;
|
||||
&lt;Reformat&gt;true&lt;/Reformat&gt;
|
||||
&lt;/Language&gt;
|
||||
&lt;Language id="XML"&gt;
|
||||
&lt;Reformat&gt;true&lt;/Reformat&gt;
|
||||
&lt;Rearrange&gt;true&lt;/Rearrange&gt;
|
||||
&lt;OptimizeImports&gt;true&lt;/OptimizeImports&gt;
|
||||
&lt;/Language&gt;
|
||||
&lt;Language id="yaml"&gt;
|
||||
&lt;Reformat&gt;true&lt;/Reformat&gt;
|
||||
&lt;/Language&gt;
|
||||
&lt;/profile&gt;</RIDER_SETTINGS><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><VBOptimizeImports>True</VBOptimizeImports><VBShortenReferences>True</VBShortenReferences><Xaml.RemoveRedundantNamespaceAlias>True</Xaml.RemoveRedundantNamespaceAlias><AspOptimizeRegisterDirectives>True</AspOptimizeRegisterDirectives><CSReorderTypeMembers>True</CSReorderTypeMembers><RemoveCodeRedundancies>True</RemoveCodeRedundancies><CSUseAutoProperty>True</CSUseAutoProperty><CSMakeFieldReadonly>True</CSMakeFieldReadonly><CSMakeAutoPropertyGetOnly>True</CSMakeAutoPropertyGetOnly><CppAddTypenameTemplateKeywords>True</CppAddTypenameTemplateKeywords><CppJoinDeclarationAndAssignmentDescriptor>True</CppJoinDeclarationAndAssignmentDescriptor><CppMakeLocalVarConstDescriptor>True</CppMakeLocalVarConstDescriptor><CppMakeMethodConst>True</CppMakeMethodConst><CppMakeMethodStatic>True</CppMakeMethodStatic><CppRemoveCastDescriptor>True</CppRemoveCastDescriptor><CppRemoveElseKeyword>True</CppRemoveElseKeyword><CppRemoveElaboratedTypeSpecifierDescriptor>True</CppRemoveElaboratedTypeSpecifierDescriptor><CppRemoveRedundantLambdaParameterListDescriptor>True</CppRemoveRedundantLambdaParameterListDescriptor><CppRemoveRedundantMemberInitializerDescriptor>True</CppRemoveRedundantMemberInitializerDescriptor><CppRemoveRedundantParentheses>True</CppRemoveRedundantParentheses><CppShortenQualifiedName>True</CppShortenQualifiedName><CppDeleteRedundantSpecifier>True</CppDeleteRedundantSpecifier><CppRemoveStatement>True</CppRemoveStatement><CppRemoveTemplateArgumentsDescriptor>True</CppRemoveTemplateArgumentsDescriptor><CppDeleteRedundantTypenameTemplateKeywords>True</CppDeleteRedundantTypenameTemplateKeywords><CppRemoveUnreachableCode>True</CppRemoveUnreachableCode><CppRemoveUnusedIncludes>True</CppRemoveUnusedIncludes><CppRemoveUnusedLambdaCaptures>True</CppRemoveUnusedLambdaCaptures><CppCStyleToStaticCastDescriptor>True</CppCStyleToStaticCastDescriptor><CppReplaceExpressionWithBooleanConst>True</CppReplaceExpressionWithBooleanConst><CppMakeIfConstexpr>True</CppMakeIfConstexpr><CppMakePostfixOperatorPrefix>True</CppMakePostfixOperatorPrefix><CppChangeSmartPointerToMakeFunction>True</CppChangeSmartPointerToMakeFunction><CppReplaceThrowWithRethrowFix>True</CppReplaceThrowWithRethrowFix><CppTypeTraitAliasDescriptor>True</CppTypeTraitAliasDescriptor><CppReplaceExpressionWithNullptr>True</CppReplaceExpressionWithNullptr><CppReplaceTieWithStructuredBindingDescriptor>True</CppReplaceTieWithStructuredBindingDescriptor><CppUseAssociativeContainsDescriptor>True</CppUseAssociativeContainsDescriptor><CppUseEraseAlgorithmDescriptor>True</CppUseEraseAlgorithmDescriptor><RemoveCodeRedundanciesVB>True</RemoveCodeRedundanciesVB><Xaml.RedundantFreezeAttribute>True</Xaml.RedundantFreezeAttribute><Xaml.RemoveRedundantModifiersAttribute>True</Xaml.RemoveRedundantModifiersAttribute><Xaml.RemoveRedundantNameAttribute>True</Xaml.RemoveRedundantNameAttribute><Xaml.RemoveRedundantResource>True</Xaml.RemoveRedundantResource><Xaml.RemoveRedundantCollectionProperty>True</Xaml.RemoveRedundantCollectionProperty><Xaml.RemoveRedundantAttachedPropertySetter>True</Xaml.RemoveRedundantAttachedPropertySetter><Xaml.RemoveRedundantStyledValue>True</Xaml.RemoveRedundantStyledValue><Xaml.RemoveForbiddenResourceName>True</Xaml.RemoveForbiddenResourceName><Xaml.RemoveRedundantGridDefinitionsAttribute>True</Xaml.RemoveRedundantGridDefinitionsAttribute><Xaml.RemoveRedundantUpdateSourceTriggerAttribute>True</Xaml.RemoveRedundantUpdateSourceTriggerAttribute><Xaml.RemoveRedundantBindingModeAttribute>True</Xaml.RemoveRedundantBindingModeAttribute><Xaml.RemoveRedundantGridSpanAttribut>True</Xaml.RemoveRedundantGridSpanAttribut><CSharpFormatDocComments>True</CSharpFormatDocComments><VBFormatDocComments>True</VBFormatDocComments></Profile></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_FOR/@EntryValue">Required</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_FOREACH/@EntryValue">Required</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_IFELSE/@EntryValue">Required</s:String>
|
||||
@@ -8,28 +71,33 @@
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_EXISTING_INITIALIZER_ARRANGEMENT/@EntryValue">False</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_EXISTING_INVOCATION_PARENS_ARRANGEMENT/@EntryValue">False</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_EXISTING_PROPERTY_PATTERNS_ARRANGEMENT/@EntryValue">False</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_EXISTING_SWITCH_EXPRESSION_ARRANGEMENT/@EntryValue">False</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_USER_LINEBREAKS/@EntryValue">False</s:Boolean>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_ACCESSORHOLDER_ATTRIBUTE_ON_SAME_LINE_EX/@EntryValue">NEVER</s:String>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_SIMPLE_ACCESSOR_ON_SINGLE_LINE/@EntryValue">False</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_SIMPLE_ANONYMOUSMETHOD_ON_SINGLE_LINE/@EntryValue">False</s:Boolean>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_SIMPLE_EMBEDDED_STATEMENT_ON_SAME_LINE/@EntryValue">NEVER</s:String>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_SIMPLE_INITIALIZER_ON_SINGLE_LINE/@EntryValue">False</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_SIMPLE_PROPERTY_PATTERN_ON_SINGLE_LINE/@EntryValue">False</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_WHILE_ON_NEW_LINE/@EntryValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_WITHIN_METHOD_CALL_PARENTHESES/@EntryValue">False</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_WITHIN_METHOD_PARENTHESES/@EntryValue">False</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_WITHIN_PARENTHESES/@EntryValue">False</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_AFTER_DECLARATION_LPAR/@EntryValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_AFTER_INVOCATION_LPAR/@EntryValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_ARGUMENTS_STYLE/@EntryValue">CHOP_IF_LONG</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_ARRAY_INITIALIZER_STYLE/@EntryValue">CHOP_IF_LONG</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_ARRAY_INITIALIZER_STYLE/@EntryValue">CHOP_ALWAYS</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_CHAINED_METHOD_CALLS/@EntryValue">CHOP_IF_LONG</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_EXTENDS_LIST_STYLE/@EntryValue">CHOP_IF_LONG</s:String>
|
||||
<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_LIMIT/@EntryValue">180</s:Int64>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_OBJECT_AND_COLLECTION_INITIALIZER_STYLE/@EntryValue">CHOP_ALWAYS</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_PARAMETERS_STYLE/@EntryValue">CHOP_IF_LONG</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_PROPERTY_PATTERN/@EntryValue">CHOP_ALWAYS</s:String>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/XmlDocFormatter/KeepUserLineBreaks/@EntryValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/XmlDocFormatter/ProcessingInstructionAttributesFormat/@EntryValue">OnDifferentLines</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/XmlDocFormatter/TagAttributesFormat/@EntryValue">OnDifferentLines</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CSharpFileLayoutPatterns/Pattern/@EntryValue"><?xml version="1.0" encoding="utf-16"?>
|
||||
<Patterns xmlns="urn:schemas-jetbrains-com:member-reordering-patterns">
|
||||
<Patterns xmlns="urn:schemas-jetbrains-com:member-reordering-patterns">
|
||||
<TypePattern DisplayName="Non-reorderable types">
|
||||
<TypePattern.Match>
|
||||
<Or>
|
||||
@@ -210,6 +278,7 @@
|
||||
</Or>
|
||||
</Entry.Match>
|
||||
<Entry.SortBy>
|
||||
<Access Order="Public Internal Protected Private" />
|
||||
<Name />
|
||||
</Entry.SortBy>
|
||||
</Entry>
|
||||
@@ -226,6 +295,9 @@
|
||||
</Entry>
|
||||
</TypePattern>
|
||||
</Patterns></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/FileHeader/FileHeaderText/@EntryValue">Licensed to the .NET Foundation under one or more agreements.
|
||||
The .NET Foundation licenses this file to you under the MIT license.
|
||||
See the LICENSE file in the project root for more information.</s:String>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpAttributeForSingleLineMethodUpgrade/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
|
||||
@@ -236,6 +308,14 @@
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002ECSharpPlaceAttributeOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=CONNACK/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=PINGREQ/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=PINGRESP/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=PUBACK/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=PUBCOMP/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=PUBREC/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=PUBREL/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=SUBACK/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Tnet/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=unsub/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=unsub/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=UNSUBACK/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||
@@ -4,7 +4,7 @@
|
||||
</p>
|
||||
|
||||
[](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://gitter.im/MQTTnet/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
# MQTTnet
|
||||
|
||||
MQTTnet is a high performance .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker) and supports the MQTT protocol up to version 5.
|
||||
MQTTnet is a high performance .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker) and supports the MQTT protocol up to version 5. It is compatible with mostly any supported .NET Framework version and CPU architecture.
|
||||
|
||||
## Features
|
||||
|
||||
@@ -50,39 +50,6 @@ 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
|
||||
* Validate subscriptions and deny subscribing of certain topics depending on requesting clients
|
||||
|
||||
## Supported frameworks
|
||||
|
||||
| Framwork | Version |
|
||||
| ------------------ | ----------- |
|
||||
|.NET | 5.0+ |
|
||||
|.NET Framework | 4.5.2+ |
|
||||
|.NET Standard | 1.3+ |
|
||||
|.NET Core | 1.1+ |
|
||||
|.NET Core App | 1.1+ |
|
||||
| Mono | 5.2+ |
|
||||
| UWP | 10.0.10240+ |
|
||||
| Xamarin.Android | 7.5+ |
|
||||
| Xamarin.iOS | 10.14+ |
|
||||
| Blazor WebAssembly | 3.2.0+ |
|
||||
|
||||
## References
|
||||
|
||||
This library is used in the following projects:
|
||||
|
||||
* Azure Functions MQTT Bindings, <https://github.com/keesschollaart81/CaseOnline.Azure.WebJobs.Extensions.Mqtt>)
|
||||
* HA4IoT (Open Source Home Automation system for .NET, <https://github.com/chkr1011/HA4IoT>)
|
||||
* MQTT Client Rx (Wrapper for Reactive Extensions, <https://github.com/1iveowl/MQTTClient.rx>)
|
||||
* MQTT Client Rx (Managed Client Wrapper for Reactive Extensions, <https://github.com/mmuecke/RxMQTTnet>)
|
||||
* MQTT Tester (MQTT client test app for [Android](https://play.google.com/store/apps/details?id=com.liveowl.mqtttester) and [iOS](https://itunes.apple.com/us/app/mqtt-tester/id1278621826?mt=8))
|
||||
* MQTTnet App (Cross platform client application for MQTT debugging, inspection etc., <https://github.com/chkr1011/MQTTnetApp>)
|
||||
* Wirehome.Core (Open Source Home Automation system for .NET Core, <https://github.com/chkr1011/Wirehome.Core>)
|
||||
* SparkplugNet (Sparkplug library for .Net, <https://github.com/SeppPenner/SparkplugNet>)
|
||||
* Silverback (Framework to build event-driven applications - support for MQTT, Kafka & RabbitMQ) <https://github.com/BEagle1984/silverback>
|
||||
|
||||
Further projects using this project can be found under https://github.com/dotnet/MQTTnet/network/dependents.
|
||||
|
||||
If you use this library and want to see your project here please create a pull request.
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
This project has adopted the code of conduct defined by the Contributor Covenant to clarify expected behavior in our community.
|
||||
|
||||
@@ -2,8 +2,13 @@
|
||||
// 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.Security.Authentication;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Extensions.WebSocket4Net;
|
||||
using MQTTnet.Formatter;
|
||||
using MQTTnet.Samples.Helpers;
|
||||
|
||||
@@ -11,6 +16,28 @@ namespace MQTTnet.Samples.Client;
|
||||
|
||||
public static class Client_Connection_Samples
|
||||
{
|
||||
public static async Task Clean_Disconnect()
|
||||
{
|
||||
/*
|
||||
* This sample disconnects in a clean way. This will send a MQTT DISCONNECT packet
|
||||
* to the server and close the connection afterwards.
|
||||
*
|
||||
* See sample _Connect_Client_ for more details.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttFactory();
|
||||
|
||||
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_Client()
|
||||
{
|
||||
/*
|
||||
@@ -111,13 +138,45 @@ public static class Client_Connection_Samples
|
||||
.WithTls(
|
||||
o =>
|
||||
{
|
||||
// The used public broker sometimes has invalid certificates. This sample accepts all
|
||||
// certificates. This should not be used in live environments.
|
||||
o.CertificateValidationHandler = _ => true;
|
||||
|
||||
// The default value is determined by the OS. Set manually to force version.
|
||||
o.SslProtocol = SslProtocols.Tls12;
|
||||
})
|
||||
.Build();
|
||||
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
using (var timeout = new CancellationTokenSource(5000))
|
||||
{
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, timeout.Token);
|
||||
|
||||
Console.WriteLine("The MQTT client is connected.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task Connect_Client_Using_WebSocket4Net()
|
||||
{
|
||||
/*
|
||||
* This sample creates a simple MQTT client and connects to a public broker using a WebSocket connection.
|
||||
* Instead of the .NET implementation of WebSockets the implementation from WebSocket4Net is used. It provides more
|
||||
* encryption algorithms and supports more platforms.
|
||||
*
|
||||
* This is a modified version of the sample _Connect_Client_! See other sample for more details.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttFactory().UseWebSocket4Net();
|
||||
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
{
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithWebSocketServer("broker.hivemq.com:8000/mqtt").Build();
|
||||
|
||||
var response = await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
Console.WriteLine("The MQTT client is connected.");
|
||||
|
||||
response.DumpToConsole();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,16 +218,99 @@ public static class Client_Connection_Samples
|
||||
.WithTls(
|
||||
o =>
|
||||
{
|
||||
o.SslProtocol = SslProtocols.Tls12; // The default value is determined by the OS. Set manually to force version.
|
||||
// The used public broker sometimes has invalid certificates. This sample accepts all
|
||||
// certificates. This should not be used in live environments.
|
||||
o.CertificateValidationHandler = _ => true;
|
||||
})
|
||||
.Build();
|
||||
|
||||
// In MQTTv5 the response contains much more information.
|
||||
var response = await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
using (var timeout = new CancellationTokenSource(5000))
|
||||
{
|
||||
var response = await mqttClient.ConnectAsync(mqttClientOptions, timeout.Token);
|
||||
|
||||
Console.WriteLine("The MQTT client is connected.");
|
||||
Console.WriteLine("The MQTT client is connected.");
|
||||
|
||||
response.DumpToConsole();
|
||||
response.DumpToConsole();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task Disconnect_Clean()
|
||||
{
|
||||
/*
|
||||
* This sample disconnects from the server with sending a DISCONNECT packet.
|
||||
* This way of disconnecting is treated as a clean disconnect which will not
|
||||
* trigger sending the last will etc.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttFactory();
|
||||
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
{
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
// Calling _DisconnectAsync_ will send a DISCONNECT packet before closing the connection.
|
||||
// Using a reason code requires MQTT version 5.0.0!
|
||||
await mqttClient.DisconnectAsync(MqttClientDisconnectOptionsReason.ImplementationSpecificError);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task Disconnect_Non_Clean()
|
||||
{
|
||||
/*
|
||||
* This sample disconnects from the server without sending a DISCONNECT packet.
|
||||
* This way of disconnecting is treated as a non clean disconnect which will
|
||||
* trigger sending the last will etc.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttFactory();
|
||||
|
||||
var mqttClient = mqttFactory.CreateMqttClient();
|
||||
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
// Calling _Dispose_ or use of a _using_ statement will close the transport connection
|
||||
// without sending a DISCONNECT packet to the server.
|
||||
mqttClient.Dispose();
|
||||
}
|
||||
|
||||
public static async Task Inspect_Certificate_Validation_Errors()
|
||||
{
|
||||
/*
|
||||
* This sample prints the certificate information while connection. This data can be used to decide whether a connection is secure or not
|
||||
* including the reason for that status.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttFactory();
|
||||
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
{
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("mqtt.fluux.io", 8883)
|
||||
.WithTls(
|
||||
o =>
|
||||
{
|
||||
o.CertificateValidationHandler = eventArgs =>
|
||||
{
|
||||
eventArgs.Certificate.Subject.DumpToConsole();
|
||||
eventArgs.Certificate.GetExpirationDateString().DumpToConsole();
|
||||
eventArgs.Chain.ChainPolicy.RevocationMode.DumpToConsole();
|
||||
eventArgs.Chain.ChainStatus.DumpToConsole();
|
||||
eventArgs.SslPolicyErrors.DumpToConsole();
|
||||
return true;
|
||||
};
|
||||
})
|
||||
.Build();
|
||||
|
||||
// In MQTTv5 the response contains much more information.
|
||||
using (var timeout = new CancellationTokenSource(5000))
|
||||
{
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, timeout.Token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,7 +319,7 @@ public static class Client_Connection_Samples
|
||||
/*
|
||||
* 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();
|
||||
@@ -244,14 +386,12 @@ public static class Client_Connection_Samples
|
||||
{
|
||||
try
|
||||
{
|
||||
// This code will also do the very first connect! So no call to _ConnectAsync_ is required
|
||||
// in the first place.
|
||||
if (!mqttClient.IsConnected)
|
||||
// This code will also do the very first connect! So no call to _ConnectAsync_ is required in the first place.
|
||||
if (!await mqttClient.TryPingAsync())
|
||||
{
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
// Subscribe to topics when session is clean etc.
|
||||
|
||||
Console.WriteLine("The MQTT client is connected.");
|
||||
}
|
||||
}
|
||||
@@ -268,4 +408,4 @@ public static class Client_Connection_Samples
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
// 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;
|
||||
|
||||
namespace MQTTnet.Samples.Client;
|
||||
@@ -36,6 +40,53 @@ public static class Client_Publish_Samples
|
||||
|
||||
await mqttClient.PublishAsync(applicationMessage, CancellationToken.None);
|
||||
|
||||
await mqttClient.DisconnectAsync();
|
||||
|
||||
Console.WriteLine("MQTT application message is published.");
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task Publish_Multiple_Application_Messages()
|
||||
{
|
||||
/*
|
||||
* This sample pushes multiple simple application message including a topic and a payload.
|
||||
*
|
||||
* See sample _Publish_Application_Message_ for more details.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttFactory();
|
||||
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
{
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder()
|
||||
.WithTcpServer("broker.hivemq.com")
|
||||
.Build();
|
||||
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
var applicationMessage = new MqttApplicationMessageBuilder()
|
||||
.WithTopic("samples/temperature/living_room")
|
||||
.WithPayload("19.5")
|
||||
.Build();
|
||||
|
||||
await mqttClient.PublishAsync(applicationMessage, CancellationToken.None);
|
||||
|
||||
applicationMessage = new MqttApplicationMessageBuilder()
|
||||
.WithTopic("samples/temperature/living_room")
|
||||
.WithPayload("20.0")
|
||||
.Build();
|
||||
|
||||
await mqttClient.PublishAsync(applicationMessage, CancellationToken.None);
|
||||
|
||||
applicationMessage = new MqttApplicationMessageBuilder()
|
||||
.WithTopic("samples/temperature/living_room")
|
||||
.WithPayload("21.0")
|
||||
.Build();
|
||||
|
||||
await mqttClient.PublishAsync(applicationMessage, CancellationToken.None);
|
||||
|
||||
await mqttClient.DisconnectAsync();
|
||||
|
||||
Console.WriteLine("MQTT application message is published.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,13 @@
|
||||
// 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.Packets;
|
||||
using MQTTnet.Protocol;
|
||||
using MQTTnet.Samples.Helpers;
|
||||
|
||||
namespace MQTTnet.Samples.Client;
|
||||
@@ -19,9 +25,7 @@ public static class Client_Subscribe_Samples
|
||||
|
||||
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
|
||||
// are also handled properly. When there is no event handler attached all
|
||||
@@ -37,7 +41,11 @@ public static class Client_Subscribe_Samples
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder()
|
||||
.WithTopicFilter(f => { f.WithTopic("mqttnet/samples/topic/2"); })
|
||||
.WithTopicFilter(
|
||||
f =>
|
||||
{
|
||||
f.WithTopic("mqttnet/samples/topic/2");
|
||||
})
|
||||
.Build();
|
||||
|
||||
await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None);
|
||||
@@ -49,6 +57,95 @@ public static class Client_Subscribe_Samples
|
||||
}
|
||||
}
|
||||
|
||||
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!
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttFactory();
|
||||
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
{
|
||||
mqttClient.ApplicationMessageReceivedAsync += delegate(MqttApplicationMessageReceivedEventArgs args)
|
||||
{
|
||||
// Do some work with the message...
|
||||
|
||||
// Now respond to the broker with a reason code other than success.
|
||||
args.ReasonCode = MqttApplicationMessageReceivedReasonCode.ImplementationSpecificError;
|
||||
args.ResponseReasonString = "That did not work!";
|
||||
|
||||
// User properties require MQTT v5!
|
||||
args.ResponseUserProperties.Add(new MqttUserProperty("My", "Data"));
|
||||
|
||||
// Now the broker will resend the message again.
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder()
|
||||
.WithTopicFilter(
|
||||
f =>
|
||||
{
|
||||
f.WithTopic("mqttnet/samples/topic/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();
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task Subscribe_Multiple_Topics()
|
||||
{
|
||||
/*
|
||||
* This sample subscribes to several topics in a single request.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttFactory();
|
||||
|
||||
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()
|
||||
.WithTopicFilter(
|
||||
f =>
|
||||
{
|
||||
f.WithTopic("mqttnet/samples/topic/1");
|
||||
})
|
||||
.WithTopicFilter(
|
||||
f =>
|
||||
{
|
||||
f.WithTopic("mqttnet/samples/topic/2").WithNoLocal();
|
||||
})
|
||||
.WithTopicFilter(
|
||||
f =>
|
||||
{
|
||||
f.WithTopic("mqttnet/samples/topic/3").WithRetainHandling(MqttRetainHandling.SendAtSubscribe);
|
||||
})
|
||||
.Build();
|
||||
|
||||
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()
|
||||
{
|
||||
/*
|
||||
@@ -59,14 +156,16 @@ public static class Client_Subscribe_Samples
|
||||
|
||||
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);
|
||||
|
||||
var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder()
|
||||
.WithTopicFilter(f => { f.WithTopic("mqttnet/samples/topic/1"); })
|
||||
.WithTopicFilter(
|
||||
f =>
|
||||
{
|
||||
f.WithTopic("mqttnet/samples/topic/1");
|
||||
})
|
||||
.Build();
|
||||
|
||||
var response = await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None);
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
// 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.Text;
|
||||
using MQTTnet.Diagnostics;
|
||||
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
// 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.Diagnostics;
|
||||
|
||||
namespace MQTTnet.Samples.Diagnostics;
|
||||
|
||||
@@ -9,14 +9,11 @@
|
||||
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
||||
<EnableNETAnalyzers>false</EnableNETAnalyzers>
|
||||
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
|
||||
<NoWarn>1591</NoWarn>
|
||||
<NoWarn>1591;NETSDK1138</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Compilers" Version="4.0.1" />
|
||||
<PackageReference Include="Microsoft.Net.Compilers" Version="4.0.1" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="2.1.2" />
|
||||
|
||||
|
||||
<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" />
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
// 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;
|
||||
|
||||
@@ -36,9 +44,9 @@ public sealed class Managed_Client_Simple_Samples
|
||||
Console.WriteLine("The managed MQTT client is connected.");
|
||||
|
||||
// Wait until the queue is fully processed.
|
||||
SpinWait.SpinUntil(() => managedMqttClient.PendingApplicationMessagesCount == 0, 10000);
|
||||
SpinWait.SpinUntil(() => managedMqttClient.EnqueuedApplicationMessagesCount == 0, 10000);
|
||||
|
||||
Console.WriteLine($"Pending messages = {managedMqttClient.PendingApplicationMessagesCount}");
|
||||
Console.WriteLine($"Pending messages = {managedMqttClient.EnqueuedApplicationMessagesCount}");
|
||||
}
|
||||
}
|
||||
}
|
||||
+9
-3
@@ -39,10 +39,16 @@ Console.WriteLine();
|
||||
|
||||
try
|
||||
{
|
||||
var task = selectedSampleMethod.Invoke(null, null) as Task;
|
||||
task?.Wait();
|
||||
if (selectedSampleMethod.Invoke(null, null) is Task task)
|
||||
{
|
||||
await task;
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Console.WriteLine(exception.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Press Enter to exit.");
|
||||
Console.ReadLine();
|
||||
@@ -2,6 +2,10 @@
|
||||
// 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.Rpc;
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
// ReSharper disable UnusedType.Global
|
||||
// ReSharper disable UnusedMember.Global
|
||||
// ReSharper disable InconsistentNaming
|
||||
// ReSharper disable EmptyConstructor
|
||||
// ReSharper disable MemberCanBeMadeStatic.Local
|
||||
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using MQTTnet.AspNetCore;
|
||||
using MQTTnet.Server;
|
||||
|
||||
namespace MQTTnet.Samples.Server;
|
||||
|
||||
public static class Server_Asp_Net_Samples
|
||||
{
|
||||
public static Task Start_Server_With_WebSockets_Support()
|
||||
{
|
||||
/*
|
||||
* This sample starts a minimal ASP.NET Webserver including a hosted MQTT server.
|
||||
*/
|
||||
var host = Host.CreateDefaultBuilder(Array.Empty<string>())
|
||||
.ConfigureWebHostDefaults(
|
||||
webBuilder =>
|
||||
{
|
||||
webBuilder.UseKestrel(
|
||||
o =>
|
||||
{
|
||||
// This will allow MQTT connections based on TCP port 1883.
|
||||
o.ListenAnyIP(1883, l => l.UseMqtt());
|
||||
|
||||
// This will allow MQTT connections based on HTTP WebSockets with URI "localhost:5000/mqtt"
|
||||
// See code below for URI configuration.
|
||||
o.ListenAnyIP(5000); // Default HTTP pipeline
|
||||
});
|
||||
|
||||
webBuilder.UseStartup<Startup>();
|
||||
});
|
||||
|
||||
return host.RunConsoleAsync();
|
||||
}
|
||||
|
||||
sealed class MqttController
|
||||
{
|
||||
public MqttController()
|
||||
{
|
||||
// Inject other services via constructor.
|
||||
}
|
||||
|
||||
public Task OnClientConnected(ClientConnectedEventArgs eventArgs)
|
||||
{
|
||||
Console.WriteLine($"Client '{eventArgs.ClientId}' connected.");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
|
||||
public Task ValidateConnection(ValidatingConnectionEventArgs eventArgs)
|
||||
{
|
||||
Console.WriteLine($"Client '{eventArgs.ClientId}' wants to connect. Accepting!");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
sealed class Startup
|
||||
{
|
||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment environment, MqttController mqttController)
|
||||
{
|
||||
app.UseRouting();
|
||||
|
||||
app.UseEndpoints(
|
||||
endpoints =>
|
||||
{
|
||||
endpoints.MapConnectionHandler<MqttConnectionHandler>(
|
||||
"/mqtt",
|
||||
httpConnectionDispatcherOptions => httpConnectionDispatcherOptions.WebSockets.SubProtocolSelector =
|
||||
protocolList => protocolList.FirstOrDefault() ?? string.Empty);
|
||||
});
|
||||
|
||||
app.UseMqttServer(
|
||||
server =>
|
||||
{
|
||||
/*
|
||||
* Attach event handlers etc. if required.
|
||||
*/
|
||||
|
||||
server.ValidatingConnectionAsync += mqttController.ValidateConnection;
|
||||
server.ClientConnectedAsync += mqttController.OnClientConnected;
|
||||
});
|
||||
}
|
||||
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddHostedMqttServer(
|
||||
optionsBuilder =>
|
||||
{
|
||||
optionsBuilder.WithDefaultEndpoint();
|
||||
});
|
||||
|
||||
services.AddMqttConnectionHandler();
|
||||
services.AddConnections();
|
||||
|
||||
services.AddSingleton<MqttController>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
// 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 mqttFactory = new MqttFactory();
|
||||
var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
|
||||
|
||||
using (var mqttServer = mqttFactory.CreateMqttServer(mqttServerOptions))
|
||||
{
|
||||
// Attach the event handler.
|
||||
mqttServer.ClientAcknowledgedPublishPacketAsync += e =>
|
||||
{
|
||||
Console.WriteLine($"Client '{e.ClientId}' acknowledged packet {e.PublishPacket.PacketIdentifier} with topic '{e.PublishPacket.Topic}'");
|
||||
|
||||
// It is also possible to read additional data from the client response. This requires casting the response packet.
|
||||
var qos1AcknowledgePacket = e.AcknowledgePacket as MqttPubAckPacket;
|
||||
Console.WriteLine($"QoS 1 reason code: {qos1AcknowledgePacket?.ReasonCode}");
|
||||
|
||||
var qos2AcknowledgePacket = e.AcknowledgePacket as MqttPubCompPacket;
|
||||
Console.WriteLine($"QoS 2 reason code: {qos1AcknowledgePacket?.ReasonCode}");
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
await mqttServer.StartAsync();
|
||||
|
||||
Console.WriteLine("Press Enter to exit.");
|
||||
Console.ReadLine();
|
||||
|
||||
await mqttServer.StopAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
// ReSharper disable UnusedType.Global
|
||||
// ReSharper disable UnusedMember.Global
|
||||
// ReSharper disable InconsistentNaming
|
||||
// ReSharper disable EmptyConstructor
|
||||
// ReSharper disable MemberCanBeMadeStatic.Local
|
||||
|
||||
using MQTTnet.Internal;
|
||||
using MQTTnet.Server;
|
||||
|
||||
namespace MQTTnet.Samples.Server;
|
||||
|
||||
public static class Server_Intercepting_Samples
|
||||
{
|
||||
public static async Task Intercept_Application_Messages()
|
||||
{
|
||||
/*
|
||||
* This sample starts a simple MQTT server which manipulate all processed application messages.
|
||||
* Please see _Server_Simple_Samples_ for more details on how to start a server.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttFactory();
|
||||
var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
|
||||
|
||||
using (var mqttServer = mqttFactory.CreateMqttServer(mqttServerOptions))
|
||||
{
|
||||
mqttServer.InterceptingPublishAsync += args =>
|
||||
{
|
||||
// Here we only change the topic of the received application message.
|
||||
// but also changing the payload etc. is required. Changing the QoS after
|
||||
// transmitting is not supported and makes no sense at all.
|
||||
args.ApplicationMessage.Topic += "/manipulated";
|
||||
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
await mqttServer.StartAsync();
|
||||
|
||||
Console.WriteLine("Press Enter to exit.");
|
||||
Console.ReadLine();
|
||||
await mqttServer.StopAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
// 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.Text.Json;
|
||||
|
||||
namespace MQTTnet.Samples.Server;
|
||||
|
||||
public static class Server_Retained_Messages_Samples
|
||||
{
|
||||
public static async Task Persist_Retained_Messages()
|
||||
{
|
||||
/*
|
||||
* This sample starts a simple MQTT server which will store all retained messages in a file.
|
||||
*/
|
||||
|
||||
var storePath = Path.Combine(Path.GetTempPath(), "RetainedMessages.json");
|
||||
|
||||
var mqttFactory = new MqttFactory();
|
||||
|
||||
// Due to security reasons the "default" endpoint (which is unencrypted) is not enabled by default!
|
||||
var mqttServerOptions = mqttFactory.CreateServerOptionsBuilder().WithDefaultEndpoint().Build();
|
||||
|
||||
using (var server = mqttFactory.CreateMqttServer(mqttServerOptions))
|
||||
{
|
||||
// Make sure that the server will load the retained messages.
|
||||
server.LoadingRetainedMessageAsync += async eventArgs =>
|
||||
{
|
||||
try
|
||||
{
|
||||
eventArgs.LoadedRetainedMessages = await JsonSerializer.DeserializeAsync<List<MqttApplicationMessage>>(File.OpenRead(storePath));
|
||||
Console.WriteLine("Retained messages loaded.");
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
// Ignore because nothing is stored yet.
|
||||
Console.WriteLine("No retained messages stored yet.");
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Console.WriteLine(exception);
|
||||
}
|
||||
};
|
||||
|
||||
// Make sure to persist the changed retained messages.
|
||||
server.RetainedMessageChangedAsync += async eventArgs =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// This sample uses the property _StoredRetainedMessages_ which will contain all(!) retained messages.
|
||||
// The event args also contain the affected retained message (property ChangedRetainedMessage). This can be
|
||||
// used to write all retained messages to dedicated files etc. Then all files must be loaded and a full list
|
||||
// of retained messages must be provided in the loaded event.
|
||||
|
||||
var buffer = JsonSerializer.SerializeToUtf8Bytes(eventArgs.StoredRetainedMessages);
|
||||
await File.WriteAllBytesAsync(storePath, buffer);
|
||||
Console.WriteLine("Retained messages saved.");
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Console.WriteLine(exception);
|
||||
}
|
||||
};
|
||||
|
||||
// Make sure to clear the retained messages when they are all deleted via API.
|
||||
server.RetainedMessagesClearedAsync += _ =>
|
||||
{
|
||||
File.Delete(storePath);
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
|
||||
await server.StartAsync();
|
||||
|
||||
Console.WriteLine("Press Enter to exit.");
|
||||
Console.ReadLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,11 @@
|
||||
// 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.Diagnostics;
|
||||
using MQTTnet.Protocol;
|
||||
using MQTTnet.Server;
|
||||
|
||||
@@ -9,27 +14,95 @@ namespace MQTTnet.Samples.Server;
|
||||
|
||||
public static class Server_Simple_Samples
|
||||
{
|
||||
public static async Task Force_Disconnecting_Client()
|
||||
{
|
||||
/*
|
||||
* This sample will disconnect a client.
|
||||
*
|
||||
* See _Run_Minimal_Server_ for more information.
|
||||
*/
|
||||
|
||||
using (var mqttServer = await StartMqttServer())
|
||||
{
|
||||
// Let the client connect.
|
||||
await Task.Delay(TimeSpan.FromSeconds(5));
|
||||
|
||||
// Now disconnect the client (if connected).
|
||||
var affectedClient = (await mqttServer.GetClientsAsync()).FirstOrDefault(c => c.Id == "MyClient");
|
||||
if (affectedClient != null)
|
||||
{
|
||||
await affectedClient.DisconnectAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task Publish_Message_From_Broker()
|
||||
{
|
||||
/*
|
||||
* This sample will publish a message directly at the broker.
|
||||
*
|
||||
* See _Run_Minimal_Server_ for more information.
|
||||
*/
|
||||
|
||||
using (var mqttServer = await StartMqttServer())
|
||||
{
|
||||
// Create a new message using the builder as usual.
|
||||
var message = new MqttApplicationMessageBuilder().WithTopic("HelloWorld").WithPayload("Test").Build();
|
||||
|
||||
// Now inject the new message at the broker.
|
||||
await mqttServer.InjectApplicationMessage(
|
||||
new InjectedMqttApplicationMessage(message)
|
||||
{
|
||||
SenderClientId = "SenderClientId"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task Run_Minimal_Server()
|
||||
{
|
||||
/*
|
||||
* This sample starts a simple MQTT server which will accept any TCP connection.
|
||||
*/
|
||||
|
||||
|
||||
var mqttFactory = new MqttFactory();
|
||||
|
||||
// The port for the default endpoint is 1883.
|
||||
// The default endpoint is NOT encrypted!
|
||||
// Use the builder classes where possible.
|
||||
var mqttServerOptions = new MqttServerOptionsBuilder()
|
||||
.WithDefaultEndpoint()
|
||||
.Build();
|
||||
|
||||
var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
|
||||
|
||||
// The port can be changed using the following API (not used in this example).
|
||||
new MqttServerOptionsBuilder()
|
||||
.WithDefaultEndpoint()
|
||||
.WithDefaultEndpointPort(1234)
|
||||
.Build();
|
||||
|
||||
// new MqttServerOptionsBuilder()
|
||||
// .WithDefaultEndpoint()
|
||||
// .WithDefaultEndpointPort(1234)
|
||||
// .Build();
|
||||
|
||||
using (var mqttServer = mqttFactory.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();
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task Run_Server_With_Logging()
|
||||
{
|
||||
/*
|
||||
* This sample starts a simple MQTT server and prints the logs to the output.
|
||||
*
|
||||
* IMPORTANT! Do not enable logging in live environment. It will decrease performance.
|
||||
*
|
||||
* See sample "Run_Minimal_Server" for more details.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttFactory(new ConsoleLogger());
|
||||
|
||||
var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
|
||||
|
||||
using (var mqttServer = mqttFactory.CreateMqttServer(mqttServerOptions))
|
||||
{
|
||||
await mqttServer.StartAsync();
|
||||
@@ -52,9 +125,7 @@ public static class Server_Simple_Samples
|
||||
|
||||
var mqttFactory = new MqttFactory();
|
||||
|
||||
var mqttServerOptions = new MqttServerOptionsBuilder()
|
||||
.WithDefaultEndpoint()
|
||||
.Build();
|
||||
var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
|
||||
|
||||
using (var mqttServer = mqttFactory.CreateMqttServer(mqttServerOptions))
|
||||
{
|
||||
@@ -67,7 +138,7 @@ public static class Server_Simple_Samples
|
||||
e.ReasonCode = MqttConnectReasonCode.ClientIdentifierNotValid;
|
||||
}
|
||||
|
||||
if (e.Username != "ValidUser")
|
||||
if (e.UserName != "ValidUser")
|
||||
{
|
||||
e.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword;
|
||||
}
|
||||
@@ -88,4 +159,61 @@ public static class Server_Simple_Samples
|
||||
await mqttServer.StopAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static async Task<MqttServer> StartMqttServer()
|
||||
{
|
||||
var mqttFactory = new MqttFactory();
|
||||
|
||||
// Due to security reasons the "default" endpoint (which is unencrypted) is not enabled by default!
|
||||
var mqttServerOptions = mqttFactory.CreateServerOptionsBuilder().WithDefaultEndpoint().Build();
|
||||
var server = mqttFactory.CreateMqttServer(mqttServerOptions);
|
||||
await server.StartAsync();
|
||||
return server;
|
||||
}
|
||||
|
||||
class ConsoleLogger : IMqttNetLogger
|
||||
{
|
||||
readonly object _consoleSyncRoot = new();
|
||||
|
||||
public bool IsEnabled => true;
|
||||
|
||||
public void Publish(MqttNetLogLevel logLevel, string source, string message, object[]? parameters, Exception? exception)
|
||||
{
|
||||
var foregroundColor = ConsoleColor.White;
|
||||
switch (logLevel)
|
||||
{
|
||||
case MqttNetLogLevel.Verbose:
|
||||
foregroundColor = ConsoleColor.White;
|
||||
break;
|
||||
|
||||
case MqttNetLogLevel.Info:
|
||||
foregroundColor = ConsoleColor.Green;
|
||||
break;
|
||||
|
||||
case MqttNetLogLevel.Warning:
|
||||
foregroundColor = ConsoleColor.DarkYellow;
|
||||
break;
|
||||
|
||||
case MqttNetLogLevel.Error:
|
||||
foregroundColor = ConsoleColor.Red;
|
||||
break;
|
||||
}
|
||||
|
||||
if (parameters?.Length > 0)
|
||||
{
|
||||
message = string.Format(message, parameters);
|
||||
}
|
||||
|
||||
lock (_consoleSyncRoot)
|
||||
{
|
||||
Console.ForegroundColor = foregroundColor;
|
||||
Console.WriteLine(message);
|
||||
|
||||
if (exception != null)
|
||||
{
|
||||
Console.WriteLine(exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,11 +9,9 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.0" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="2.1.2" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="2.1.2" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Compilers" Version="4.0.1" />
|
||||
<PackageReference Include="Microsoft.Net.Compilers" Version="4.0.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="2.2.10" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="2.2.10" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition=" '$(TargetFramework)' == 'netcoreapp3.1' ">
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO.Pipelines;
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
|
||||
namespace MQTTnet.AspNetCore.Tests.Mockups
|
||||
{
|
||||
public class ConnectionContextMockup : ConnectionContext
|
||||
{
|
||||
public override string ConnectionId { get; set; }
|
||||
|
||||
public override IFeatureCollection Features { get; }
|
||||
|
||||
public override IDictionary<object, object> Items { get; set; }
|
||||
public override IDuplexPipe Transport { get; set; }
|
||||
|
||||
public ConnectionContextMockup()
|
||||
{
|
||||
//Transport = new DefaultConnectionContext
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,20 +2,24 @@
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.Formatter;
|
||||
using MQTTnet.Server;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Formatter;
|
||||
using MQTTnet.Server;
|
||||
|
||||
namespace MQTTnet.AspNetCore.Tests.Mockups
|
||||
{
|
||||
public class ConnectionHandlerMockup: IMqttServerAdapter
|
||||
public sealed class ConnectionHandlerMockup : IMqttServerAdapter
|
||||
{
|
||||
public TaskCompletionSource<MqttConnectionContext> Context { get; } = new TaskCompletionSource<MqttConnectionContext>();
|
||||
public Func<IMqttChannelAdapter, Task> ClientHandler { get; set; }
|
||||
public TaskCompletionSource<MqttConnectionContext> Context { get; } = new TaskCompletionSource<MqttConnectionContext>();
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
public async Task OnConnectedAsync(ConnectionContext connection)
|
||||
{
|
||||
@@ -42,9 +46,5 @@ namespace MQTTnet.AspNetCore.Tests.Mockups
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ using System.IO.Pipelines;
|
||||
|
||||
namespace MQTTnet.AspNetCore.Tests.Mockups
|
||||
{
|
||||
public class DuplexPipeMockup : IDuplexPipe
|
||||
public sealed class DuplexPipeMockup : IDuplexPipe
|
||||
{
|
||||
public DuplexPipeMockup()
|
||||
{
|
||||
@@ -20,9 +20,8 @@ namespace MQTTnet.AspNetCore.Tests.Mockups
|
||||
|
||||
PipeWriter IDuplexPipe.Output => Send.Writer;
|
||||
|
||||
public Pipe Receive { get; }
|
||||
|
||||
public Pipe Receive { get; set; }
|
||||
|
||||
public Pipe Send { get; set; }
|
||||
public Pipe Send { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ using System.Buffers;
|
||||
|
||||
namespace MQTTnet.AspNetCore.Tests.Mockups
|
||||
{
|
||||
public class LimitedMemoryPool : MemoryPool<byte>
|
||||
public sealed class LimitedMemoryPool : MemoryPool<byte>
|
||||
{
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
|
||||
@@ -7,9 +7,9 @@ using System.Buffers;
|
||||
|
||||
namespace MQTTnet.AspNetCore.Tests.Mockups
|
||||
{
|
||||
public class MemoryOwner : IMemoryOwner<byte>
|
||||
public sealed class MemoryOwner : IMemoryOwner<byte>
|
||||
{
|
||||
private readonly byte[] _raw;
|
||||
readonly byte[] _raw;
|
||||
|
||||
public MemoryOwner(int size)
|
||||
{
|
||||
@@ -27,11 +27,11 @@ namespace MQTTnet.AspNetCore.Tests.Mockups
|
||||
Memory = _raw;
|
||||
}
|
||||
|
||||
public Memory<byte> Memory { get; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(_raw);
|
||||
}
|
||||
|
||||
public Memory<byte> Memory { get; }
|
||||
}
|
||||
}
|
||||
@@ -2,44 +2,29 @@
|
||||
// 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.Builder;
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
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.Client;
|
||||
using MQTTnet.Protocol;
|
||||
using MQTTnet.Tests.Extensions;
|
||||
using MQTTnet.Tests.Helpers;
|
||||
|
||||
namespace MQTTnet.AspNetCore.Tests
|
||||
{
|
||||
[TestClass]
|
||||
public class MqttConnectionContextTest
|
||||
{
|
||||
[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));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task TestCorruptedConnectPacket()
|
||||
{
|
||||
@@ -49,13 +34,43 @@ namespace MQTTnet.AspNetCore.Tests
|
||||
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 pipe.Receive.Writer.WriteAsync(writer.AddMqttHeader(MqttControlPacketType.Connect, Array.Empty<byte>()));
|
||||
|
||||
await Assert.ThrowsExceptionAsync<MqttProtocolViolationException>(() => ctx.ReceivePacketAsync(CancellationToken.None));
|
||||
|
||||
// the first exception should complete the pipes so if someone tries to use the connection after that it should throw immidiatly
|
||||
await Assert.ThrowsExceptionAsync<InvalidOperationException>(() => ctx.ReceivePacketAsync(CancellationToken.None));
|
||||
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!
|
||||
@@ -78,7 +93,7 @@ namespace MQTTnet.AspNetCore.Tests
|
||||
|
||||
// await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
//}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public async Task TestLargePacket()
|
||||
{
|
||||
@@ -94,43 +109,25 @@ namespace MQTTnet.AspNetCore.Tests
|
||||
Assert.IsTrue(readResult.Buffer.Length > 20000);
|
||||
}
|
||||
|
||||
private class Startup
|
||||
[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)
|
||||
{
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
[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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ using MQTTnet.Packets;
|
||||
namespace MQTTnet.AspNetCore.Tests
|
||||
{
|
||||
[TestClass]
|
||||
public class ReaderExtensionsTest
|
||||
public sealed class ReaderExtensionsTest
|
||||
{
|
||||
[TestMethod]
|
||||
public void TestTryDeserialize()
|
||||
|
||||
@@ -8,17 +8,15 @@
|
||||
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
||||
<EnableNETAnalyzers>false</EnableNETAnalyzers>
|
||||
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
|
||||
<NoWarn>1591;NETSDK1138</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj"/>
|
||||
|
||||
<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" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="wwwroot\"/>
|
||||
<Folder Include="wwwroot\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -44,7 +44,7 @@ namespace MQTTnet.AspNetCore.Client.Tcp
|
||||
_sender = new SocketSender(_socket, PipeScheduler.ThreadPool);
|
||||
_receiver = new SocketReceiver(_socket, PipeScheduler.ThreadPool);
|
||||
}
|
||||
#if NETCOREAPP3_1 || NET5_0_OR_GREATER
|
||||
#if NETCOREAPP3_1_OR_GREATER
|
||||
public override ValueTask DisposeAsync()
|
||||
#else
|
||||
public Task DisposeAsync()
|
||||
@@ -57,7 +57,7 @@ namespace MQTTnet.AspNetCore.Client.Tcp
|
||||
|
||||
_socket?.Dispose();
|
||||
|
||||
#if NETCOREAPP3_1 || NET5_0_OR_GREATER
|
||||
#if NETCOREAPP3_1_OR_GREATER
|
||||
|
||||
return base.DisposeAsync();
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
|
||||
#if NETCOREAPP3_1 || NET5_0_OR_GREATER
|
||||
#if NETCOREAPP3_1_OR_GREATER
|
||||
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;netcoreapp2.1;netcoreapp3.1;net5.0;net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>netstandard2.0;netcoreapp2.1;netcoreapp3.1;net5.0;net6.0;net7.0</TargetFrameworks>
|
||||
|
||||
<AssemblyName>MQTTnet.AspNetCore</AssemblyName>
|
||||
<RootNamespace>MQTTnet.AspNetCore</RootNamespace>
|
||||
@@ -32,7 +32,8 @@
|
||||
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<NoWarn>1591</NoWarn>
|
||||
<NoWarn>1591;NETSDK1138</NoWarn>
|
||||
<LangVersion>7.3</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -43,18 +44,18 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<DefineConstants>RELEASE;NETSTANDARD2_0</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp3.1' or '$(TargetFramework)' == 'net5.0' or '$(TargetFramework)' == 'net6.0' ">
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp3.1' or '$(TargetFramework)' == 'net5.0' or '$(TargetFramework)' == 'net6.0' or '$(TargetFramework)' == 'net7.0' ">
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' != 'netcoreapp3.1' and '$(TargetFramework)' != 'net5.0' and '$(TargetFramework)' != 'net6.0' ">
|
||||
<ItemGroup Condition="'$(TargetFramework)' != 'netcoreapp3.1' and '$(TargetFramework)' != 'net5.0' and '$(TargetFramework)' != 'net6.0' and '$(TargetFramework)' != 'net7.0' ">
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http.Connections" Version="1.1.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -2,38 +2,59 @@
|
||||
// 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;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
using Microsoft.AspNetCore.Http.Connections.Features;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.AspNetCore.Client.Tcp;
|
||||
using MQTTnet.Exceptions;
|
||||
using MQTTnet.Formatter;
|
||||
using MQTTnet.Packets;
|
||||
using System;
|
||||
using System.IO.Pipelines;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Internal;
|
||||
using MQTTnet.Packets;
|
||||
|
||||
namespace MQTTnet.AspNetCore
|
||||
{
|
||||
public sealed class MqttConnectionContext : IMqttChannelAdapter
|
||||
{
|
||||
readonly ConnectionContext _connection;
|
||||
readonly AsyncLock _writerLock = new AsyncLock();
|
||||
|
||||
|
||||
PipeReader _input;
|
||||
PipeWriter _output;
|
||||
|
||||
|
||||
public MqttConnectionContext(MqttPacketFormatterAdapter packetFormatterAdapter, ConnectionContext connection)
|
||||
{
|
||||
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)
|
||||
_input = connection.Transport.Input;
|
||||
_output = connection.Transport.Output;
|
||||
}
|
||||
|
||||
public long BytesReceived { get; private set; }
|
||||
|
||||
public long BytesSent { get; private set; }
|
||||
|
||||
public X509Certificate2 ClientCertificate
|
||||
{
|
||||
get
|
||||
{
|
||||
_input = Connection.Transport.Input;
|
||||
_output = Connection.Transport.Output;
|
||||
// mqtt over tcp
|
||||
var tlsFeature = _connection.Features.Get<ITlsConnectionFeature>();
|
||||
if (tlsFeature != null)
|
||||
{
|
||||
return tlsFeature.ClientCertificate;
|
||||
}
|
||||
|
||||
// mqtt over websocket
|
||||
var httpFeature = _connection.Features.Get<IHttpContextFeature>();
|
||||
return httpFeature?.HttpContext?.Connection.ClientCertificate;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,47 +62,59 @@ namespace MQTTnet.AspNetCore
|
||||
{
|
||||
get
|
||||
{
|
||||
#if NETCOREAPP3_1
|
||||
if (Connection?.RemoteEndPoint != null)
|
||||
// mqtt over tcp
|
||||
#if NETCOREAPP3_1_OR_GREATER
|
||||
if (_connection.RemoteEndPoint != null)
|
||||
{
|
||||
return Connection.RemoteEndPoint.ToString();
|
||||
return _connection.RemoteEndPoint.ToString();
|
||||
}
|
||||
#endif
|
||||
var connection = Http?.HttpContext?.Connection;
|
||||
if (connection == null)
|
||||
// mqtt over websocket
|
||||
var httpFeature = _connection.Features.Get<IHttpConnectionFeature>();
|
||||
if (httpFeature?.RemoteIpAddress != null)
|
||||
{
|
||||
return Connection.ConnectionId;
|
||||
return new IPEndPoint(httpFeature.RemoteIpAddress, httpFeature.RemotePort).ToString();
|
||||
}
|
||||
|
||||
return $"{connection.RemoteIpAddress}:{connection.RemotePort}";
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsSecureConnection => Http?.HttpContext?.Request?.IsHttps ?? false;
|
||||
|
||||
public X509Certificate2 ClientCertificate => Http?.HttpContext?.Connection?.ClientCertificate;
|
||||
|
||||
public ConnectionContext Connection { get; }
|
||||
|
||||
public MqttPacketFormatterAdapter PacketFormatterAdapter { get; }
|
||||
|
||||
public long BytesSent { get; set; }
|
||||
|
||||
public long BytesReceived { get; set; }
|
||||
|
||||
public bool IsReadingPacket { get; private set; }
|
||||
|
||||
IHttpContextFeature Http => Connection.Features.Get<IHttpContextFeature>();
|
||||
public bool IsSecureConnection
|
||||
{
|
||||
get
|
||||
{
|
||||
// mqtt over tcp
|
||||
var tlsFeature = _connection.Features.Get<ITlsConnectionFeature>();
|
||||
if (tlsFeature != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// mqtt over websocket
|
||||
var httpFeature = _connection.Features.Get<IHttpContextFeature>();
|
||||
if (httpFeature?.HttpContext != null)
|
||||
{
|
||||
return httpFeature.HttpContext.Request.IsHttps;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public MqttPacketFormatterAdapter PacketFormatterAdapter { get; }
|
||||
|
||||
public async Task ConnectAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (Connection is TcpConnection tcp && !tcp.IsConnected)
|
||||
if (_connection is TcpConnection tcp && !tcp.IsConnected)
|
||||
{
|
||||
await tcp.StartAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
_input = Connection.Transport.Input;
|
||||
_output = Connection.Transport.Output;
|
||||
_input = _connection.Transport.Input;
|
||||
_output = _connection.Transport.Output;
|
||||
}
|
||||
|
||||
public Task DisconnectAsync(CancellationToken cancellationToken)
|
||||
@@ -92,16 +125,19 @@ namespace MQTTnet.AspNetCore
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_writerLock.Dispose();
|
||||
}
|
||||
|
||||
public async Task<MqttPacket> ReceivePacketAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var input = Connection.Transport.Input;
|
||||
|
||||
try
|
||||
{
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
ReadResult readResult;
|
||||
var readTask = input.ReadAsync(cancellationToken);
|
||||
var readTask = _input.ReadAsync(cancellationToken);
|
||||
if (readTask.IsCompleted)
|
||||
{
|
||||
readResult = readTask.Result;
|
||||
@@ -141,15 +177,15 @@ namespace MQTTnet.AspNetCore
|
||||
// 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);
|
||||
_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
|
||||
_input?.Complete(e);
|
||||
_output?.Complete(e);
|
||||
_input?.Complete(exception);
|
||||
_output?.Complete(exception);
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
@@ -169,25 +205,44 @@ namespace MQTTnet.AspNetCore
|
||||
|
||||
public async Task SendPacketAsync(MqttPacket packet, CancellationToken cancellationToken)
|
||||
{
|
||||
var formatter = PacketFormatterAdapter;
|
||||
|
||||
using (await _writerLock.WaitAsync(cancellationToken).ConfigureAwait(false))
|
||||
using (await _writerLock.EnterAsync(cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
var buffer = formatter.Encode(packet);
|
||||
var msg = buffer.Join().AsMemory();
|
||||
var output = _output;
|
||||
var result = await output.WriteAsync(msg, cancellationToken).ConfigureAwait(false);
|
||||
if (result.IsCompleted)
|
||||
try
|
||||
{
|
||||
BytesSent += msg.Length;
|
||||
}
|
||||
var buffer = PacketFormatterAdapter.Encode(packet);
|
||||
|
||||
PacketFormatterAdapter.Cleanup();
|
||||
if (buffer.Payload.Count == 0)
|
||||
{
|
||||
// zero copy
|
||||
// https://github.com/dotnet/runtime/blob/e31ddfdc4f574b26231233dc10c9a9c402f40590/src/libraries/System.IO.Pipelines/src/System/IO/Pipelines/StreamPipeWriter.cs#L279
|
||||
await _output.WriteAsync(buffer.Packet, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
WritePacketBuffer(_output, buffer);
|
||||
await _output.FlushAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
BytesSent += buffer.Length;
|
||||
}
|
||||
finally
|
||||
{
|
||||
PacketFormatterAdapter.Cleanup();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
buffer.Payload.AsSpan().CopyTo(span.Slice(buffer.Packet.Count));
|
||||
|
||||
output.Advance(buffer.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,21 +2,23 @@
|
||||
// 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 MQTTnet.Adapter;
|
||||
using MQTTnet.Exceptions;
|
||||
using MQTTnet.Formatter;
|
||||
using MQTTnet.Packets;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace MQTTnet.AspNetCore
|
||||
{
|
||||
public static class ReaderExtensions
|
||||
{
|
||||
public static bool TryDecode(this MqttPacketFormatterAdapter formatter,
|
||||
in ReadOnlySequence<byte> input,
|
||||
out MqttPacket packet,
|
||||
out SequencePosition consumed,
|
||||
public static bool TryDecode(this MqttPacketFormatterAdapter formatter,
|
||||
in ReadOnlySequence<byte> input,
|
||||
out MqttPacket packet,
|
||||
out SequencePosition consumed,
|
||||
out SequencePosition observed,
|
||||
out int bytesRead)
|
||||
{
|
||||
@@ -33,22 +35,22 @@ namespace MQTTnet.AspNetCore
|
||||
return false;
|
||||
}
|
||||
|
||||
var fixedHeader = copy.First.Span[0];
|
||||
if (!TryReadBodyLength(ref copy, out int headerLength, out var bodyLength))
|
||||
if (!TryReadBodyLength(ref copy, out var headerLength, out var bodyLength))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var fixedHeader = copy.First.Span[0];
|
||||
copy = copy.Slice(headerLength);
|
||||
if (copy.Length < bodyLength)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var bodySlice = copy.Slice(0, bodyLength);
|
||||
var buffer = bodySlice.GetMemory().ToArray();
|
||||
|
||||
var receivedMqttPacket = new ReceivedMqttPacket(fixedHeader, new ArraySegment<byte>(buffer, 0, buffer.Length), buffer.Length + 2);
|
||||
|
||||
var bodySegment = GetArraySegment(ref bodySlice);
|
||||
|
||||
var receivedMqttPacket = new ReceivedMqttPacket(fixedHeader, bodySegment, headerLength + bodyLength);
|
||||
if (formatter.ProtocolVersion == MqttProtocolVersion.Unknown)
|
||||
{
|
||||
formatter.DetectProtocolVersion(receivedMqttPacket);
|
||||
@@ -61,18 +63,41 @@ namespace MQTTnet.AspNetCore
|
||||
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
|
||||
return input.ToArray();
|
||||
var array = input.ToArray();
|
||||
return new ArraySegment<byte>(array);
|
||||
}
|
||||
|
||||
static bool TryReadBodyLength(ref ReadOnlySequence<byte> input, out int headerLength, out int bodyLength)
|
||||
{
|
||||
var valueSequence = input.Slice(0, Math.Min(5, input.Length));
|
||||
if (valueSequence.IsSingleSegment)
|
||||
{
|
||||
var valueSpan =
|
||||
#if NET5_0_OR_GREATER
|
||||
valueSequence.FirstSpan;
|
||||
#else
|
||||
valueSequence.First.Span;
|
||||
#endif
|
||||
return TryReadBodyLength(valueSpan, out headerLength, out bodyLength);
|
||||
}
|
||||
else
|
||||
{
|
||||
Span<byte> valueSpan = stackalloc byte[8];
|
||||
valueSequence.CopyTo(valueSpan);
|
||||
valueSpan = valueSpan.Slice(0, (int)valueSequence.Length);
|
||||
return TryReadBodyLength(valueSpan, out headerLength, out bodyLength);
|
||||
}
|
||||
}
|
||||
|
||||
static bool TryReadBodyLength(ReadOnlySpan<byte> span, out int headerLength, out int bodyLength)
|
||||
{
|
||||
// Alorithm taken from https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/errata01/os/mqtt-v3.1.1-errata01-os-complete.html.
|
||||
var multiplier = 1;
|
||||
@@ -81,33 +106,35 @@ namespace MQTTnet.AspNetCore
|
||||
var index = 1;
|
||||
headerLength = 0;
|
||||
bodyLength = 0;
|
||||
|
||||
var temp = input.Slice(0, Math.Min(5, input.Length)).GetMemory().Span;
|
||||
|
||||
do
|
||||
{
|
||||
if (index == temp.Length)
|
||||
if (index == span.Length)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
encodedByte = temp[index];
|
||||
encodedByte = span[index];
|
||||
index++;
|
||||
|
||||
value += (byte)(encodedByte & 127) * multiplier;
|
||||
if (multiplier > 128 * 128 * 128)
|
||||
{
|
||||
throw new MqttProtocolViolationException($"Remaining length is invalid (Data={string.Join(",", temp.Slice(1, index).ToArray())}).");
|
||||
ThrowProtocolViolationException(span, index);
|
||||
}
|
||||
|
||||
multiplier *= 128;
|
||||
} while ((encodedByte & 128) != 0);
|
||||
|
||||
input = input.Slice(index);
|
||||
|
||||
headerLength = index;
|
||||
bodyLength = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static void ThrowProtocolViolationException(ReadOnlySpan<byte> valueSpan, int index)
|
||||
{
|
||||
throw new MqttProtocolViolationException($"Remaining length is invalid (Data={string.Join(",", valueSpan.Slice(1, index).ToArray())}).");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using MQTTnet.Internal;
|
||||
|
||||
namespace MQTTnet.Benchmarks
|
||||
{
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[MemoryDiagnoser]
|
||||
public class AsyncLockBenchmark : BaseBenchmark
|
||||
{
|
||||
[Benchmark]
|
||||
public async Task Synchronize_100_Tasks()
|
||||
{
|
||||
const int tasksCount = 100;
|
||||
|
||||
var tasks = new Task[tasksCount];
|
||||
var asyncLock = new AsyncLock();
|
||||
var globalI = 0;
|
||||
|
||||
for (var i = 0; i < tasksCount; i++)
|
||||
{
|
||||
tasks[i] = Task.Run(
|
||||
async () =>
|
||||
{
|
||||
using (await asyncLock.EnterAsync().ConfigureAwait(false))
|
||||
{
|
||||
var localI = globalI;
|
||||
await Task.Delay(5).ConfigureAwait(false); // Increase the chance for wrong data.
|
||||
localI++;
|
||||
globalI = localI;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
|
||||
if (globalI != tasksCount)
|
||||
{
|
||||
throw new Exception($"Code is broken ({globalI})!");
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public async Task Wait_100_000_Times()
|
||||
{
|
||||
var asyncLock = new AsyncLock();
|
||||
|
||||
using (var cancellationToken = new CancellationTokenSource())
|
||||
{
|
||||
for (var i = 0; i < 100000; i++)
|
||||
{
|
||||
using (await asyncLock.EnterAsync(cancellationToken.Token).ConfigureAwait(false))
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,7 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace MQTTnet.Benchmarks
|
||||
{
|
||||
public abstract class BaseBenchmark
|
||||
|
||||
@@ -2,54 +2,25 @@
|
||||
// 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 MQTTnet.Adapter;
|
||||
using MQTTnet.Internal;
|
||||
using MQTTnet.Packets;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Formatter;
|
||||
using MQTTnet.Packets;
|
||||
using MQTTnet.Tests.Mockups;
|
||||
|
||||
namespace MQTTnet.Benchmarks
|
||||
{
|
||||
[MemoryDiagnoser]
|
||||
public sealed class ChannelAdapterBenchmark
|
||||
public sealed class ChannelAdapterBenchmark : BaseBenchmark
|
||||
{
|
||||
MqttChannelAdapter _channelAdapter;
|
||||
int _iterations;
|
||||
MemoryStream _stream;
|
||||
MqttPublishPacket _packet;
|
||||
|
||||
[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 TestMqttChannel(_stream);
|
||||
|
||||
_channelAdapter = new MqttChannelAdapter(channel, serializer, null, new MqttNetEventLogger());
|
||||
}
|
||||
MemoryStream _stream;
|
||||
|
||||
[Benchmark]
|
||||
public void Receive_10000_Messages()
|
||||
@@ -77,6 +48,34 @@ namespace MQTTnet.Benchmarks
|
||||
_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, null, new MqttNetEventLogger());
|
||||
}
|
||||
|
||||
static byte[] Join(params ArraySegment<byte>[] chunks)
|
||||
{
|
||||
var buffer = new MemoryStream();
|
||||
@@ -88,4 +87,4 @@ namespace MQTTnet.Benchmarks
|
||||
return buffer.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,10 +8,10 @@ using MQTTnet.Diagnostics;
|
||||
|
||||
namespace MQTTnet.Benchmarks
|
||||
{
|
||||
[SimpleJob(RuntimeMoniker.NetCoreApp50)]
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[RPlotExporter]
|
||||
[MemoryDiagnoser]
|
||||
public class LoggerBenchmark
|
||||
public class LoggerBenchmark : BaseBenchmark
|
||||
{
|
||||
MqttNetNullLogger _nullLogger;
|
||||
MqttNetSourceLogger _sourceNullLogger;
|
||||
|
||||
@@ -1,25 +1,23 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<OutputType>Exe</OutputType>
|
||||
<DebugType>Full</DebugType>
|
||||
<TargetFrameworks>net5.0;net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
|
||||
<LangVersion>7.2</LangVersion>
|
||||
<IsPackable>false</IsPackable>
|
||||
<EnableNETAnalyzers>false</EnableNETAnalyzers>
|
||||
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
|
||||
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
||||
<NoWarn>1591;NETSDK1138</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.12.1" />
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.13.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore" Version="2.2.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' != 'net5.0'">
|
||||
<PackageReference Include="System.IO.Pipelines" Version="4.7.2" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net5.0'">
|
||||
<PackageReference Include="System.IO.Pipelines" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
@@ -28,11 +26,12 @@
|
||||
<PackageReference Include="System.IO.Pipelines" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
|
||||
<PackageReference Include="System.IO.Pipelines" Version="7.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj" />
|
||||
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Compilers" Version="4.0.1" />
|
||||
<PackageReference Include="Microsoft.Net.Compilers" Version="4.0.1" />
|
||||
<ProjectReference Include="..\MQTTnet.Tests\MQTTnet.Tests.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using System;
|
||||
|
||||
|
||||
namespace MQTTnet.Benchmarks
|
||||
{
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[RPlotExporter, RankColumn]
|
||||
[MemoryDiagnoser]
|
||||
public class MemoryCopyBenchmark
|
||||
{
|
||||
const int max_length = 1024 * 8;
|
||||
private byte[] source;
|
||||
private byte[] target;
|
||||
|
||||
[Params(64 - 1, 128 - 1, 256 - 1, 512 - 1, 1024 - 1, 2048 - 1, 5096 - 1)]
|
||||
public int Length { get; set; }
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
source = new byte[max_length];
|
||||
target = new byte[max_length];
|
||||
}
|
||||
|
||||
[Benchmark(Baseline = true)]
|
||||
public void Array_Copy()
|
||||
{
|
||||
Array.Copy(source, 0, target, 0, Length);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Memory_Copy()
|
||||
{
|
||||
MQTTnet.Internal.MqttMemoryHelper.Copy(source, 0, target, 0, Length);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
// 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.Jobs;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Server;
|
||||
using System;
|
||||
@@ -8,14 +11,10 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
namespace MQTTnet.Benchmarks
|
||||
{
|
||||
/// <summary>
|
||||
/// Create a number of topics, publish, subscribe, and wait for response
|
||||
/// </summary>
|
||||
[MemoryDiagnoser]
|
||||
public class MessageDeliveryBenchmark
|
||||
public class MessageDeliveryBenchmark : BaseBenchmark
|
||||
{
|
||||
List<MqttApplicationMessage> _topicPublishMessages;
|
||||
|
||||
|
||||
@@ -6,14 +6,13 @@ using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Server;
|
||||
using MqttClient = MQTTnet.Client.MqttClient;
|
||||
|
||||
namespace MQTTnet.Benchmarks
|
||||
{
|
||||
[SimpleJob(RuntimeMoniker.NetCoreApp50)]
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[RPlotExporter, RankColumn]
|
||||
[MemoryDiagnoser]
|
||||
public class MessageProcessingBenchmark
|
||||
public class MessageProcessingBenchmark : BaseBenchmark
|
||||
{
|
||||
MqttServer _mqttServer;
|
||||
IMqttClient _mqttClient;
|
||||
|
||||
@@ -13,9 +13,9 @@ using MQTTnet.Diagnostics;
|
||||
|
||||
namespace MQTTnet.Benchmarks
|
||||
{
|
||||
[SimpleJob(RuntimeMoniker.NetCoreApp50)]
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[MemoryDiagnoser]
|
||||
public class MessageProcessingMqttConnectionContextBenchmark
|
||||
public class MessageProcessingMqttConnectionContextBenchmark : BaseBenchmark
|
||||
{
|
||||
IWebHost _host;
|
||||
IMqttClient _mqttClient;
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Text;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using MQTTnet.Formatter;
|
||||
|
||||
namespace MQTTnet.Benchmarks
|
||||
{
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[MemoryDiagnoser]
|
||||
public class MqttBufferReaderBenchmark
|
||||
{
|
||||
byte[] _buffer;
|
||||
int _bufferLength;
|
||||
|
||||
[GlobalSetup]
|
||||
public void GlobalSetup()
|
||||
{
|
||||
var writer = new MqttBufferWriter(1024, 1024);
|
||||
writer.WriteString("hgfjkdfkjlghfdjghdfljkdfhgdlkjfshgsldkfjghsdflkjghdsflkjhrstiuoghlkfjbhnfbutghjoiöjhklötnbhtroliöuhbjntluiobkjzbhtdrlskbhtruhjkfthgbkftgjhgfiklhotriuöhbjtrsioöbtrsötrhträhtrühjtriüoätrhjtsrölbktrbnhtrulöbionhströloubinströoliubhnsöotrunbtöroisntröointröioujhgötiohjgötorshjnbgtöorihbnjtröoihbjntröobntröoibntrjhötrohjbtröoihntröoibnrtoiöbtrjnboöitrhjtnriohötrhjtöroihjtroöihjtroösibntsroönbotöirsbntöoihjntröoihntroöbtrboöitrnhoöitrhjntröoishbnjtröosbhtröbntriohjtröoijtöoitbjöotibjnhöotirhbjntroöibhnjrtoöibnhtroöibnhtörsbnhtöoirbnhtöroibntoörhjnbträöbtrbträbtrbtirbätrsibohjntrsöiobthnjiohjsrtoib");
|
||||
|
||||
_buffer = writer.GetBuffer();
|
||||
_bufferLength = writer.Length;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Use_Span()
|
||||
{
|
||||
var span = _buffer.AsSpan(0, _bufferLength);
|
||||
Encoding.UTF8.GetString(span);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Use_Encoding()
|
||||
{
|
||||
Encoding.UTF8.GetString(_buffer, 0, _bufferLength);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,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 System;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using MQTTnet.AspNetCore;
|
||||
using MQTTnet.Formatter;
|
||||
using MQTTnet.Tests.Mockups;
|
||||
|
||||
namespace MQTTnet.Benchmarks
|
||||
{
|
||||
[SimpleJob(RuntimeMoniker.NetCoreApp50)]
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[MemoryDiagnoser]
|
||||
public class MqttPacketReaderWriterBenchmark
|
||||
public class MqttPacketReaderWriterBenchmark : BaseBenchmark
|
||||
{
|
||||
readonly byte[] _demoPayload = new byte[1024];
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using MQTTnet.Channel;
|
||||
using MQTTnet.Implementations;
|
||||
@@ -14,9 +15,9 @@ using MQTTnet.Diagnostics;
|
||||
|
||||
namespace MQTTnet.Benchmarks
|
||||
{
|
||||
[SimpleJob(RuntimeMoniker.NetCoreApp50)]
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[MemoryDiagnoser]
|
||||
public sealed class MqttTcpChannelBenchmark
|
||||
public class MqttTcpChannelBenchmark : BaseBenchmark
|
||||
{
|
||||
MqttServer _mqttServer;
|
||||
IMqttChannel _serverChannel;
|
||||
@@ -70,8 +71,8 @@ namespace MQTTnet.Benchmarks
|
||||
|
||||
while (read < expected)
|
||||
{
|
||||
var readresult = await _clientChannel.ReadAsync(new byte[size], 0, size, CancellationToken.None).ConfigureAwait(false);
|
||||
read += readresult;
|
||||
var readResult = await _clientChannel.ReadAsync(new byte[size], 0, size, CancellationToken.None).ConfigureAwait(false);
|
||||
read += readResult;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,9 +80,11 @@ namespace MQTTnet.Benchmarks
|
||||
{
|
||||
await Task.Yield();
|
||||
|
||||
var buffer = new ArraySegment<byte>(new byte[size]);
|
||||
|
||||
for (var i = 0; i < iterations; i++)
|
||||
{
|
||||
await _serverChannel.WriteAsync(new byte[size], 0, size, CancellationToken.None).ConfigureAwait(false);
|
||||
await _serverChannel.WriteAsync(buffer, true, CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,81 +3,140 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using BenchmarkDotNet.Loggers;
|
||||
using BenchmarkDotNet.Running;
|
||||
using MQTTnet.Benchmarks.Configurations;
|
||||
using MQTTnet.Diagnostics;
|
||||
|
||||
namespace MQTTnet.Benchmarks
|
||||
{
|
||||
public static class Program
|
||||
{
|
||||
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");
|
||||
static int _selectedBenchmarkIndex;
|
||||
static List<Type> _benchmarks;
|
||||
|
||||
var pressedKey = Console.ReadKey(true);
|
||||
switch (pressedKey.KeyChar)
|
||||
public static void Main(string[] arguments)
|
||||
{
|
||||
_benchmarks = CollectBenchmarks();
|
||||
HandleArguments(arguments);
|
||||
|
||||
while (true)
|
||||
{
|
||||
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>();
|
||||
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)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
BenchmarkRunner.Run(_benchmarks[_selectedBenchmarkIndex]);
|
||||
|
||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
Console.WriteLine("Press any key to exit");
|
||||
Console.ReadLine();
|
||||
}
|
||||
|
||||
static List<Type> CollectBenchmarks()
|
||||
{
|
||||
var benchmarks = new List<Type>();
|
||||
|
||||
var types = typeof(Program).Assembly.GetExportedTypes();
|
||||
foreach (var type in types)
|
||||
{
|
||||
if (typeof(BaseBenchmark).IsAssignableFrom(type))
|
||||
{
|
||||
if (type != typeof(BaseBenchmark))
|
||||
{
|
||||
benchmarks.Add(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return benchmarks.OrderBy(b => b.Name).ToList();
|
||||
}
|
||||
|
||||
static void HandleArguments(string[] arguments)
|
||||
{
|
||||
if (arguments.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Allow for preselection to avoid developer frustration.
|
||||
|
||||
if (int.TryParse(arguments[0], out var benchmarkIndex))
|
||||
{
|
||||
_selectedBenchmarkIndex = benchmarkIndex;
|
||||
return;
|
||||
}
|
||||
|
||||
_selectedBenchmarkIndex = _benchmarks.FindIndex(b => b.Name.Equals(arguments[0]));
|
||||
|
||||
if (_selectedBenchmarkIndex < 0)
|
||||
{
|
||||
_selectedBenchmarkIndex = 0;
|
||||
}
|
||||
|
||||
if (_selectedBenchmarkIndex > _benchmarks.Count - 1)
|
||||
{
|
||||
_selectedBenchmarkIndex = _benchmarks.Count - 1;
|
||||
}
|
||||
}
|
||||
|
||||
static void RenderMenu()
|
||||
{
|
||||
Console.Clear();
|
||||
|
||||
Console.WriteLine($"MQTTnet - Benchmarks ({TargetFrameworkProvider.TargetFramework})");
|
||||
Console.WriteLine("-----------------------------------------------");
|
||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
Console.WriteLine("Press arrow keys for benchmark selection");
|
||||
Console.WriteLine("Press Enter for benchmark execution");
|
||||
Console.WriteLine("Press Esc for exit");
|
||||
Console.ForegroundColor = ConsoleColor.White;
|
||||
Console.WriteLine();
|
||||
|
||||
for (var index = 0; index < _benchmarks.Count; index++)
|
||||
{
|
||||
var benchmark = _benchmarks[index];
|
||||
|
||||
if (_selectedBenchmarkIndex == index)
|
||||
{
|
||||
Console.Write("-> ");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.Write(" ");
|
||||
}
|
||||
|
||||
Console.WriteLine(benchmark.Name);
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.AspNetCore;
|
||||
using MQTTnet.Exceptions;
|
||||
using MQTTnet.Formatter;
|
||||
using MQTTnet.Packets;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.IO;
|
||||
using System.IO.Pipelines;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MQTTnet.Benchmarks
|
||||
{
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[RPlotExporter, RankColumn]
|
||||
[MemoryDiagnoser]
|
||||
public class ReaderExtensionsBenchmark
|
||||
{
|
||||
MqttPacketFormatterAdapter mqttPacketFormatter;
|
||||
MemoryStream stream;
|
||||
|
||||
[GlobalSetup]
|
||||
public void GlobalSetup()
|
||||
{
|
||||
mqttPacketFormatter = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535));
|
||||
var mqttMessage = new MqttApplicationMessageBuilder()
|
||||
.WithTopic("topic")
|
||||
.WithPayload(new byte[10 * 1024])
|
||||
.Build();
|
||||
|
||||
var packet = MqttPacketFactories.Publish.Create(mqttMessage);
|
||||
|
||||
var buffer = mqttPacketFormatter.Encode(packet);
|
||||
stream = new MemoryStream();
|
||||
stream.Write(buffer.Packet);
|
||||
stream.Write(buffer.Payload);
|
||||
mqttPacketFormatter.Cleanup();
|
||||
}
|
||||
|
||||
[Benchmark(Baseline = true)]
|
||||
public async Task Before()
|
||||
{
|
||||
stream.Position = 0;
|
||||
var input = PipeReader.Create(stream);
|
||||
|
||||
while (true)
|
||||
{
|
||||
ReadResult readResult;
|
||||
var readTask = input.ReadAsync();
|
||||
if (readTask.IsCompleted)
|
||||
{
|
||||
readResult = readTask.Result;
|
||||
}
|
||||
else
|
||||
{
|
||||
readResult = await readTask.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
var buffer = readResult.Buffer;
|
||||
|
||||
var consumed = buffer.Start;
|
||||
var observed = buffer.Start;
|
||||
|
||||
try
|
||||
{
|
||||
if (!buffer.IsEmpty)
|
||||
{
|
||||
if (ReaderExtensions_Before.TryDecode(mqttPacketFormatter, buffer, out var packet, out consumed, out observed, out var received))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (readResult.IsCompleted)
|
||||
{
|
||||
throw new MqttCommunicationException("Connection Aborted");
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
// The buffer was sliced up to where it was consumed, so we can just advance to the start.
|
||||
// We mark examined as buffer.End so that if we didn't receive a full frame, we'll wait for more data
|
||||
// before yielding the read again.
|
||||
input.AdvanceTo(consumed, observed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public async Task After()
|
||||
{
|
||||
stream.Position = 0;
|
||||
var input = PipeReader.Create(stream);
|
||||
|
||||
while (true)
|
||||
{
|
||||
ReadResult readResult;
|
||||
var readTask = input.ReadAsync();
|
||||
if (readTask.IsCompleted)
|
||||
{
|
||||
readResult = readTask.Result;
|
||||
}
|
||||
else
|
||||
{
|
||||
readResult = await readTask.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
var buffer = readResult.Buffer;
|
||||
|
||||
var consumed = buffer.Start;
|
||||
var observed = buffer.Start;
|
||||
|
||||
try
|
||||
{
|
||||
if (!buffer.IsEmpty)
|
||||
{
|
||||
if (ReaderExtensions.TryDecode(mqttPacketFormatter, buffer, out var packet, out consumed, out observed, out var received))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (readResult.IsCompleted)
|
||||
{
|
||||
throw new MqttCommunicationException("Connection Aborted");
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
// The buffer was sliced up to where it was consumed, so we can just advance to the start.
|
||||
// We mark examined as buffer.End so that if we didn't receive a full frame, we'll wait for more data
|
||||
// before yielding the read again.
|
||||
input.AdvanceTo(consumed, observed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class ReaderExtensions_Before
|
||||
{
|
||||
public static bool TryDecode(MqttPacketFormatterAdapter formatter,
|
||||
in ReadOnlySequence<byte> input,
|
||||
out MqttPacket packet,
|
||||
out SequencePosition consumed,
|
||||
out SequencePosition observed,
|
||||
out int bytesRead)
|
||||
{
|
||||
if (formatter == null) throw new ArgumentNullException(nameof(formatter));
|
||||
|
||||
packet = null;
|
||||
consumed = input.Start;
|
||||
observed = input.End;
|
||||
bytesRead = 0;
|
||||
var copy = input;
|
||||
|
||||
if (copy.Length < 2)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var fixedHeader = copy.First.Span[0];
|
||||
if (!TryReadBodyLength(ref copy, out int headerLength, out var bodyLength))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (copy.Length < bodyLength)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var bodySlice = copy.Slice(0, bodyLength);
|
||||
var buffer = GetMemory(bodySlice).ToArray();
|
||||
|
||||
var receivedMqttPacket = new ReceivedMqttPacket(fixedHeader, new ArraySegment<byte>(buffer, 0, buffer.Length), buffer.Length + 2);
|
||||
|
||||
if (formatter.ProtocolVersion == MqttProtocolVersion.Unknown)
|
||||
{
|
||||
formatter.DetectProtocolVersion(receivedMqttPacket);
|
||||
}
|
||||
|
||||
packet = formatter.Decode(receivedMqttPacket);
|
||||
consumed = bodySlice.End;
|
||||
observed = bodySlice.End;
|
||||
bytesRead = headerLength + bodyLength;
|
||||
return true;
|
||||
}
|
||||
|
||||
static ReadOnlyMemory<byte> GetMemory(in ReadOnlySequence<byte> input)
|
||||
{
|
||||
if (input.IsSingleSegment)
|
||||
{
|
||||
return input.First;
|
||||
}
|
||||
|
||||
// Should be rare
|
||||
return input.ToArray();
|
||||
}
|
||||
|
||||
static bool TryReadBodyLength(ref ReadOnlySequence<byte> input, out int headerLength, out int bodyLength)
|
||||
{
|
||||
// Alorithm taken from https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/errata01/os/mqtt-v3.1.1-errata01-os-complete.html.
|
||||
var multiplier = 1;
|
||||
var value = 0;
|
||||
byte encodedByte;
|
||||
var index = 1;
|
||||
headerLength = 0;
|
||||
bodyLength = 0;
|
||||
|
||||
var temp = GetMemory(input.Slice(0, Math.Min(5, input.Length))).Span;
|
||||
|
||||
do
|
||||
{
|
||||
if (index == temp.Length)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
encodedByte = temp[index];
|
||||
index++;
|
||||
|
||||
value += (byte)(encodedByte & 127) * multiplier;
|
||||
if (multiplier > 128 * 128 * 128)
|
||||
{
|
||||
throw new MqttProtocolViolationException($"Remaining length is invalid (Data={string.Join(",", temp.Slice(1, index).ToArray())}).");
|
||||
}
|
||||
|
||||
multiplier *= 128;
|
||||
} while ((encodedByte & 128) != 0);
|
||||
|
||||
input = input.Slice(index);
|
||||
|
||||
headerLength = index;
|
||||
bodyLength = value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -5,10 +5,10 @@ using MQTTnet.Tests.Server;
|
||||
|
||||
namespace MQTTnet.Benchmarks
|
||||
{
|
||||
[SimpleJob(RuntimeMoniker.NetCoreApp50)]
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[RPlotExporter, RankColumn]
|
||||
[MemoryDiagnoser]
|
||||
public class RoundtripProcessingBenchmark
|
||||
public class RoundtripProcessingBenchmark : BaseBenchmark
|
||||
{
|
||||
[GlobalSetup]
|
||||
public void GlobalSetup()
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using MQTTnet.Formatter;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Pipelines;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MQTTnet.Benchmarks
|
||||
{
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[RPlotExporter, RankColumn]
|
||||
[MemoryDiagnoser]
|
||||
public class SendPacketAsyncBenchmark : BaseBenchmark
|
||||
{
|
||||
MemoryStream stream;
|
||||
MqttPacketBuffer buffer;
|
||||
|
||||
[GlobalSetup]
|
||||
public void GlobalSetup()
|
||||
{
|
||||
stream = new MemoryStream(1024);
|
||||
var packet = new ArraySegment<byte>(new byte[10]);
|
||||
buffer = new MqttPacketBuffer(packet);
|
||||
}
|
||||
|
||||
[Benchmark(Baseline = true)]
|
||||
public async ValueTask Before()
|
||||
{
|
||||
stream.Position = 0;
|
||||
var output = PipeWriter.Create(stream);
|
||||
|
||||
WritePacketBuffer(output, buffer);
|
||||
await output.FlushAsync();
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public async ValueTask After()
|
||||
{
|
||||
stream.Position = 0;
|
||||
var output = PipeWriter.Create(stream);
|
||||
|
||||
if (buffer.Payload.Count == 0)
|
||||
{
|
||||
await output.WriteAsync(buffer.Packet).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
WritePacketBuffer(output, buffer);
|
||||
await output.FlushAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void WritePacketBuffer(PipeWriter output, MqttPacketBuffer buffer)
|
||||
{
|
||||
// copy MqttPacketBuffer's Packet and Payload to the same buffer block of PipeWriter
|
||||
// MqttPacket will be transmitted within the bounds of a WebSocket frame after PipeWriter.FlushAsync
|
||||
|
||||
var span = output.GetSpan(buffer.Length);
|
||||
|
||||
buffer.Packet.AsSpan().CopyTo(span);
|
||||
buffer.Payload.AsSpan().CopyTo(span.Slice(buffer.Packet.Count));
|
||||
|
||||
output.Advance(buffer.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ using MQTTnet.Diagnostics;
|
||||
|
||||
namespace MQTTnet.Benchmarks
|
||||
{
|
||||
[SimpleJob(RuntimeMoniker.NetCoreApp50)]
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[RPlotExporter]
|
||||
[MemoryDiagnoser]
|
||||
public class SerializerBenchmark : BaseBenchmark
|
||||
@@ -104,7 +104,7 @@ namespace MQTTnet.Benchmarks
|
||||
return Task.FromResult(count);
|
||||
}
|
||||
|
||||
public Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
public Task WriteAsync(ArraySegment<byte> buffer, bool isEndOfPacket, CancellationToken cancellationToken)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using MQTTnet.Tests.Mockups;
|
||||
@@ -5,10 +9,10 @@ using MQTTnet.Tests.Server;
|
||||
|
||||
namespace MQTTnet.Benchmarks
|
||||
{
|
||||
[SimpleJob(RuntimeMoniker.NetCoreApp50)]
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[RPlotExporter, RankColumn]
|
||||
[MemoryDiagnoser]
|
||||
public class ServerProcessingBenchmark
|
||||
public class ServerProcessingBenchmark : BaseBenchmark
|
||||
{
|
||||
[GlobalSetup]
|
||||
public void GlobalSetup()
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using MQTTnet.Client;
|
||||
@@ -8,7 +12,7 @@ using System.Linq;
|
||||
namespace MQTTnet.Benchmarks
|
||||
{
|
||||
[MemoryDiagnoser]
|
||||
public class SubscribeBenchmark
|
||||
public class SubscribeBenchmark : BaseBenchmark
|
||||
{
|
||||
MqttServer _mqttServer;
|
||||
IMqttClient _mqttClient;
|
||||
|
||||
@@ -13,9 +13,9 @@ using MQTTnet.AspNetCore.Client.Tcp;
|
||||
|
||||
namespace MQTTnet.Benchmarks
|
||||
{
|
||||
[SimpleJob(RuntimeMoniker.NetCoreApp50)]
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[MemoryDiagnoser]
|
||||
public class TcpPipesBenchmark
|
||||
public class TcpPipesBenchmark : BaseBenchmark
|
||||
{
|
||||
IDuplexPipe _client;
|
||||
IDuplexPipe _server;
|
||||
|
||||
@@ -8,10 +8,10 @@ using BenchmarkDotNet.Jobs;
|
||||
|
||||
namespace MQTTnet.Benchmarks
|
||||
{
|
||||
[SimpleJob(RuntimeMoniker.NetCoreApp50)]
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[RPlotExporter]
|
||||
[MemoryDiagnoser]
|
||||
public class TopicFilterComparerBenchmark
|
||||
public class TopicFilterComparerBenchmark : BaseBenchmark
|
||||
{
|
||||
static readonly char[] TopicLevelSeparator = { '/' };
|
||||
|
||||
|
||||
@@ -1,31 +1,30 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace MQTTnet.Benchmarks
|
||||
{
|
||||
public class TopicGenerator
|
||||
{
|
||||
public static void Generate(
|
||||
int numPublishers, int numTopicsPerPublisher,
|
||||
out Dictionary<string, List<string>> topicsByPublisher,
|
||||
out Dictionary<string, List<string>> singleWildcardTopicsByPublisher,
|
||||
out Dictionary<string, List<string>> multiWildcardTopicsByPublisher
|
||||
)
|
||||
int numPublishers,
|
||||
int numTopicsPerPublisher,
|
||||
out Dictionary<string, List<string>> topicsByPublisher,
|
||||
out Dictionary<string, List<string>> singleWildcardTopicsByPublisher,
|
||||
out Dictionary<string, List<string>> multiWildcardTopicsByPublisher)
|
||||
{
|
||||
topicsByPublisher = new Dictionary<string, List<string>>();
|
||||
singleWildcardTopicsByPublisher = new Dictionary<string, List<string>>();
|
||||
multiWildcardTopicsByPublisher = new Dictionary<string, List<string>>();
|
||||
|
||||
// Find some reasonable distribution across three topic levels
|
||||
var topicsPerLevel = (int)Math.Pow(numTopicsPerPublisher, (1.0 / 3.0));
|
||||
var topicsPerLevel = (int)Math.Pow(numTopicsPerPublisher, 1.0 / 3.0);
|
||||
if (topicsPerLevel <= 0)
|
||||
{
|
||||
topicsPerLevel = 1;
|
||||
}
|
||||
|
||||
int numLevel1Topics = topicsPerLevel;
|
||||
int numLevel2Topics = topicsPerLevel;
|
||||
var numLevel1Topics = topicsPerLevel;
|
||||
var numLevel2Topics = topicsPerLevel;
|
||||
|
||||
var maxNumLevel3Topics = 1 + (int)((double)numTopicsPerPublisher / numLevel1Topics / numLevel2Topics);
|
||||
if (maxNumLevel3Topics <= 0)
|
||||
@@ -35,7 +34,7 @@ namespace MQTTnet.Benchmarks
|
||||
|
||||
for (var p = 0; p < numPublishers; ++p)
|
||||
{
|
||||
int publisherTopicCount = 0;
|
||||
var publisherTopicCount = 0;
|
||||
var publisherName = "pub" + p;
|
||||
for (var l1 = 0; l1 < numLevel1Topics; ++l1)
|
||||
{
|
||||
@@ -44,19 +43,22 @@ namespace MQTTnet.Benchmarks
|
||||
for (var l3 = 0; l3 < maxNumLevel3Topics; ++l3)
|
||||
{
|
||||
if (publisherTopicCount >= numTopicsPerPublisher)
|
||||
{
|
||||
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);
|
||||
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -75,7 +77,8 @@ namespace MQTTnet.Benchmarks
|
||||
topicList = new List<string>();
|
||||
topicsByPublisher.Add(publisherName, topicList);
|
||||
}
|
||||
|
||||
topicList.Add(topic);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,7 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Server;
|
||||
@@ -7,7 +11,7 @@ using System.Linq;
|
||||
namespace MQTTnet.Benchmarks
|
||||
{
|
||||
[MemoryDiagnoser]
|
||||
public class UnsubscribeBenchmark
|
||||
public class UnsubscribeBenchmark : BaseBenchmark
|
||||
{
|
||||
MqttServer _mqttServer;
|
||||
IMqttClient _mqttClient;
|
||||
|
||||
@@ -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,52 +1,56 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Packets;
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public interface IManagedMqttClient : IDisposable
|
||||
{
|
||||
event Func<ApplicationMessageDroppedEventArgs, Task> ApplicationMessageDroppedAsync;
|
||||
|
||||
event Func<EventArgs, Task> ApplicationMessageEnqueueingAsync;
|
||||
|
||||
event Func<ApplicationMessageProcessedEventArgs, Task> ApplicationMessageProcessedAsync;
|
||||
|
||||
|
||||
event Func<MqttApplicationMessageReceivedEventArgs, Task> ApplicationMessageReceivedAsync;
|
||||
|
||||
event Func<ApplicationMessageSkippedEventArgs, Task> ApplicationMessageSkippedAsync;
|
||||
|
||||
|
||||
event Func<MqttClientConnectedEventArgs, Task> ConnectedAsync;
|
||||
|
||||
|
||||
event Func<ConnectingFailedEventArgs, Task> ConnectingFailedAsync;
|
||||
|
||||
event Func<EventArgs, Task> ConnectionStateChangedAsync;
|
||||
|
||||
|
||||
event Func<MqttClientDisconnectedEventArgs, Task> DisconnectedAsync;
|
||||
|
||||
event Func<ManagedProcessFailedEventArgs, Task> SynchronizingSubscriptionsFailedAsync;
|
||||
|
||||
|
||||
event Func<SubscribeProcessedEventArgs, Task> SubscribeProcessedAsync;
|
||||
|
||||
event Func<UnsubscribeProcessedEventArgs, Task> UnsubscribeProcessedAsync;
|
||||
|
||||
int EnqueuedApplicationMessagesCount { get; }
|
||||
|
||||
IMqttClient InternalClient { get; }
|
||||
|
||||
|
||||
bool IsConnected { get; }
|
||||
|
||||
|
||||
bool IsStarted { get; }
|
||||
|
||||
|
||||
ManagedMqttClientOptions Options { get; }
|
||||
|
||||
int PendingApplicationMessagesCount { get; }
|
||||
|
||||
|
||||
Task EnqueueAsync(MqttApplicationMessage applicationMessage);
|
||||
|
||||
|
||||
Task EnqueueAsync(ManagedMqttApplicationMessage applicationMessage);
|
||||
|
||||
|
||||
Task PingAsync(CancellationToken cancellationToken = default);
|
||||
|
||||
|
||||
Task StartAsync(ManagedMqttClientOptions options);
|
||||
|
||||
Task StopAsync();
|
||||
|
||||
Task SubscribeAsync(ICollection<MqttTopicFilter> topicFilters);
|
||||
|
||||
Task UnsubscribeAsync(ICollection<string> topics);
|
||||
|
||||
Task StopAsync(MqttClientDisconnectOptions options, CancellationToken cancellationToken);
|
||||
|
||||
Task SubscribeAsync(MqttClientSubscribeOptions options);
|
||||
|
||||
Task UnsubscribeAsync(MqttClientUnsubscribeOptions options);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0</TargetFrameworks>
|
||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks>
|
||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
|
||||
|
||||
|
||||
<LangVersion>7.3</LangVersion>
|
||||
<AssemblyName>MQTTnet.Extensions.ManagedClient</AssemblyName>
|
||||
<RootNamespace>MQTTnet.Extensions.ManagedClient</RootNamespace>
|
||||
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
||||
@@ -33,7 +34,7 @@
|
||||
<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>
|
||||
<NoWarn>1591;NETSDK1138</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0'">
|
||||
@@ -57,7 +58,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
<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:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=new/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=new_005Cevents/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=new_005Coptions/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||
@@ -6,10 +6,16 @@ using System;
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public class ManagedMqttApplicationMessage
|
||||
public sealed class ManagedMqttApplicationMessage
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public ManagedMqttApplicationMessage(string id, MqttApplicationMessage applicationMessage)
|
||||
{
|
||||
Id = id ?? throw new ArgumentNullException(nameof(id));
|
||||
ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
|
||||
}
|
||||
|
||||
public string Id { get; }
|
||||
|
||||
public MqttApplicationMessage ApplicationMessage { get; set; }
|
||||
public MqttApplicationMessage ApplicationMessage { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,15 +6,19 @@ using System;
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public class ManagedMqttApplicationMessageBuilder
|
||||
public sealed class ManagedMqttApplicationMessageBuilder
|
||||
{
|
||||
private Guid _id = Guid.NewGuid();
|
||||
private MqttApplicationMessage _applicationMessage;
|
||||
MqttApplicationMessage _applicationMessage;
|
||||
string _id = Guid.NewGuid().ToString();
|
||||
|
||||
public ManagedMqttApplicationMessageBuilder WithId(Guid id)
|
||||
public ManagedMqttApplicationMessage Build()
|
||||
{
|
||||
_id = id;
|
||||
return this;
|
||||
if (_applicationMessage == null)
|
||||
{
|
||||
throw new InvalidOperationException("The ApplicationMessage cannot be null.");
|
||||
}
|
||||
|
||||
return new ManagedMqttApplicationMessage(_id, _applicationMessage);
|
||||
}
|
||||
|
||||
public ManagedMqttApplicationMessageBuilder WithApplicationMessage(MqttApplicationMessage applicationMessage)
|
||||
@@ -25,7 +29,10 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
|
||||
public ManagedMqttApplicationMessageBuilder WithApplicationMessage(Action<MqttApplicationMessageBuilder> builder)
|
||||
{
|
||||
if (builder == null) throw new ArgumentNullException(nameof(builder));
|
||||
if (builder == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(builder));
|
||||
}
|
||||
|
||||
var internalBuilder = new MqttApplicationMessageBuilder();
|
||||
builder(internalBuilder);
|
||||
@@ -34,18 +41,16 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
return this;
|
||||
}
|
||||
|
||||
public ManagedMqttApplicationMessage Build()
|
||||
public ManagedMqttApplicationMessageBuilder WithId(Guid id)
|
||||
{
|
||||
if (_applicationMessage == null)
|
||||
{
|
||||
throw new InvalidOperationException("The ApplicationMessage cannot be null.");
|
||||
}
|
||||
_id = id.ToString();
|
||||
return this;
|
||||
}
|
||||
|
||||
return new ManagedMqttApplicationMessage
|
||||
{
|
||||
Id = _id,
|
||||
ApplicationMessage = _applicationMessage
|
||||
};
|
||||
public ManagedMqttApplicationMessageBuilder WithId(string id)
|
||||
{
|
||||
_id = id ?? throw new ArgumentNullException(nameof(id));
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,10 +17,10 @@ using MQTTnet.Server;
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public sealed class ManagedMqttClient : Disposable, IManagedMqttClient
|
||||
public sealed class ManagedMqttClient : Disposable
|
||||
{
|
||||
readonly AsyncEvent<ApplicationMessageProcessedEventArgs> _applicationMessageProcessedEvent = new AsyncEvent<ApplicationMessageProcessedEventArgs>();
|
||||
readonly AsyncEvent<ApplicationMessageSkippedEventArgs> _applicationMessageSkippedEvent = new AsyncEvent<ApplicationMessageSkippedEventArgs>();
|
||||
readonly AsyncEvent<ApplicationMessageDroppedEventArgs> _applicationMessageSkippedEvent = new AsyncEvent<ApplicationMessageDroppedEventArgs>();
|
||||
readonly AsyncEvent<ConnectingFailedEventArgs> _connectingFailedEvent = new AsyncEvent<ConnectingFailedEventArgs>();
|
||||
readonly AsyncEvent<EventArgs> _connectionStateChangedEvent = new AsyncEvent<EventArgs>();
|
||||
readonly AsyncEvent<ManagedProcessFailedEventArgs> _synchronizingSubscriptionsFailedEvent = new AsyncEvent<ManagedProcessFailedEventArgs>();
|
||||
@@ -51,9 +51,9 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
/// cref="MaintainConnectionAsync" />
|
||||
/// .
|
||||
/// </summary>
|
||||
readonly Dictionary<string, MqttQualityOfServiceLevel> _reconnectSubscriptions = new Dictionary<string, MqttQualityOfServiceLevel>();
|
||||
readonly Dictionary<string, MqttTopicFilter> _reconnectSubscriptions = new Dictionary<string, MqttTopicFilter>();
|
||||
|
||||
readonly Dictionary<string, MqttQualityOfServiceLevel> _subscriptions = new Dictionary<string, MqttQualityOfServiceLevel>();
|
||||
readonly Dictionary<string, MqttTopicFilter> _subscriptions = new Dictionary<string, MqttTopicFilter>();
|
||||
readonly SemaphoreSlim _subscriptionsQueuedSignal = new SemaphoreSlim(0);
|
||||
readonly HashSet<string> _unsubscriptions = new HashSet<string>();
|
||||
|
||||
@@ -62,6 +62,7 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
CancellationTokenSource _publishingCancellationToken;
|
||||
|
||||
ManagedMqttClientStorageManager _storageManager;
|
||||
bool _isCleanDisconnect;
|
||||
|
||||
public ManagedMqttClient(IMqttClient mqttClient, IMqttNetLogger logger)
|
||||
{
|
||||
@@ -75,7 +76,7 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
_logger = logger.WithSource(nameof(ManagedMqttClient));
|
||||
}
|
||||
|
||||
public event Func<ApplicationMessageSkippedEventArgs, Task> ApplicationMessageSkippedAsync
|
||||
public event Func<ApplicationMessageDroppedEventArgs, Task> ApplicationMessageSkippedAsync
|
||||
{
|
||||
add => _applicationMessageSkippedEvent.AddHandler(value);
|
||||
remove => _applicationMessageSkippedEvent.RemoveHandler(value);
|
||||
@@ -117,12 +118,18 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
remove => InternalClient.DisconnectedAsync -= value;
|
||||
}
|
||||
|
||||
public event Func<ManagedProcessFailedEventArgs, Task> SynchronizingSubscriptionsFailedAsync
|
||||
public event Func<SubscribeProcessedEventArgs, Task> SubscribeProcessedAsync;
|
||||
|
||||
public event Func<UnsubscribeProcessedEventArgs, Task> UnsubscribeProcessedAsync;
|
||||
|
||||
public event Func<ManagedProcessFailedEventArgs, Task> SubscribedAsync
|
||||
{
|
||||
add => _synchronizingSubscriptionsFailedEvent.AddHandler(value);
|
||||
remove => _synchronizingSubscriptionsFailedEvent.RemoveHandler(value);
|
||||
}
|
||||
|
||||
public event Func<EventArgs, Task> ApplicationMessageEnqueued;
|
||||
|
||||
public IMqttClient InternalClient { get; }
|
||||
|
||||
public bool IsConnected => InternalClient.IsConnected;
|
||||
@@ -160,21 +167,21 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
throw new InvalidOperationException("call StartAsync before publishing messages");
|
||||
}
|
||||
|
||||
MqttTopicValidator.ThrowIfInvalid(applicationMessage.ApplicationMessage.Topic);
|
||||
MqttTopicValidator.ThrowIfInvalid(applicationMessage.ApplicationMessage);
|
||||
|
||||
ManagedMqttApplicationMessage removedMessage = null;
|
||||
ApplicationMessageSkippedEventArgs applicationMessageSkippedEventArgs = null;
|
||||
ApplicationMessageDroppedEventArgs applicationMessageDroppedEventArgs = null;
|
||||
|
||||
try
|
||||
{
|
||||
using (await _messageQueueLock.WaitAsync(CancellationToken.None).ConfigureAwait(false))
|
||||
using (await _messageQueueLock.EnterAsync().ConfigureAwait(false))
|
||||
{
|
||||
if (_messageQueue.Count >= Options.MaxPendingMessages)
|
||||
{
|
||||
if (Options.PendingMessagesOverflowStrategy == MqttPendingMessagesOverflowStrategy.DropNewMessage)
|
||||
{
|
||||
_logger.Verbose("Skipping publish of new application message because internal queue is full.");
|
||||
applicationMessageSkippedEventArgs = new ApplicationMessageSkippedEventArgs(applicationMessage);
|
||||
applicationMessageDroppedEventArgs = new ApplicationMessageDroppedEventArgs(applicationMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -182,7 +189,7 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
removedMessage = _messageQueue.RemoveFirst();
|
||||
_logger.Verbose("Removed oldest application message from internal queue because it is full.");
|
||||
applicationMessageSkippedEventArgs = new ApplicationMessageSkippedEventArgs(removedMessage);
|
||||
applicationMessageDroppedEventArgs = new ApplicationMessageDroppedEventArgs(removedMessage);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,9 +208,9 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (_applicationMessageSkippedEvent.HasHandlers)
|
||||
if (applicationMessageDroppedEventArgs != null && _applicationMessageSkippedEvent.HasHandlers)
|
||||
{
|
||||
await _applicationMessageSkippedEvent.InvokeAsync(applicationMessageSkippedEventArgs).ConfigureAwait(false);
|
||||
await _applicationMessageSkippedEvent.InvokeAsync(applicationMessageDroppedEventArgs).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -255,10 +262,12 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
_logger.Info("Started");
|
||||
}
|
||||
|
||||
public async Task StopAsync()
|
||||
public async Task StopAsync(bool cleanDisconnect = true)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
_isCleanDisconnect = cleanDisconnect;
|
||||
|
||||
StopPublishing();
|
||||
StopMaintainingConnection();
|
||||
|
||||
@@ -289,14 +298,14 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
foreach (var topicFilter in topicFilters)
|
||||
{
|
||||
_subscriptions[topicFilter.Topic] = topicFilter.QualityOfServiceLevel;
|
||||
_subscriptions[topicFilter.Topic] = topicFilter;
|
||||
_unsubscriptions.Remove(topicFilter.Topic);
|
||||
}
|
||||
}
|
||||
|
||||
_subscriptionsQueuedSignal.Release();
|
||||
|
||||
return Task.FromResult(0);
|
||||
return CompletedTask.Instance;
|
||||
}
|
||||
|
||||
public Task UnsubscribeAsync(ICollection<string> topics)
|
||||
@@ -319,7 +328,7 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
|
||||
_subscriptionsQueuedSignal.Release();
|
||||
|
||||
return Task.FromResult(0);
|
||||
return CompletedTask.Instance;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
@@ -382,9 +391,12 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var disconnectTimeout = new CancellationTokenSource(Options.ClientOptions.Timeout))
|
||||
if (_isCleanDisconnect)
|
||||
{
|
||||
await InternalClient.DisconnectAsync(new MqttClientDisconnectOptions(), disconnectTimeout.Token).ConfigureAwait(false);
|
||||
using (var disconnectTimeout = new CancellationTokenSource(Options.ClientOptions.Timeout))
|
||||
{
|
||||
await InternalClient.DisconnectAsync(new MqttClientDisconnectOptions(), disconnectTimeout.Token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
@@ -455,18 +467,11 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
if (_reconnectSubscriptions.Any())
|
||||
{
|
||||
var subscriptions = _reconnectSubscriptions.Select(
|
||||
i => new MqttTopicFilter
|
||||
{
|
||||
Topic = i.Key,
|
||||
QualityOfServiceLevel = i.Value
|
||||
});
|
||||
|
||||
var topicFilters = new List<MqttTopicFilter>();
|
||||
|
||||
foreach (var sub in subscriptions)
|
||||
foreach (var sub in _reconnectSubscriptions)
|
||||
{
|
||||
topicFilters.Add(sub);
|
||||
topicFilters.Add(sub.Value);
|
||||
|
||||
if (topicFilters.Count == Options.MaxTopicFiltersInSubscribeUnsubscribePackets)
|
||||
{
|
||||
@@ -495,14 +500,7 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
|
||||
lock (_subscriptions)
|
||||
{
|
||||
subscriptions = _subscriptions.Select(
|
||||
i => new MqttTopicFilter
|
||||
{
|
||||
Topic = i.Key,
|
||||
QualityOfServiceLevel = i.Value
|
||||
})
|
||||
.ToList();
|
||||
|
||||
subscriptions = _subscriptions.Values.ToList();
|
||||
_subscriptions.Clear();
|
||||
|
||||
unsubscriptions = new HashSet<string>(_unsubscriptions);
|
||||
@@ -523,7 +521,7 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
|
||||
foreach (var subscription in subscriptions)
|
||||
{
|
||||
_reconnectSubscriptions[subscription.Topic] = subscription.QualityOfServiceLevel;
|
||||
_reconnectSubscriptions[subscription.Topic] = subscription;
|
||||
}
|
||||
|
||||
var addedTopicFilters = new List<MqttTopicFilter>();
|
||||
@@ -707,7 +705,7 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
await InternalClient.PublishAsync(message.ApplicationMessage).ConfigureAwait(false);
|
||||
|
||||
using (await _messageQueueLock.WaitAsync(CancellationToken.None).ConfigureAwait(false)) //lock to avoid conflict with this.PublishAsync
|
||||
using (await _messageQueueLock.EnterAsync().ConfigureAwait(false)) //lock to avoid conflict with this.PublishAsync
|
||||
{
|
||||
// While publishing this message, this.PublishAsync could have booted this
|
||||
// message off the queue to make room for another (when using a cap
|
||||
@@ -738,7 +736,7 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
//contradict the expected behavior of QoS 1 and 2, that's also true
|
||||
//for the usage of a message queue cap, so it's still consistent
|
||||
//with prior behavior in that way.
|
||||
using (await _messageQueueLock.WaitAsync(CancellationToken.None).ConfigureAwait(false)) //lock to avoid conflict with this.PublishAsync
|
||||
using (await _messageQueueLock.EnterAsync().ConfigureAwait(false)) //lock to avoid conflict with this.PublishAsync
|
||||
{
|
||||
_messageQueue.RemoveFirst(i => i.Id.Equals(message.Id));
|
||||
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Packets;
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
@@ -38,6 +39,35 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
return managedMqttClient.EnqueueAsync(applicationMessage);
|
||||
}
|
||||
|
||||
public static Task StopAsync(this IManagedMqttClient managedMqttClient, bool isClean = true, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (managedMqttClient == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(managedMqttClient));
|
||||
}
|
||||
|
||||
return managedMqttClient.StopAsync(new MqttClientDisconnectOptions
|
||||
{
|
||||
IsClean = isClean
|
||||
}, cancellationToken);
|
||||
}
|
||||
|
||||
public static Task SubscribeAsync(this IManagedMqttClient managedMqttClient, MqttTopicFilter topicFilter)
|
||||
{
|
||||
if (managedMqttClient == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(managedMqttClient));
|
||||
}
|
||||
|
||||
if (topicFilter == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(topicFilter));
|
||||
}
|
||||
|
||||
var subscribeOptions = new MqttClientSubscribeOptionsBuilder().WithTopicFilter(topicFilter).Build();
|
||||
return managedMqttClient.SubscribeAsync(subscribeOptions);
|
||||
}
|
||||
|
||||
public static Task SubscribeAsync(
|
||||
this IManagedMqttClient managedMqttClient,
|
||||
string topic,
|
||||
@@ -53,11 +83,8 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
throw new ArgumentNullException(nameof(topic));
|
||||
}
|
||||
|
||||
return managedMqttClient.SubscribeAsync(
|
||||
new List<MqttTopicFilter>
|
||||
{
|
||||
new MqttTopicFilterBuilder().WithTopic(topic).WithQualityOfServiceLevel(qualityOfServiceLevel).Build()
|
||||
});
|
||||
var subscribeOptions = new MqttClientSubscribeOptionsBuilder().WithTopicFilter(topic, qualityOfServiceLevel).Build();
|
||||
return managedMqttClient.SubscribeAsync(subscribeOptions);
|
||||
}
|
||||
|
||||
public static Task UnsubscribeAsync(this IManagedMqttClient managedMqttClient, string topic)
|
||||
@@ -72,7 +99,8 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
throw new ArgumentNullException(nameof(topic));
|
||||
}
|
||||
|
||||
return managedMqttClient.UnsubscribeAsync(new List<string> { topic });
|
||||
var unsubscribeOptions = new MqttClientUnsubscribeOptionsBuilder().WithTopicFilter(topic).Build();
|
||||
return managedMqttClient.UnsubscribeAsync(unsubscribeOptions);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,7 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
|
||||
|
||||
using (await _messagesLock.WaitAsync(CancellationToken.None).ConfigureAwait(false))
|
||||
using (await _messagesLock.EnterAsync().ConfigureAwait(false))
|
||||
{
|
||||
_messages.Add(applicationMessage);
|
||||
await SaveAsync().ConfigureAwait(false);
|
||||
@@ -45,7 +45,7 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
|
||||
|
||||
using (await _messagesLock.WaitAsync(CancellationToken.None).ConfigureAwait(false))
|
||||
using (await _messagesLock.EnterAsync().ConfigureAwait(false))
|
||||
{
|
||||
var index = _messages.IndexOf(applicationMessage);
|
||||
if (index == -1)
|
||||
|
||||
@@ -10,24 +10,54 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public static class MqttFactoryExtensions
|
||||
{
|
||||
public static IManagedMqttClient CreateManagedMqttClient(this MqttFactory factory, IMqttClient mqttClient = null)
|
||||
public static IManagedMqttClient CreateManagedMqttClient(this MqttFactory factory, IMqttClient mqttClient, IMqttNetLogger logger)
|
||||
{
|
||||
if (factory == null) throw new ArgumentNullException(nameof(factory));
|
||||
if (factory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(factory));
|
||||
}
|
||||
|
||||
if (mqttClient == null)
|
||||
{
|
||||
return new ManagedMqttClient(factory.CreateMqttClient(), factory.DefaultLogger);
|
||||
return new NewManagedMqttClient(factory.CreateMqttClient(), logger);
|
||||
}
|
||||
|
||||
return new ManagedMqttClient(mqttClient, factory.DefaultLogger);
|
||||
|
||||
return new NewManagedMqttClient(mqttClient, logger);
|
||||
}
|
||||
|
||||
|
||||
public static IManagedMqttClient CreateManagedMqttClient(this MqttFactory factory, IMqttClient mqttClient = null)
|
||||
{
|
||||
if (factory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(factory));
|
||||
}
|
||||
|
||||
if (mqttClient == null)
|
||||
{
|
||||
return new NewManagedMqttClient(factory.CreateMqttClient(), factory.DefaultLogger);
|
||||
}
|
||||
|
||||
return new NewManagedMqttClient(mqttClient, factory.DefaultLogger);
|
||||
}
|
||||
|
||||
public static IManagedMqttClient CreateManagedMqttClient(this MqttFactory factory, IMqttNetLogger logger)
|
||||
{
|
||||
if (factory == null) throw new ArgumentNullException(nameof(factory));
|
||||
if (logger == null) throw new ArgumentNullException(nameof(logger));
|
||||
if (factory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(factory));
|
||||
}
|
||||
|
||||
return new ManagedMqttClient(factory.CreateMqttClient(logger), logger);
|
||||
if (logger == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
return new NewManagedMqttClient(factory.CreateMqttClient(logger), logger);
|
||||
}
|
||||
|
||||
public static ManagedMqttClientOptionsBuilder CreateManagedMqttClientOptionsBuilder(this MqttFactory factory)
|
||||
{
|
||||
return new ManagedMqttClientOptionsBuilder();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Internal;
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public sealed class ApplicationMessagesManager
|
||||
{
|
||||
readonly MqttNetSourceLogger _logger;
|
||||
|
||||
readonly Queue<ManagedMqttApplicationMessage> _messageQueue = new Queue<ManagedMqttApplicationMessage>();
|
||||
readonly IMqttClient _mqttClient;
|
||||
|
||||
public ApplicationMessagesManager(IMqttClient mqttClient, IMqttNetLogger logger)
|
||||
{
|
||||
_mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
|
||||
|
||||
if (logger == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
_logger = logger.WithSource<ApplicationMessagesManager>();
|
||||
}
|
||||
|
||||
public AsyncEvent<ApplicationMessageDroppedEventArgs> ApplicationMessageDroppedEvent { get; } = new AsyncEvent<ApplicationMessageDroppedEventArgs>();
|
||||
|
||||
public AsyncEvent<ApplicationMessageEnqueueingEventArgs> ApplicationMessageEnqueueingEvent { get; } = new AsyncEvent<ApplicationMessageEnqueueingEventArgs>();
|
||||
|
||||
public int EnqueuedApplicationMessagesCount
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_messageQueue)
|
||||
{
|
||||
return _messageQueue.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dequeue()
|
||||
{
|
||||
lock (_messageQueue)
|
||||
{
|
||||
_messageQueue.Dequeue();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task Enqueue(ManagedMqttApplicationMessage applicationMessage)
|
||||
{
|
||||
if (applicationMessage == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(applicationMessage));
|
||||
}
|
||||
|
||||
// Fire the event first so that the handler can make sure that this message is
|
||||
// properly backed up in a suitable storage.
|
||||
if (ApplicationMessageEnqueueingEvent.HasHandlers)
|
||||
{
|
||||
var eventArgs = new ApplicationMessageEnqueueingEventArgs(applicationMessage, 0);
|
||||
await ApplicationMessageEnqueueingEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
lock (_messageQueue)
|
||||
{
|
||||
|
||||
// TODO: Drop if too much!
|
||||
|
||||
_messageQueue.Enqueue(applicationMessage);
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryPeek(out ManagedMqttApplicationMessage applicationMessage)
|
||||
{
|
||||
lock (_messageQueue)
|
||||
{
|
||||
if (_messageQueue.Count == 0)
|
||||
{
|
||||
applicationMessage = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
applicationMessage = _messageQueue.Peek();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -6,13 +6,13 @@ using System;
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public sealed class ApplicationMessageSkippedEventArgs : EventArgs
|
||||
public sealed class ApplicationMessageDroppedEventArgs : EventArgs
|
||||
{
|
||||
public ApplicationMessageSkippedEventArgs(ManagedMqttApplicationMessage applicationMessage)
|
||||
public ApplicationMessageDroppedEventArgs(ManagedMqttApplicationMessage applicationMessage)
|
||||
{
|
||||
ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
|
||||
}
|
||||
|
||||
public ManagedMqttApplicationMessage ApplicationMessage { get; }
|
||||
}
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public sealed class ApplicationMessageEnqueueingEventArgs : EventArgs
|
||||
{
|
||||
public ApplicationMessageEnqueueingEventArgs(ManagedMqttApplicationMessage applicationMessage, ushort packetIdentifier)
|
||||
{
|
||||
ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
|
||||
PacketIdentifier = packetIdentifier;
|
||||
}
|
||||
|
||||
public ManagedMqttApplicationMessage ApplicationMessage { get; }
|
||||
|
||||
public ushort PacketIdentifier { get; }
|
||||
}
|
||||
}
|
||||
+5
-3
@@ -15,10 +15,12 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
}
|
||||
|
||||
public ManagedMqttApplicationMessage ApplicationMessage { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Then this is _null_ the message was processed successfully without any error.
|
||||
/// Gets or sets the exception which was thrown while sending the message.
|
||||
/// </summary>
|
||||
public Exception Exception { get; }
|
||||
|
||||
public bool HasFailed => Exception != null;
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
-1
@@ -1,3 +1,7 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using MQTTnet.Client;
|
||||
|
||||
@@ -12,7 +16,7 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is null when the connection was failing and the server was not reachable.
|
||||
/// This is null when the server was not reachable and thus no MQTT response was transmitted.
|
||||
/// </summary>
|
||||
public MqttClientConnectResult ConnectResult { get; }
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Packets;
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public sealed class SubscribeProcessedEventArgs : EventArgs
|
||||
{
|
||||
public SubscribeProcessedEventArgs(MqttClientSubscribeOptions options, MqttClientSubscribeResultCode result, IReadOnlyCollection<MqttUserProperty> userProperties)
|
||||
{
|
||||
Options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
Result = result;
|
||||
UserProperties = userProperties;
|
||||
}
|
||||
|
||||
public MqttClientSubscribeOptions Options { get; }
|
||||
|
||||
public MqttClientSubscribeResultCode Result { get; }
|
||||
|
||||
public IReadOnlyCollection<MqttUserProperty> UserProperties { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Packets;
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public sealed class UnsubscribeProcessedEventArgs : EventArgs
|
||||
{
|
||||
public UnsubscribeProcessedEventArgs(MqttClientUnsubscribeOptions options, MqttClientUnsubscribeResultCode result, IReadOnlyCollection<MqttUserProperty> userProperties)
|
||||
{
|
||||
Options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
Result = result;
|
||||
UserProperties = userProperties;
|
||||
}
|
||||
|
||||
public MqttClientUnsubscribeOptions Options { get; }
|
||||
|
||||
public MqttClientUnsubscribeResultCode Result { get; }
|
||||
|
||||
public IReadOnlyCollection<MqttUserProperty> UserProperties { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public enum MaintainConnectionResult
|
||||
{
|
||||
NoChange,
|
||||
|
||||
ConnectedEstablished,
|
||||
|
||||
NotConnected
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// 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.Linq;
|
||||
using MQTTnet.Client;
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public sealed class ManagedMqttSubscription
|
||||
{
|
||||
public ManagedMqttSubscription(MqttClientSubscribeOptions subscribeOptions)
|
||||
{
|
||||
SubscribeOptions = subscribeOptions ?? throw new ArgumentNullException(nameof(subscribeOptions));
|
||||
|
||||
Topic = subscribeOptions.TopicFilters.First().Topic;
|
||||
}
|
||||
|
||||
public bool IsObsolete => UnsubscribeOptions != null;
|
||||
|
||||
public bool IsPending { get; set; } = true;
|
||||
|
||||
public MqttClientSubscribeOptions SubscribeOptions { get; }
|
||||
|
||||
public string Topic { get; }
|
||||
|
||||
public MqttClientUnsubscribeOptions UnsubscribeOptions { get; private set; }
|
||||
|
||||
public void MarkAsUnsubscribed(MqttClientUnsubscribeOptions options)
|
||||
{
|
||||
UnsubscribeOptions = options ?? throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Internal;
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public sealed class ManagedMqttSubscriptionsManager
|
||||
{
|
||||
readonly MqttNetSourceLogger _logger;
|
||||
readonly IMqttClient _mqttClient;
|
||||
|
||||
// Do not use a dictionary in order to preserve subscription ordering (retained messages).
|
||||
readonly List<ManagedMqttSubscription> _subscriptions = new List<ManagedMqttSubscription>();
|
||||
|
||||
public ManagedMqttSubscriptionsManager(IMqttClient mqttClient, IMqttNetLogger logger)
|
||||
{
|
||||
_mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
|
||||
|
||||
if (logger == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
_logger = logger.WithSource<ManagedMqttSubscriptionsManager>();
|
||||
}
|
||||
|
||||
public AsyncEvent<SubscribeProcessedEventArgs> SubscribeProcessedEvent { get; } = new AsyncEvent<SubscribeProcessedEventArgs>();
|
||||
|
||||
public AsyncEvent<UnsubscribeProcessedEventArgs> UnsubscribeProcessedEvent { get; } = new AsyncEvent<UnsubscribeProcessedEventArgs>();
|
||||
|
||||
public void ResubscribeAll()
|
||||
{
|
||||
lock (_subscriptions)
|
||||
{
|
||||
foreach (var subscription in _subscriptions)
|
||||
{
|
||||
subscription.IsPending = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Subscribe(MqttClientSubscribeOptions options)
|
||||
{
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
if (options.TopicFilters.Count > 1)
|
||||
{
|
||||
throw new NotSupportedException("The managed client only supports subscribing to one topic at the same time.");
|
||||
}
|
||||
|
||||
var topic = options.TopicFilters.First().Topic;
|
||||
|
||||
lock (_subscriptions)
|
||||
{
|
||||
MqttTopicValidator.ThrowIfInvalidSubscribe(topic);
|
||||
|
||||
_subscriptions.RemoveAll(s => s.Topic.Equals(topic));
|
||||
_subscriptions.Add(new ManagedMqttSubscription(options));
|
||||
}
|
||||
}
|
||||
|
||||
public async Task Synchronize(CancellationToken cancellationToken)
|
||||
{
|
||||
// TODO: Remember real changes.
|
||||
|
||||
List<ManagedMqttSubscription> pendingSubscriptions;
|
||||
List<ManagedMqttSubscription> obsoleteSubscriptions;
|
||||
lock (_subscriptions)
|
||||
{
|
||||
pendingSubscriptions = _subscriptions.Where(s => s.IsPending).ToList();
|
||||
obsoleteSubscriptions = _subscriptions.Where(s => s.IsObsolete).ToList();
|
||||
}
|
||||
|
||||
if (pendingSubscriptions.Count == 0 && obsoleteSubscriptions.Count == 0)
|
||||
{
|
||||
_logger.Verbose("No subscriptions to synchronize");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var subscription in pendingSubscriptions)
|
||||
{
|
||||
var subscribeResult = await _mqttClient.SubscribeAsync(subscription.SubscribeOptions, cancellationToken).ConfigureAwait(false);
|
||||
subscription.IsPending = false;
|
||||
await FireSubscribeProcessedEvent(subscription, subscribeResult).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
foreach (var subscription in obsoleteSubscriptions)
|
||||
{
|
||||
var unsubscribeResult = await _mqttClient.UnsubscribeAsync(subscription.UnsubscribeOptions, cancellationToken).ConfigureAwait(false);
|
||||
lock (_subscriptions)
|
||||
{
|
||||
_subscriptions.Remove(subscription);
|
||||
}
|
||||
|
||||
await FireUnsubscribeProcessedEvent(subscription, unsubscribeResult).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
public void Unsubscribe(MqttClientUnsubscribeOptions options)
|
||||
{
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
lock (_subscriptions)
|
||||
{
|
||||
var topic = options.TopicFilters.First();
|
||||
var existingSubscription = _subscriptions.FirstOrDefault(s => s.Topic.Equals(topic));
|
||||
|
||||
if (existingSubscription != null)
|
||||
{
|
||||
existingSubscription.MarkAsUnsubscribed(options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async Task FireSubscribeProcessedEvent(ManagedMqttSubscription subscription, MqttClientSubscribeResult subscribeResult)
|
||||
{
|
||||
_logger.Info("Subscribed topic '{0}'.", subscription.Topic);
|
||||
|
||||
if (SubscribeProcessedEvent.HasHandlers)
|
||||
{
|
||||
var resultCode = subscribeResult?.Items.First().ResultCode ?? MqttClientSubscribeResultCode.UnspecifiedError;
|
||||
var eventArgs = new SubscribeProcessedEventArgs(subscription.SubscribeOptions, resultCode, subscribeResult?.UserProperties);
|
||||
await SubscribeProcessedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
async Task FireUnsubscribeProcessedEvent(ManagedMqttSubscription subscription, MqttClientUnsubscribeResult unsubscribeResult)
|
||||
{
|
||||
_logger.Info("Unsubscribed topic '{0}'.", subscription.Topic);
|
||||
|
||||
if (UnsubscribeProcessedEvent.HasHandlers)
|
||||
{
|
||||
var resultCode = unsubscribeResult?.Items.First().ResultCode ?? MqttClientUnsubscribeResultCode.UnspecifiedError;
|
||||
var eventArgs = new UnsubscribeProcessedEventArgs(subscription.UnsubscribeOptions, resultCode, unsubscribeResult?.UserProperties);
|
||||
await UnsubscribeProcessedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Internal;
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public sealed class MqttConnectionManager
|
||||
{
|
||||
readonly MqttNetSourceLogger _logger;
|
||||
readonly IMqttClient _mqttClient;
|
||||
|
||||
bool _wasConnected;
|
||||
|
||||
public MqttConnectionManager(IMqttClient mqttClient, IMqttNetLogger logger)
|
||||
{
|
||||
_mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
|
||||
|
||||
if (logger == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
_logger = logger.WithSource<MqttConnectionManager>();
|
||||
|
||||
_mqttClient.DisconnectedAsync += OnClientDisconnected;
|
||||
}
|
||||
|
||||
public AsyncEvent<MqttClientConnectedEventArgs> ConnectedEvent { get; } = new AsyncEvent<MqttClientConnectedEventArgs>();
|
||||
|
||||
public AsyncEvent<ConnectingFailedEventArgs> ConnectingFailedEvent { get; } = new AsyncEvent<ConnectingFailedEventArgs>();
|
||||
|
||||
public AsyncEvent<MqttClientDisconnectedEventArgs> DisconnectedEvent { get; } = new AsyncEvent<MqttClientDisconnectedEventArgs>();
|
||||
|
||||
public async Task<MaintainConnectionResult> MaintainConnection(ManagedMqttClientOptions options, CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
if (_wasConnected)
|
||||
{
|
||||
try
|
||||
{
|
||||
// The only valid way for testing the connection is to send a PING. So we start with this
|
||||
// in order to check if reconnecting etc. is require or not.
|
||||
await _mqttClient.PingAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// The client is properly connection. No further action required.
|
||||
return MaintainConnectionResult.NoChange;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.Error(exception, "Error while checking connection.");
|
||||
}
|
||||
}
|
||||
|
||||
MqttClientConnectResult connectResult = null;
|
||||
try
|
||||
{
|
||||
// The PING was not successful so that there is no working connection. So we connect.
|
||||
connectResult = await _mqttClient.ConnectAsync(options.ClientOptions, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
_wasConnected = true;
|
||||
_logger.Info("Connected.");
|
||||
|
||||
if (ConnectedEvent.HasHandlers)
|
||||
{
|
||||
var eventArgs = new MqttClientConnectedEventArgs(connectResult);
|
||||
await ConnectedEvent.TryInvokeAsync(eventArgs, _logger).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return MaintainConnectionResult.ConnectedEstablished;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
if (!(exception is OperationCanceledException))
|
||||
{
|
||||
_logger.Error(exception, "Error while connecting.");
|
||||
}
|
||||
|
||||
if (ConnectingFailedEvent.HasHandlers)
|
||||
{
|
||||
var eventArgs = new ConnectingFailedEventArgs(connectResult, exception);
|
||||
await ConnectingFailedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
_wasConnected = false;
|
||||
return MaintainConnectionResult.NotConnected;
|
||||
}
|
||||
|
||||
Task OnClientDisconnected(MqttClientDisconnectedEventArgs arg)
|
||||
{
|
||||
_wasConnected = false;
|
||||
return DisconnectedEvent?.InvokeAsync(arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,309 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Internal;
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public sealed class NewManagedMqttClient : Disposable, IManagedMqttClient
|
||||
{
|
||||
readonly AsyncEvent<ApplicationMessageProcessedEventArgs> _applicationMessageProcessedEvent = new AsyncEvent<ApplicationMessageProcessedEventArgs>();
|
||||
readonly ApplicationMessagesManager _applicationMessagesManager;
|
||||
readonly MqttConnectionManager _connectionManager;
|
||||
readonly AsyncEvent<EventArgs> _isConnectedChangedEvent = new AsyncEvent<EventArgs>();
|
||||
readonly MqttNetSourceLogger _logger;
|
||||
readonly ManagedMqttSubscriptionsManager _subscriptionsManager;
|
||||
|
||||
readonly ManualResetEventSlim _workSignal = new ManualResetEventSlim();
|
||||
|
||||
CancellationTokenSource _workerCancellationToken;
|
||||
Task _workerTask;
|
||||
|
||||
public NewManagedMqttClient(IMqttClient mqttClient, IMqttNetLogger logger)
|
||||
{
|
||||
InternalClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
|
||||
|
||||
if (logger == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
InternalClient.DisconnectedAsync += OnInternalClientDisconnected;
|
||||
|
||||
_logger = logger.WithSource(nameof(NewManagedMqttClient));
|
||||
|
||||
_connectionManager = new MqttConnectionManager(mqttClient, logger);
|
||||
_subscriptionsManager = new ManagedMqttSubscriptionsManager(mqttClient, logger);
|
||||
_applicationMessagesManager = new ApplicationMessagesManager(mqttClient, logger);
|
||||
|
||||
// TODO: Start timer for periodical connection checking.
|
||||
}
|
||||
|
||||
public event Func<EventArgs, Task> ApplicationMessageEnqueueingAsync
|
||||
{
|
||||
add => _applicationMessagesManager.ApplicationMessageEnqueueingEvent.AddHandler(value);
|
||||
remove => _applicationMessagesManager.ApplicationMessageEnqueueingEvent.RemoveHandler(value);
|
||||
}
|
||||
|
||||
public event Func<ApplicationMessageProcessedEventArgs, Task> ApplicationMessageProcessedAsync
|
||||
{
|
||||
add => _applicationMessageProcessedEvent.AddHandler(value);
|
||||
remove => _applicationMessageProcessedEvent.RemoveHandler(value);
|
||||
}
|
||||
|
||||
public event Func<MqttApplicationMessageReceivedEventArgs, Task> ApplicationMessageReceivedAsync
|
||||
{
|
||||
add => InternalClient.ApplicationMessageReceivedAsync += value;
|
||||
remove => InternalClient.ApplicationMessageReceivedAsync += value;
|
||||
}
|
||||
|
||||
public event Func<ApplicationMessageDroppedEventArgs, Task> ApplicationMessageDroppedAsync
|
||||
{
|
||||
add => _applicationMessagesManager.ApplicationMessageDroppedEvent.AddHandler(value);
|
||||
remove => _applicationMessagesManager.ApplicationMessageDroppedEvent.RemoveHandler(value);
|
||||
}
|
||||
|
||||
public event Func<MqttClientConnectedEventArgs, Task> ConnectedAsync
|
||||
{
|
||||
add => _connectionManager.ConnectedEvent.AddHandler(value);
|
||||
remove => _connectionManager.ConnectedEvent.RemoveHandler(value);
|
||||
}
|
||||
|
||||
public event Func<ConnectingFailedEventArgs, Task> ConnectingFailedAsync
|
||||
{
|
||||
add => _connectionManager.ConnectingFailedEvent.AddHandler(value);
|
||||
remove => _connectionManager.ConnectingFailedEvent.RemoveHandler(value);
|
||||
}
|
||||
|
||||
public event Func<MqttClientDisconnectedEventArgs, Task> DisconnectedAsync
|
||||
{
|
||||
add => _connectionManager.DisconnectedEvent.AddHandler(value);
|
||||
remove => _connectionManager.DisconnectedEvent.RemoveHandler(value);
|
||||
}
|
||||
|
||||
public event Func<SubscribeProcessedEventArgs, Task> SubscribeProcessedAsync
|
||||
{
|
||||
add => _subscriptionsManager.SubscribeProcessedEvent.AddHandler(value);
|
||||
remove => _subscriptionsManager.SubscribeProcessedEvent.RemoveHandler(value);
|
||||
}
|
||||
|
||||
public event Func<UnsubscribeProcessedEventArgs, Task> UnsubscribeProcessedAsync
|
||||
{
|
||||
add => _subscriptionsManager.UnsubscribeProcessedEvent.AddHandler(value);
|
||||
remove => _subscriptionsManager.UnsubscribeProcessedEvent.RemoveHandler(value);
|
||||
}
|
||||
|
||||
public IMqttClient InternalClient { get; }
|
||||
|
||||
public bool IsConnected { get; private set; }
|
||||
|
||||
public bool IsStarted { get; private set; }
|
||||
|
||||
public ManagedMqttClientOptions Options { get; private set; }
|
||||
|
||||
public int EnqueuedApplicationMessagesCount => _applicationMessagesManager.EnqueuedApplicationMessagesCount;
|
||||
|
||||
public Task EnqueueAsync(MqttApplicationMessage applicationMessage)
|
||||
{
|
||||
return EnqueueAsync(new ManagedMqttApplicationMessage(Guid.NewGuid().ToString(), applicationMessage));
|
||||
}
|
||||
|
||||
public async Task EnqueueAsync(ManagedMqttApplicationMessage applicationMessage)
|
||||
{
|
||||
if (applicationMessage == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(applicationMessage));
|
||||
}
|
||||
|
||||
await _applicationMessagesManager.Enqueue(applicationMessage).ConfigureAwait(false);
|
||||
_workSignal.Set();
|
||||
}
|
||||
|
||||
public Task PingAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return InternalClient.PingAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public Task StartAsync(ManagedMqttClientOptions options)
|
||||
{
|
||||
Options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
|
||||
_workerCancellationToken = new CancellationTokenSource();
|
||||
|
||||
_workerTask = Task.Factory.StartNew(
|
||||
() => WorkerLoop(_workerCancellationToken.Token),
|
||||
_workerCancellationToken.Token,
|
||||
TaskCreationOptions.PreferFairness,
|
||||
TaskScheduler.Default);
|
||||
|
||||
_workSignal.Set();
|
||||
|
||||
return CompletedTask.Instance;
|
||||
}
|
||||
|
||||
public async Task StopAsync(MqttClientDisconnectOptions options, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
// TODO: Send proper Disconnect signal.
|
||||
|
||||
try
|
||||
{
|
||||
await InternalClient.DisconnectAsync(options, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.Error(exception, "Error while disconnecting.");
|
||||
}
|
||||
|
||||
_workerCancellationToken?.Cancel(false);
|
||||
_workerCancellationToken?.Dispose();
|
||||
|
||||
_workerCancellationToken = null;
|
||||
|
||||
if (_workerTask != null)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public Task SubscribeAsync(MqttClientSubscribeOptions options)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
_subscriptionsManager.Subscribe(options);
|
||||
_workSignal.Set();
|
||||
|
||||
return CompletedTask.Instance;
|
||||
}
|
||||
|
||||
public Task UnsubscribeAsync(MqttClientUnsubscribeOptions options)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
_subscriptionsManager.Unsubscribe(options);
|
||||
_workSignal.Set();
|
||||
|
||||
return CompletedTask.Instance;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
Task OnInternalClientDisconnected(MqttClientDisconnectedEventArgs eventArgs)
|
||||
{
|
||||
_workSignal.Set();
|
||||
return CompletedTask.Instance;
|
||||
}
|
||||
|
||||
async Task UpdateConnectionStatus(MaintainConnectionResult maintainConnectionResult)
|
||||
{
|
||||
var isConnected = maintainConnectionResult != MaintainConnectionResult.NotConnected;
|
||||
|
||||
if (IsConnected.Equals(isConnected))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
IsConnected = isConnected;
|
||||
|
||||
if (_isConnectedChangedEvent.HasHandlers)
|
||||
{
|
||||
await _isConnectedChangedEvent.InvokeAsync(EventArgs.Empty).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
async Task WorkerLoop(CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
IsStarted = true;
|
||||
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
_workSignal.Wait(cancellationToken);
|
||||
_workSignal.Reset();
|
||||
|
||||
var maintainConnectionResult = await _connectionManager.MaintainConnection(Options, cancellationToken).ConfigureAwait(false);
|
||||
await UpdateConnectionStatus(maintainConnectionResult).ConfigureAwait(false);
|
||||
|
||||
if (maintainConnectionResult == MaintainConnectionResult.ConnectedEstablished && Options.ClientOptions.CleanSession)
|
||||
{
|
||||
// Since the session is clean the server will now know any subscription so that
|
||||
// we have to subscribe to all of them again.
|
||||
_subscriptionsManager.ResubscribeAll();
|
||||
}
|
||||
|
||||
if (IsConnected)
|
||||
{
|
||||
await _subscriptionsManager.Synchronize(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (IsConnected)
|
||||
{
|
||||
// TODO: Move into pending messages manager + locking.
|
||||
while (_applicationMessagesManager.TryPeek(out var nextPendingMessage))
|
||||
{
|
||||
Exception processingException = null;
|
||||
try
|
||||
{
|
||||
_logger.Verbose("Publishing managed application message {0}.", nextPendingMessage.Id);
|
||||
|
||||
await InternalClient.PublishAsync(nextPendingMessage.ApplicationMessage, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// The message was delivered successfully. So we can get rid of it.
|
||||
_applicationMessagesManager.Dequeue();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
processingException = exception;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (_applicationMessageProcessedEvent.HasHandlers)
|
||||
{
|
||||
var eventArgs = new ApplicationMessageProcessedEventArgs(nextPendingMessage, processingException);
|
||||
await _applicationMessageProcessedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
#if !NETSTANDARD1_3
|
||||
catch (ThreadAbortException)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.Error(exception, "Error while maintaining connection.");
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsStarted = false;
|
||||
|
||||
await UpdateConnectionStatus(MaintainConnectionResult.NotConnected).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
// TODO: Disconnected!
|
||||
}
|
||||
}
|
||||
}
|
||||
+9
@@ -10,6 +10,15 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public sealed class ManagedMqttClientOptions
|
||||
{
|
||||
public ManagedMqttClientOptions()
|
||||
{
|
||||
}
|
||||
|
||||
public ManagedMqttClientOptions(MqttClientOptions clientOptions)
|
||||
{
|
||||
ClientOptions = clientOptions;
|
||||
}
|
||||
|
||||
public MqttClientOptions ClientOptions { get; set; }
|
||||
|
||||
public TimeSpan AutoReconnectDelay { get; set; } = TimeSpan.FromSeconds(5);
|
||||
+1
-1
@@ -8,7 +8,7 @@ using MQTTnet.Server;
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public class ManagedMqttClientOptionsBuilder
|
||||
public sealed class ManagedMqttClientOptionsBuilder
|
||||
{
|
||||
readonly ManagedMqttClientOptions _options = new ManagedMqttClientOptions();
|
||||
MqttClientOptionsBuilder _clientOptionsBuilder;
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0</TargetFrameworks>
|
||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks>
|
||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
|
||||
|
||||
@@ -33,7 +33,8 @@
|
||||
<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>
|
||||
<NoWarn>1591;NETSDK1138</NoWarn>
|
||||
<LangVersion>7.3</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0'">
|
||||
@@ -57,7 +58,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -11,10 +11,12 @@ namespace MQTTnet.Extensions.Rpc
|
||||
{
|
||||
public static IMqttRpcClient CreateMqttRpcClient(this MqttFactory factory, IMqttClient mqttClient)
|
||||
{
|
||||
return factory.CreateMqttRpcClient(mqttClient, new MqttRpcClientOptions
|
||||
{
|
||||
TopicGenerationStrategy = new DefaultMqttRpcClientTopicGenerationStrategy()
|
||||
});
|
||||
return factory.CreateMqttRpcClient(
|
||||
mqttClient,
|
||||
new MqttRpcClientOptions
|
||||
{
|
||||
TopicGenerationStrategy = new DefaultMqttRpcClientTopicGenerationStrategy()
|
||||
});
|
||||
}
|
||||
|
||||
public static IMqttRpcClient CreateMqttRpcClient(this MqttFactory _, IMqttClient mqttClient, MqttRpcClientOptions rpcClientOptions)
|
||||
|
||||
@@ -2,23 +2,25 @@
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Exceptions;
|
||||
using MQTTnet.Protocol;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Implementations;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Exceptions;
|
||||
using MQTTnet.Formatter;
|
||||
using MQTTnet.Internal;
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
namespace MQTTnet.Extensions.Rpc
|
||||
{
|
||||
public sealed class MqttRpcClient : IMqttRpcClient
|
||||
{
|
||||
readonly ConcurrentDictionary<string, TaskCompletionSource<byte[]>> _waitingCalls = new ConcurrentDictionary<string, TaskCompletionSource<byte[]>>();
|
||||
readonly IMqttClient _mqttClient;
|
||||
readonly MqttRpcClientOptions _options;
|
||||
|
||||
|
||||
readonly ConcurrentDictionary<string, AsyncTaskCompletionSource<byte[]>> _waitingCalls = new ConcurrentDictionary<string, AsyncTaskCompletionSource<byte[]>>();
|
||||
|
||||
public MqttRpcClient(IMqttClient mqttClient, MqttRpcClientOptions options)
|
||||
{
|
||||
_mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
|
||||
@@ -27,6 +29,18 @@ namespace MQTTnet.Extensions.Rpc
|
||||
_mqttClient.ApplicationMessageReceivedAsync += HandleApplicationMessageReceivedAsync;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_mqttClient.ApplicationMessageReceivedAsync -= HandleApplicationMessageReceivedAsync;
|
||||
|
||||
foreach (var tcs in _waitingCalls)
|
||||
{
|
||||
tcs.Value.TrySetCanceled();
|
||||
}
|
||||
|
||||
_waitingCalls.Clear();
|
||||
}
|
||||
|
||||
public async Task<byte[]> ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel)
|
||||
{
|
||||
using (var timeoutToken = new CancellationTokenSource(timeout))
|
||||
@@ -49,15 +63,13 @@ namespace MQTTnet.Extensions.Rpc
|
||||
|
||||
public async Task<byte[]> ExecuteAsync(string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (methodName == null) throw new ArgumentNullException(nameof(methodName));
|
||||
|
||||
var topicNames = _options.TopicGenerationStrategy.CreateRpcTopics(new TopicGenerationContext
|
||||
if (methodName == null)
|
||||
{
|
||||
MethodName = methodName,
|
||||
QualityOfServiceLevel = qualityOfServiceLevel,
|
||||
MqttClient = _mqttClient,
|
||||
Options = _options
|
||||
});
|
||||
throw new ArgumentNullException(nameof(methodName));
|
||||
}
|
||||
|
||||
var context = new TopicGenerationContext(_mqttClient, _options, methodName, qualityOfServiceLevel);
|
||||
var topicNames = _options.TopicGenerationStrategy.CreateRpcTopics(context);
|
||||
|
||||
var requestTopic = topicNames.RequestTopic;
|
||||
var responseTopic = topicNames.ResponseTopic;
|
||||
@@ -72,34 +84,34 @@ namespace MQTTnet.Extensions.Rpc
|
||||
throw new MqttProtocolViolationException("RPC response topic is empty.");
|
||||
}
|
||||
|
||||
var requestMessage = new MqttApplicationMessageBuilder()
|
||||
.WithTopic(requestTopic)
|
||||
.WithPayload(payload)
|
||||
.WithQualityOfServiceLevel(qualityOfServiceLevel)
|
||||
.WithResponseTopic(responseTopic)
|
||||
.Build();
|
||||
var requestMessageBuilder = new MqttApplicationMessageBuilder().WithTopic(requestTopic).WithPayload(payload).WithQualityOfServiceLevel(qualityOfServiceLevel);
|
||||
|
||||
if (_mqttClient.Options.ProtocolVersion == MqttProtocolVersion.V500)
|
||||
{
|
||||
requestMessageBuilder.WithResponseTopic(responseTopic);
|
||||
}
|
||||
|
||||
var requestMessage = requestMessageBuilder.Build();
|
||||
|
||||
try
|
||||
{
|
||||
#if NET452
|
||||
var awaitable = new TaskCompletionSource<byte[]>();
|
||||
#else
|
||||
var awaitable = new TaskCompletionSource<byte[]>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
#endif
|
||||
var awaitable = new AsyncTaskCompletionSource<byte[]>();
|
||||
|
||||
if (!_waitingCalls.TryAdd(responseTopic, awaitable))
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
var subscribeOptions = new MqttClientSubscribeOptionsBuilder()
|
||||
.WithTopicFilter(responseTopic, qualityOfServiceLevel)
|
||||
.Build();
|
||||
var subscribeOptions = new MqttClientSubscribeOptionsBuilder().WithTopicFilter(responseTopic, qualityOfServiceLevel).Build();
|
||||
|
||||
await _mqttClient.SubscribeAsync(subscribeOptions, cancellationToken).ConfigureAwait(false);
|
||||
await _mqttClient.PublishAsync(requestMessage, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
using (cancellationToken.Register(() => { awaitable.TrySetCanceled(); }))
|
||||
using (cancellationToken.Register(
|
||||
() =>
|
||||
{
|
||||
awaitable.TrySetCanceled();
|
||||
}))
|
||||
{
|
||||
return await awaitable.Task.ConfigureAwait(false);
|
||||
}
|
||||
@@ -107,8 +119,8 @@ namespace MQTTnet.Extensions.Rpc
|
||||
finally
|
||||
{
|
||||
_waitingCalls.TryRemove(responseTopic, out _);
|
||||
|
||||
await _mqttClient.UnsubscribeAsync(responseTopic).ConfigureAwait(false);
|
||||
|
||||
await _mqttClient.UnsubscribeAsync(responseTopic, CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,31 +128,15 @@ namespace MQTTnet.Extensions.Rpc
|
||||
{
|
||||
if (!_waitingCalls.TryRemove(eventArgs.ApplicationMessage.Topic, out var awaitable))
|
||||
{
|
||||
return PlatformAbstractionLayer.CompletedTask;
|
||||
return CompletedTask.Instance;
|
||||
}
|
||||
|
||||
#if NET452
|
||||
Task.Run(() => awaitable.TrySetResult(eventArgs.ApplicationMessage.Payload));
|
||||
#else
|
||||
awaitable.TrySetResult(eventArgs.ApplicationMessage.Payload);
|
||||
#endif
|
||||
|
||||
// Set this message to handled to that other code can avoid execution etc.
|
||||
eventArgs.IsHandled = true;
|
||||
|
||||
return PlatformAbstractionLayer.CompletedTask;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_mqttClient.ApplicationMessageReceivedAsync -= HandleApplicationMessageReceivedAsync;
|
||||
|
||||
foreach (var tcs in _waitingCalls)
|
||||
{
|
||||
tcs.Value.TrySetCanceled();
|
||||
}
|
||||
|
||||
_waitingCalls.Clear();
|
||||
return CompletedTask.Instance;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,13 +10,6 @@ namespace MQTTnet.Extensions.Rpc
|
||||
{
|
||||
IMqttRpcClientTopicGenerationStrategy _topicGenerationStrategy = new DefaultMqttRpcClientTopicGenerationStrategy();
|
||||
|
||||
public MqttRpcClientOptionsBuilder WithTopicGenerationStrategy(IMqttRpcClientTopicGenerationStrategy value)
|
||||
{
|
||||
_topicGenerationStrategy = value ?? throw new ArgumentNullException(nameof(value));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttRpcClientOptions Build()
|
||||
{
|
||||
return new MqttRpcClientOptions
|
||||
@@ -24,5 +17,12 @@ namespace MQTTnet.Extensions.Rpc
|
||||
TopicGenerationStrategy = _topicGenerationStrategy
|
||||
};
|
||||
}
|
||||
|
||||
public MqttRpcClientOptionsBuilder WithTopicGenerationStrategy(IMqttRpcClientTopicGenerationStrategy value)
|
||||
{
|
||||
_topicGenerationStrategy = value ?? throw new ArgumentNullException(nameof(value));
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
@@ -10,6 +10,11 @@ namespace MQTTnet.Extensions.Rpc
|
||||
{
|
||||
public MqttRpcTopicPair CreateRpcTopics(TopicGenerationContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (context.MethodName.Contains("/") || context.MethodName.Contains("+") || context.MethodName.Contains("#"))
|
||||
{
|
||||
throw new ArgumentException("The method name cannot contain /, + or #.");
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
@@ -9,20 +10,29 @@ namespace MQTTnet.Extensions.Rpc
|
||||
{
|
||||
public sealed class TopicGenerationContext
|
||||
{
|
||||
public string MethodName { get; set; }
|
||||
public TopicGenerationContext(IMqttClient mqttClient, MqttRpcClientOptions options, string methodName, MqttQualityOfServiceLevel qualityOfServiceLevel)
|
||||
{
|
||||
MethodName = methodName ?? throw new ArgumentNullException(nameof(methodName));
|
||||
QualityOfServiceLevel = qualityOfServiceLevel;
|
||||
MqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
|
||||
Options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
public string MethodName { get; }
|
||||
|
||||
public IMqttClient MqttClient { get; }
|
||||
|
||||
public MqttRpcClientOptions Options { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the quality of service level.
|
||||
/// The Quality of Service (QoS) level is an agreement between the sender of a message and the receiver of a message that defines the guarantee of delivery for a specific message.
|
||||
/// There are 3 QoS levels in MQTT:
|
||||
/// - At most once (0): Message gets delivered no time, once or multiple times.
|
||||
/// - At least once (1): Message gets delivered at least once (one time or more often).
|
||||
/// - Exactly once (2): Message gets delivered exactly once (It's ensured that the message only comes once).
|
||||
/// Gets or sets the quality of service level.
|
||||
/// The Quality of Service (QoS) level is an agreement between the sender of a message and the receiver of a message
|
||||
/// that defines the guarantee of delivery for a specific message.
|
||||
/// There are 3 QoS levels in MQTT:
|
||||
/// - At most once (0): Message gets delivered no time, once or multiple times.
|
||||
/// - At least once (1): Message gets delivered at least once (one time or more often).
|
||||
/// - Exactly once (2): Message gets delivered exactly once (It's ensured that the message only comes once).
|
||||
/// </summary>
|
||||
public MqttQualityOfServiceLevel QualityOfServiceLevel { get; set; }
|
||||
|
||||
public IMqttClient MqttClient { get; set; }
|
||||
|
||||
public MqttRpcClientOptions Options { get; set; }
|
||||
public MqttQualityOfServiceLevel QualityOfServiceLevel { get; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0</TargetFrameworks>
|
||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks>
|
||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
|
||||
|
||||
@@ -33,7 +33,8 @@
|
||||
<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>
|
||||
<NoWarn>1591;NETSDK1138</NoWarn>
|
||||
<LangVersion>7.3</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0'">
|
||||
@@ -57,15 +58,15 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All"/>
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="WebSocket4Net" Version="0.15.2"/>
|
||||
<PackageReference Include="WebSocket4Net" Version="0.15.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MQTTnet\MQTTnet.csproj"/>
|
||||
<ProjectReference Include="..\MQTTnet\MQTTnet.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -10,9 +10,12 @@ namespace MQTTnet.Extensions.WebSocket4Net
|
||||
{
|
||||
public static MqttFactory UseWebSocket4Net(this MqttFactory mqttFactory)
|
||||
{
|
||||
if (mqttFactory == null) throw new ArgumentNullException(nameof(mqttFactory));
|
||||
if (mqttFactory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(mqttFactory));
|
||||
}
|
||||
|
||||
return mqttFactory.UseClientAdapterFactory(new WebSocket4NetMqttClientAdapterFactory());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Security.Authentication;
|
||||
@@ -14,16 +15,17 @@ using System.Threading.Tasks;
|
||||
using MQTTnet.Channel;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Exceptions;
|
||||
using MQTTnet.Internal;
|
||||
using SuperSocket.ClientEngine;
|
||||
using WebSocket4Net;
|
||||
using DataReceivedEventArgs = WebSocket4Net.DataReceivedEventArgs;
|
||||
|
||||
namespace MQTTnet.Extensions.WebSocket4Net
|
||||
{
|
||||
public sealed class WebSocket4NetMqttChannel : IMqttChannel
|
||||
{
|
||||
readonly BlockingCollection<byte> _receiveBuffer = new BlockingCollection<byte>();
|
||||
|
||||
readonly MqttClientOptions _clientOptions;
|
||||
readonly BlockingCollection<byte> _receiveBuffer = new BlockingCollection<byte>();
|
||||
readonly MqttClientWebSocketOptions _webSocketOptions;
|
||||
|
||||
WebSocket _webSocket;
|
||||
@@ -34,13 +36,13 @@ namespace MQTTnet.Extensions.WebSocket4Net
|
||||
_webSocketOptions = webSocketOptions ?? throw new ArgumentNullException(nameof(webSocketOptions));
|
||||
}
|
||||
|
||||
public X509Certificate2 ClientCertificate { get; }
|
||||
|
||||
public string Endpoint => _webSocketOptions.Uri;
|
||||
|
||||
public bool IsSecureConnection { get; private set; }
|
||||
|
||||
public X509Certificate2 ClientCertificate { get; }
|
||||
|
||||
public async Task ConnectAsync(CancellationToken cancellationToken)
|
||||
public Task ConnectAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var uri = _webSocketOptions.Uri;
|
||||
if (!uri.StartsWith("ws://", StringComparison.OrdinalIgnoreCase) && !uri.StartsWith("wss://", StringComparison.OrdinalIgnoreCase))
|
||||
@@ -54,11 +56,13 @@ namespace MQTTnet.Extensions.WebSocket4Net
|
||||
uri = "wss://" + uri;
|
||||
}
|
||||
}
|
||||
|
||||
IsSecureConnection = uri.StartsWith("wss://", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
#if NET48 || NETCOREAPP3_1 || NET5 || NET6
|
||||
var sslProtocols = _webSocketOptions?.TlsOptions?.SslProtocol ?? SslProtocols.Tls12 | SslProtocols.Tls13;
|
||||
#if NET48 || NETCOREAPP3_0_OR_GREATER
|
||||
var sslProtocols = _webSocketOptions.TlsOptions?.SslProtocol ?? SslProtocols.Tls12 | SslProtocols.Tls13;
|
||||
#else
|
||||
var sslProtocols = _webSocketOptions?.TlsOptions?.SslProtocol ?? SslProtocols.Tls12 | (SslProtocols)0x00003000 /*Tls13*/;
|
||||
var sslProtocols = _webSocketOptions.TlsOptions?.SslProtocol ?? SslProtocols.Tls12 | (SslProtocols)0x00003000 /*Tls13*/;
|
||||
#endif
|
||||
|
||||
var subProtocol = _webSocketOptions.SubProtocols.FirstOrDefault() ?? string.Empty;
|
||||
@@ -89,7 +93,7 @@ namespace MQTTnet.Extensions.WebSocket4Net
|
||||
var receiveBufferSize = 0;
|
||||
|
||||
var certificates = new X509CertificateCollection();
|
||||
if (_webSocketOptions?.TlsOptions?.Certificates != null)
|
||||
if (_webSocketOptions.TlsOptions?.Certificates != null)
|
||||
{
|
||||
foreach (var certificate in _webSocketOptions.TlsOptions.Certificates)
|
||||
{
|
||||
@@ -98,7 +102,6 @@ namespace MQTTnet.Extensions.WebSocket4Net
|
||||
#else
|
||||
certificates.Add(certificate);
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,15 +110,13 @@ namespace MQTTnet.Extensions.WebSocket4Net
|
||||
NoDelay = true,
|
||||
Security =
|
||||
{
|
||||
AllowUnstrustedCertificate = _webSocketOptions?.TlsOptions?.AllowUntrustedCertificates == true,
|
||||
AllowCertificateChainErrors = _webSocketOptions?.TlsOptions?.IgnoreCertificateChainErrors == true,
|
||||
AllowUnstrustedCertificate = _webSocketOptions.TlsOptions?.AllowUntrustedCertificates == true,
|
||||
AllowCertificateChainErrors = _webSocketOptions.TlsOptions?.IgnoreCertificateChainErrors == true,
|
||||
Certificates = certificates
|
||||
}
|
||||
};
|
||||
|
||||
await ConnectInternalAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
IsSecureConnection = uri.StartsWith("wss://", StringComparison.OrdinalIgnoreCase);
|
||||
return ConnectInternalAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public Task DisconnectAsync(CancellationToken cancellationToken)
|
||||
@@ -124,8 +125,21 @@ namespace MQTTnet.Extensions.WebSocket4Net
|
||||
{
|
||||
_webSocket.Close();
|
||||
}
|
||||
|
||||
return Task.FromResult(0);
|
||||
|
||||
return CompletedTask.Instance;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_webSocket == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_webSocket.DataReceived -= OnDataReceived;
|
||||
_webSocket.Error -= OnError;
|
||||
_webSocket.Dispose();
|
||||
_webSocket = null;
|
||||
}
|
||||
|
||||
public Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
@@ -155,36 +169,10 @@ namespace MQTTnet.Extensions.WebSocket4Net
|
||||
return Task.FromResult(readBytes);
|
||||
}
|
||||
|
||||
public Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
public Task WriteAsync(ArraySegment<byte> buffer, bool isEndOfPacket, CancellationToken cancellationToken)
|
||||
{
|
||||
_webSocket.Send(buffer, offset, count);
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_webSocket == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_webSocket.DataReceived -= OnDataReceived;
|
||||
_webSocket.Error -= OnError;
|
||||
_webSocket.Dispose();
|
||||
_webSocket = null;
|
||||
}
|
||||
|
||||
static void OnError(object sender, ErrorEventArgs e)
|
||||
{
|
||||
System.Diagnostics.Debug.Write(e.Exception.ToString());
|
||||
}
|
||||
|
||||
void OnDataReceived(object sender, DataReceivedEventArgs e)
|
||||
{
|
||||
foreach (var @byte in e.Data)
|
||||
{
|
||||
_receiveBuffer.Add(@byte);
|
||||
}
|
||||
_webSocket.Send(buffer.Array, buffer.Offset, buffer.Count);
|
||||
return CompletedTask.Instance;
|
||||
}
|
||||
|
||||
async Task ConnectInternalAsync(CancellationToken cancellationToken)
|
||||
@@ -192,44 +180,50 @@ namespace MQTTnet.Extensions.WebSocket4Net
|
||||
_webSocket.Error += OnError;
|
||||
_webSocket.DataReceived += OnDataReceived;
|
||||
|
||||
var taskCompletionSource = new TaskCompletionSource<Exception>();
|
||||
var promise = new AsyncTaskCompletionSource<bool>();
|
||||
|
||||
void ErrorHandler(object sender, ErrorEventArgs e)
|
||||
{
|
||||
taskCompletionSource.TrySetResult(e.Exception);
|
||||
promise.TrySetException(e.Exception);
|
||||
}
|
||||
|
||||
void SuccessHandler(object sender, EventArgs e)
|
||||
void OpenedHandler(object sender, EventArgs e)
|
||||
{
|
||||
taskCompletionSource.TrySetResult(null);
|
||||
promise.TrySetResult(true);
|
||||
}
|
||||
|
||||
void ClosedHandler(object sender, EventArgs e)
|
||||
{
|
||||
promise.TrySetException(new MqttCommunicationException("Connection closed."));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_webSocket.Opened += SuccessHandler;
|
||||
_webSocket.Opened += OpenedHandler;
|
||||
_webSocket.Closed += ClosedHandler;
|
||||
_webSocket.Error += ErrorHandler;
|
||||
|
||||
#pragma warning disable AsyncFixer02 // Long-running or blocking operations inside an async method
|
||||
_webSocket.Open();
|
||||
#pragma warning restore AsyncFixer02 // Long-running or blocking operations inside an async method
|
||||
|
||||
using (var timeoutCts = new CancellationTokenSource(_clientOptions.Timeout))
|
||||
using (var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(timeoutCts.Token, cancellationToken))
|
||||
using (var timeout = new CancellationTokenSource(_clientOptions.Timeout))
|
||||
using (var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(timeout.Token, cancellationToken))
|
||||
{
|
||||
using (linkedCts.Token.Register(() => taskCompletionSource.TrySetCanceled()))
|
||||
using (linkedCts.Token.Register(() => promise.TrySetCanceled()))
|
||||
{
|
||||
try
|
||||
{
|
||||
await taskCompletionSource.Task.ConfigureAwait(false);
|
||||
// OpenAsync does basically the same but without support for a cancellation token.
|
||||
// So we do it on our own!
|
||||
_webSocket.Open();
|
||||
|
||||
await promise.Task.ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
var timeoutReached = timeoutCts.IsCancellationRequested && !cancellationToken.IsCancellationRequested;
|
||||
var timeoutReached = timeout.IsCancellationRequested && !cancellationToken.IsCancellationRequested;
|
||||
if (timeoutReached)
|
||||
{
|
||||
throw new MqttCommunicationTimedOutException(exception);
|
||||
}
|
||||
|
||||
|
||||
if (exception is AuthenticationException authenticationException)
|
||||
{
|
||||
throw new MqttCommunicationException(authenticationException.InnerException);
|
||||
@@ -251,9 +245,23 @@ namespace MQTTnet.Extensions.WebSocket4Net
|
||||
}
|
||||
finally
|
||||
{
|
||||
_webSocket.Opened -= SuccessHandler;
|
||||
_webSocket.Opened -= OpenedHandler;
|
||||
_webSocket.Closed -= ClosedHandler;
|
||||
_webSocket.Error -= ErrorHandler;
|
||||
}
|
||||
}
|
||||
|
||||
void OnDataReceived(object sender, DataReceivedEventArgs e)
|
||||
{
|
||||
foreach (var @byte in e.Data)
|
||||
{
|
||||
_receiveBuffer.Add(@byte);
|
||||
}
|
||||
}
|
||||
|
||||
static void OnError(object sender, ErrorEventArgs e)
|
||||
{
|
||||
Debug.Write(e.Exception.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,12 @@
|
||||
// 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.Formatter;
|
||||
using MQTTnet.Implementations;
|
||||
using System;
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Formatter;
|
||||
using MQTTnet.Implementations;
|
||||
|
||||
namespace MQTTnet.Extensions.WebSocket4Net
|
||||
{
|
||||
@@ -15,7 +15,10 @@ namespace MQTTnet.Extensions.WebSocket4Net
|
||||
{
|
||||
public IMqttChannelAdapter CreateClientAdapter(MqttClientOptions options, MqttPacketInspector packetInspector, IMqttNetLogger logger)
|
||||
{
|
||||
if (options == null) throw new ArgumentNullException(nameof(options));
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
switch (options.ChannelOptions)
|
||||
{
|
||||
@@ -23,7 +26,7 @@ namespace MQTTnet.Extensions.WebSocket4Net
|
||||
{
|
||||
return new MqttChannelAdapter(
|
||||
new MqttTcpChannel(options),
|
||||
new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(4096, 65535)),
|
||||
new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(options.WriterBufferSize, options.WriterBufferSizeMax)),
|
||||
packetInspector,
|
||||
logger);
|
||||
}
|
||||
@@ -32,16 +35,16 @@ namespace MQTTnet.Extensions.WebSocket4Net
|
||||
{
|
||||
return new MqttChannelAdapter(
|
||||
new WebSocket4NetMqttChannel(options, webSocketOptions),
|
||||
new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(4068, 65535)),
|
||||
new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(options.WriterBufferSize, options.WriterBufferSizeMax)),
|
||||
packetInspector,
|
||||
logger);
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// 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.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Internal;
|
||||
|
||||
namespace MQTTnet.TestApp
|
||||
{
|
||||
public sealed class AsyncLockTest
|
||||
{
|
||||
public async Task Run()
|
||||
{
|
||||
var asyncLock = new AsyncLock();
|
||||
|
||||
using (var cancellationToken = new CancellationTokenSource())
|
||||
{
|
||||
for (var i = 0; i < 100000; i++)
|
||||
{
|
||||
using (await asyncLock.EnterAsync(cancellationToken.Token).ConfigureAwait(false))
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ using System;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Implementations;
|
||||
using MQTTnet.Internal;
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
namespace MQTTnet.TestApp
|
||||
@@ -40,7 +40,7 @@ namespace MQTTnet.TestApp
|
||||
Console.WriteLine($"+ Retain = {e.ApplicationMessage.Retain}");
|
||||
Console.WriteLine();
|
||||
|
||||
return PlatformAbstractionLayer.CompletedTask;
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
client.ConnectedAsync += async e =>
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<DebugType>Full</DebugType>
|
||||
<TargetFrameworks>net5.0;net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>net5.0;net6.0;net7.0</TargetFrameworks>
|
||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks>
|
||||
<IsPackable>false</IsPackable>
|
||||
<EnableNETAnalyzers>false</EnableNETAnalyzers>
|
||||
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
|
||||
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
||||
<NoWarn>1591;NETSDK1138</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Compilers" Version="4.0.1" />
|
||||
<PackageReference Include="Microsoft.Net.Compilers" Version="4.0.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -23,10 +22,4 @@
|
||||
<ProjectReference Include="..\..\Source\MQTTnet\MQTTnet.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="Start.bat">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -9,7 +9,7 @@ using Newtonsoft.Json;
|
||||
using System.Collections.Generic;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Extensions.ManagedClient;
|
||||
using MQTTnet.Implementations;
|
||||
using MQTTnet.Internal;
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
namespace MQTTnet.TestApp
|
||||
@@ -42,7 +42,7 @@ namespace MQTTnet.TestApp
|
||||
managedClient.ApplicationMessageReceivedAsync += e =>
|
||||
{
|
||||
Console.WriteLine(">> RECEIVED: " + e.ApplicationMessage.Topic);
|
||||
return PlatformAbstractionLayer.CompletedTask;
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
await managedClient.StartAsync(options);
|
||||
@@ -85,7 +85,7 @@ namespace MQTTnet.TestApp
|
||||
public Task SaveQueuedMessagesAsync(IList<ManagedMqttApplicationMessage> messages)
|
||||
{
|
||||
File.WriteAllText(Filename, JsonConvert.SerializeObject(messages));
|
||||
return Task.FromResult(0);
|
||||
return CompletedTask.Instance;
|
||||
}
|
||||
|
||||
public Task<IList<ManagedMqttApplicationMessage>> LoadQueuedMessagesAsync()
|
||||
|
||||
@@ -5,6 +5,7 @@ using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Linq;
|
||||
using MQTTnet.Internal;
|
||||
|
||||
namespace MQTTnet.TestApp
|
||||
{
|
||||
@@ -124,7 +125,7 @@ namespace MQTTnet.TestApp
|
||||
{
|
||||
_cancellationTokenSource.Cancel();
|
||||
}
|
||||
return Implementations.PlatformAbstractionLayer.CompletedTask;
|
||||
return CompletedTask.Instance;
|
||||
}
|
||||
|
||||
public async Task Cleanup()
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user