mirror of
https://github.com/dotnet/MQTTnet.git
synced 2026-10-15 00:15:15 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ffc4f1408a | ||
|
|
c8b3066883 | ||
|
|
fb4f89b412 | ||
|
|
6bee6a0469 | ||
|
|
0c2ab9b231 | ||
|
|
03bef4c15d | ||
|
|
ba62ca7306 | ||
|
|
56a1530cee | ||
|
|
699cd5fc23 | ||
|
|
381c59d1e5 | ||
|
|
70f4dc9e10 | ||
|
|
0367ad31f1 | ||
|
|
5c6bcdbca6 | ||
|
|
400c0f05ad | ||
|
|
00df7c9b7b | ||
|
|
a272ed7daa | ||
|
|
b2e7a2e370 | ||
|
|
39bc70cbf0 | ||
|
|
682298b69f | ||
|
|
028a39ec25 | ||
|
|
3b104049b4 | ||
|
|
dd83a360b4 | ||
|
|
976c62d9c2 | ||
|
|
4cc9f12a03 | ||
|
|
0b5a751c56 | ||
|
|
cdb05e5894 | ||
|
|
6c08b5caf3 | ||
|
|
d5b53f3712 | ||
|
|
80fbb2f0b2 | ||
|
|
b4f90decc7 | ||
|
|
61b324043a | ||
|
|
2ced921008 | ||
|
|
1e2fee0f33 | ||
|
|
ac36c9c406 | ||
|
|
f70c79aaf9 | ||
|
|
0839fa5757 | ||
|
|
60bc4ebcee | ||
|
|
65e239652d | ||
|
|
6b9015a928 | ||
|
|
a740c38ee6 | ||
|
|
603639126e | ||
|
|
043cfdb3f9 | ||
|
|
b0da4b2b2d | ||
|
|
5402859a30 | ||
|
|
2b10200101 | ||
|
|
55e8927ab7 | ||
|
|
d30fe61264 | ||
|
|
f7ea2d29e1 | ||
|
|
e6cfef5295 | ||
|
|
0dea9e6a2b | ||
|
|
ae80de26ec | ||
|
|
53bd2a0960 | ||
|
|
b55f5ff298 | ||
|
|
21048b51ae | ||
|
|
773fd09166 | ||
|
|
57532a290a | ||
|
|
740a35b018 | ||
|
|
6e995230a7 | ||
|
|
65a8b091b9 | ||
|
|
fa34908d82 | ||
|
|
b09eb1213c | ||
|
|
23ae8ab3b5 | ||
|
|
0bdb7e9e44 | ||
|
|
54e3cd7cb7 | ||
|
|
16ad07cfc9 | ||
|
|
dee0adcd8c | ||
|
|
acbf1226ab | ||
|
|
f002744d87 | ||
|
|
9efee7af21 | ||
|
|
9814b10496 | ||
|
|
978694b3c0 | ||
|
|
bc6f155f12 | ||
|
|
d494c2104b | ||
|
|
8035c6859f | ||
|
|
2f10295022 | ||
|
|
e5f32730a7 | ||
|
|
cd257b4d25 | ||
|
|
ce534baa75 | ||
|
|
2b420b8e66 | ||
|
|
35300abfb5 | ||
|
|
3d0ecc05ff | ||
|
|
bfb6ec8856 | ||
|
|
bf1d682a85 | ||
|
|
7d9326a147 | ||
|
|
7fd09009e7 | ||
|
|
9fa5e2dc1f | ||
|
|
15d4ee4d68 | ||
|
|
6ca6a52ae8 | ||
|
|
3d643aa4cb | ||
|
|
9739d23b6f | ||
|
|
c43acf7dac | ||
|
|
995c226893 | ||
|
|
8574bbb64f | ||
|
|
b2bb545947 | ||
|
|
bf8dae67de | ||
|
|
c6309da015 | ||
|
|
2e83a1010e | ||
|
|
d5efb31e3a | ||
|
|
b77798f885 | ||
|
|
5923c7c408 | ||
|
|
7dfb6fdc52 | ||
|
|
4e40b2ddf3 | ||
|
|
4e9abd41c0 | ||
|
|
7240b86d98 | ||
|
|
211853d519 | ||
|
|
f39589a8c1 | ||
|
|
e387971292 | ||
|
|
4fc1912ee8 | ||
|
|
31a704b675 | ||
|
|
c5c088300f | ||
|
|
ccea8e6eb5 | ||
|
|
ac60ed4559 | ||
|
|
8026263a27 | ||
|
|
6fa6abe6f0 | ||
|
|
e8d5bd7bfb | ||
|
|
2a03c1f1f4 | ||
|
|
0c6b92536e | ||
|
|
dddc1ec112 | ||
|
|
8b376874c0 | ||
|
|
0322660561 | ||
|
|
a7205ddf02 | ||
|
|
64b38487bc | ||
|
|
bc20850fba | ||
|
|
66048931a2 | ||
|
|
8444b6b3be | ||
|
|
baac26c5f6 | ||
|
|
56e9e15ac0 | ||
|
|
beb54acc2f | ||
|
|
d573423372 | ||
|
|
410bc211a5 | ||
|
|
e65c3f2f2b | ||
|
|
cf947415e8 | ||
|
|
c32a606a70 | ||
|
|
78c6059668 | ||
|
|
7c2adf636f | ||
|
|
d5ac8c7183 | ||
|
|
7a7fab0907 | ||
|
|
c758ae89f4 | ||
|
|
caea7910b4 | ||
|
|
6192685671 | ||
|
|
26eed30dbf | ||
|
|
20779dfece | ||
|
|
6bcfa436ea | ||
|
|
e62316105f | ||
|
|
9e318fc40d | ||
|
|
4b407d5213 | ||
|
|
841a22b2b2 | ||
|
|
840d8ab4fd | ||
|
|
2e3e58ed73 | ||
|
|
eb48f87667 | ||
|
|
a164c120d5 | ||
|
|
9dddd40dbe | ||
|
|
ad778636cf | ||
|
|
78360bc24a | ||
|
|
3480d9ac35 | ||
|
|
ae72bc3f99 | ||
|
|
c8bf36044b | ||
|
|
b05cb0d2ea | ||
|
|
fd1880f1ff | ||
|
|
70c5bd1875 | ||
|
|
00ac9ce961 | ||
|
|
1d455221e4 | ||
|
|
31b4eb7937 | ||
|
|
c88b46783b | ||
|
|
39edd22a9d | ||
|
|
afe25db0d3 | ||
|
|
f86a5af794 | ||
|
|
e6de4d014e | ||
|
|
b38df9eecd | ||
|
|
54abf84e96 | ||
|
|
66ebdfd61a | ||
|
|
4fe7c13d8e | ||
|
|
2578b380f9 | ||
|
|
cadf9f797f | ||
|
|
fd50e4e260 | ||
|
|
1cbec27b7f | ||
|
|
8d110c44f7 | ||
|
|
66a77db851 | ||
|
|
0505efbac1 | ||
|
|
99614f2e15 | ||
|
|
08e41244fc | ||
|
|
4e74b59796 | ||
|
|
2b6b268b4e | ||
|
|
09aacf011e | ||
|
|
9bea46adcb | ||
|
|
a10183628b | ||
|
|
7e0017d14a | ||
|
|
62e9a333c7 | ||
|
|
4838da48ef | ||
|
|
b5a43c416a | ||
|
|
c1297eadd4 | ||
|
|
06886fe6c2 | ||
|
|
137d1d181b | ||
|
|
97d0286a31 | ||
|
|
782dcffc62 | ||
|
|
40f7a7c593 | ||
|
|
44e5b2c2fd | ||
|
|
f844a8be5e | ||
|
|
449d8aca65 | ||
|
|
9b29f8c23a | ||
|
|
5e79581662 | ||
|
|
79a2952f94 | ||
|
|
8077d8dfeb | ||
|
|
d72ac9694e | ||
|
|
5e0bc17341 | ||
|
|
932aa1fd39 | ||
|
|
2c45e9fedd | ||
|
|
c1352d50c1 | ||
|
|
2499d29196 | ||
|
|
fbf1cbc76e | ||
|
|
719838de3b | ||
|
|
ba66c108fa | ||
|
|
5287bf5cac | ||
|
|
10740f12c4 | ||
|
|
3e6edb92c5 | ||
|
|
dc7b9414d9 | ||
|
|
8df8cb1eb6 | ||
|
|
e14ea9a240 | ||
|
|
57287a5303 | ||
|
|
febc8f774d | ||
|
|
e51042ed04 | ||
|
|
910c123887 | ||
|
|
e0899abaac | ||
|
|
9df1bb2ff2 | ||
|
|
7b987635f7 | ||
|
|
8c37cf16c0 | ||
|
|
c569c10740 | ||
|
|
325541642e | ||
|
|
97d91ec0f8 | ||
|
|
17db90d5ab | ||
|
|
e7a7bb1a7b | ||
|
|
b621806a7c | ||
|
|
7720a3f091 | ||
|
|
b22b02a0b6 | ||
|
|
d14088512b | ||
|
|
62527a91ac | ||
|
|
f9e4a97eb8 | ||
|
|
ff45be1f71 | ||
|
|
aaed2ed790 | ||
|
|
d9501de252 | ||
|
|
9fdcb08364 | ||
|
|
9f4f37bb51 | ||
|
|
49cf364352 | ||
|
|
b9a73cc23d | ||
|
|
357fd844d7 | ||
|
|
74a5522965 | ||
|
|
84dab18c2a | ||
|
|
9e90111b37 | ||
|
|
ffb1645f14 | ||
|
|
6448b217d5 | ||
|
|
1b84ab40ce | ||
|
|
af8d1ec6be | ||
|
|
04a4080768 | ||
|
|
942bf70582 | ||
|
|
b9f0ee2317 | ||
|
|
cb09bac246 | ||
|
|
109e4b2cf1 | ||
|
|
e4cf2978e6 | ||
|
|
17b74f20c9 | ||
|
|
ae11e31048 | ||
|
|
e455ce9c78 | ||
|
|
41df5cdd8b | ||
|
|
383225d7f4 | ||
|
|
a62c15ad1a | ||
|
|
5300742543 | ||
|
|
cd09a2f176 | ||
|
|
11ba46d140 | ||
|
|
e006377e64 | ||
|
|
786fa53334 | ||
|
|
9bc34e4406 | ||
|
|
c915af8dad | ||
|
|
98fbbc2864 | ||
|
|
d2049d63d6 | ||
|
|
63a12d4d40 | ||
|
|
9051f8275b | ||
|
|
a5f33bc244 | ||
|
|
b23d25217b | ||
|
|
1df6798934 | ||
|
|
580a431fbf | ||
|
|
d076aac421 | ||
|
|
e6d811ea7c |
+1
-6
@@ -1,8 +1,3 @@
|
||||
exclude:
|
||||
- /Frameworks/.*
|
||||
- /MQTTnet.TestApp.NetCore/Program.cs
|
||||
- /Tests/MQTTnet.TestApp.NetFramework/.*
|
||||
- /Tests/MQTTnet.TestApp.UniversalWindows/.*
|
||||
component_depth: 1
|
||||
component_depth: 2
|
||||
languages:
|
||||
- csharp
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<package >
|
||||
<metadata>
|
||||
<id>MQTTnet.AspNetCore</id>
|
||||
<version>2.7.4</version>
|
||||
<version>0.0.0</version>
|
||||
<authors>Christian Kratky</authors>
|
||||
<owners>Christian Kratky</owners>
|
||||
<licenseUrl>https://github.com/chkr1011/MQTTnet/blob/master/LICENSE</licenseUrl>
|
||||
@@ -10,19 +10,20 @@
|
||||
<iconUrl>https://raw.githubusercontent.com/chkr1011/MQTTnet/master/Images/Logo_128x128.png</iconUrl>
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<description>This is a support library to integrate MQTTnet into AspNetCore.</description>
|
||||
<releaseNotes>* Updated to MQTTnet 2.7.4.
|
||||
<releaseNotes>* For more release notes please check the MQTTnet release notes.
|
||||
</releaseNotes>
|
||||
<copyright>Copyright Christian Kratky 2016-2018</copyright>
|
||||
<tags>MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin</tags>
|
||||
<tags>MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin</tags>
|
||||
<dependencies>
|
||||
<group targetFramework="netstandard2.0">
|
||||
<dependency id="MQTTnet" version="2.7.4" />
|
||||
</group>
|
||||
<dependency id="MQTTnet" version="2.8.4" />
|
||||
<dependency id="Microsoft.AspNetCore.Connections.Abstractions" version="2.1.0" />
|
||||
<dependency id="Microsoft.AspNetCore.WebSockets" version="2.0.1" />
|
||||
<dependency id="Microsoft.Extensions.Hosting.Abstractions" version="2.0.1" />
|
||||
</dependencies>
|
||||
</metadata>
|
||||
|
||||
<files>
|
||||
<!-- .NET Standard 2.0 -->
|
||||
<file src="..\Frameworks\MQTTnet.AspNetCore\bin\Release\netstandard2.0\MQTTnet.AspNetCore.*" target="lib\netstandard2.0\"/>
|
||||
<file src="..\Source\MQTTnet.AspNetCore\bin\Release\netstandard2.0\MQTTnet.AspNetCore.*" target="lib\netstandard2.0\"/>
|
||||
</files>
|
||||
</package>
|
||||
</package>
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
<?xml version="1.0"?>
|
||||
<package >
|
||||
<metadata>
|
||||
<id>MQTTnet.Extensions.ManagedClient</id>
|
||||
<version>0.0.0</version>
|
||||
<authors>Christian Kratky</authors>
|
||||
<owners>Christian Kratky</owners>
|
||||
<licenseUrl>https://github.com/chkr1011/MQTTnet/blob/master/LICENSE</licenseUrl>
|
||||
<projectUrl>https://github.com/chkr1011/MQTTnet</projectUrl>
|
||||
<iconUrl>https://raw.githubusercontent.com/chkr1011/MQTTnet/master/Images/Logo_128x128.png</iconUrl>
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<description>This is an extension library which provides a managed MQTT client with additional features using MQTTnet.</description>
|
||||
<releaseNotes>* For more release notes please check the MQTTnet release notes.
|
||||
</releaseNotes>
|
||||
<copyright>Copyright Christian Kratky 2016-2018</copyright>
|
||||
<tags>MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin</tags>
|
||||
<dependencies>
|
||||
<dependency id="MQTTnet" version="2.8.4" />
|
||||
</dependencies>
|
||||
</metadata>
|
||||
|
||||
<files>
|
||||
<!-- .NET Standard 1.3 -->
|
||||
<file src="..\Source\MQTTnet.Extensions.ManagedClient\bin\Release\netstandard1.3\MQTTnet.Extensions.ManagedClient.*" target="lib\netstandard1.3\"/>
|
||||
|
||||
<!-- .NET Standard 2.0 -->
|
||||
<file src="..\Source\MQTTnet.Extensions.ManagedClient\bin\Release\netstandard2.0\MQTTnet.Extensions.ManagedClient.*" target="lib\netstandard2.0\"/>
|
||||
|
||||
<!-- Universal Windows -->
|
||||
<file src="..\Source\MQTTnet.Extensions.ManagedClient\bin\Release\uap10.0\MQTTnet.Extensions.ManagedClient.*" target="lib\uap10.0\"/>
|
||||
|
||||
<!-- .NET Framework -->
|
||||
<file src="..\Source\MQTTnet.Extensions.ManagedClient\bin\Release\net452\MQTTnet.Extensions.ManagedClient.*" target="lib\net452\"/>
|
||||
|
||||
<file src="..\Source\MQTTnet.Extensions.ManagedClient\bin\Release\netstandard2.0\MQTTnet.Extensions.ManagedClient.*" target="lib\net461\"/>
|
||||
<file src="..\Source\MQTTnet.Extensions.ManagedClient\bin\Release\netstandard2.0\MQTTnet.Extensions.ManagedClient.*" target="lib\net462\"/>
|
||||
<file src="..\Source\MQTTnet.Extensions.ManagedClient\bin\Release\netstandard2.0\MQTTnet.Extensions.ManagedClient.*" target="lib\net470\"/>
|
||||
<file src="..\Source\MQTTnet.Extensions.ManagedClient\bin\Release\netstandard2.0\MQTTnet.Extensions.ManagedClient.*" target="lib\net471\"/>
|
||||
<file src="..\Source\MQTTnet.Extensions.ManagedClient\bin\Release\netstandard2.0\MQTTnet.Extensions.ManagedClient.*" target="lib\net472\"/>
|
||||
</files>
|
||||
</package>
|
||||
@@ -2,46 +2,40 @@
|
||||
<package >
|
||||
<metadata>
|
||||
<id>MQTTnet.Extensions.Rpc</id>
|
||||
<version>2.6.0</version>
|
||||
<version>0.0.0</version>
|
||||
<authors>Christian Kratky</authors>
|
||||
<owners>Christian Kratky</owners>
|
||||
<licenseUrl>https://github.com/chkr1011/MQTTnet/blob/master/LICENSE</licenseUrl>
|
||||
<projectUrl>https://github.com/chkr1011/MQTTnet</projectUrl>
|
||||
<iconUrl>https://raw.githubusercontent.com/chkr1011/MQTTnet/master/Images/Logo_128x128.png</iconUrl>
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<description>This is a extension library which allows executing synchronous device calls including a response using MQTTnet.</description>
|
||||
<releaseNotes>* Initial version.
|
||||
<description>This is an extension library which allows executing synchronous device calls including a response using MQTTnet.</description>
|
||||
<releaseNotes>* For more release notes please check the MQTTnet release notes.
|
||||
</releaseNotes>
|
||||
<copyright>Copyright Christian Kratky 2016-2017</copyright>
|
||||
<copyright>Copyright Christian Kratky 2016-2018</copyright>
|
||||
<tags>MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin</tags>
|
||||
<dependencies>
|
||||
<group targetFramework="netstandard2.0">
|
||||
<dependency id="MQTTnet" version="2.6.0-rc1" />
|
||||
</group>
|
||||
<group targetFramework="netstandard1.3">
|
||||
<dependency id="MQTTnet" version="2.6.0-rc1" />
|
||||
</group>
|
||||
|
||||
<group targetFramework="netstandard2.0">
|
||||
<dependency id="MQTTnet" version="2.6.0-rc1" />
|
||||
</group>
|
||||
|
||||
<group targetFramework="uap10.0">
|
||||
<dependency id="MQTTnet" version="2.6.0-rc1" />
|
||||
</group>
|
||||
|
||||
<group targetFramework="net452">
|
||||
<dependency id="MQTTnet" version="2.6.0-rc1" />
|
||||
</group>
|
||||
|
||||
<group targetFramework="net461">
|
||||
<dependency id="MQTTnet" version="2.6.0-rc1" />
|
||||
</group>
|
||||
<dependency id="MQTTnet" version="2.8.4" />
|
||||
</dependencies>
|
||||
</metadata>
|
||||
|
||||
<files>
|
||||
<!-- .NET Standard 1.3 -->
|
||||
<file src="..\Source\MQTTnet.Extensions.Rpc\bin\Release\netstandard1.3\MQTTnet.Extensions.Rpc.*" target="lib\netstandard1.3\"/>
|
||||
|
||||
<!-- .NET Standard 2.0 -->
|
||||
<file src="..\Frameworks\MQTTnet.AspNetCore\bin\Release\netstandard2.0\MQTTnet.AspNetCore.*" target="lib\netstandard2.0\"/>
|
||||
<file src="..\Source\MQTTnet.Extensions.Rpc\bin\Release\netstandard2.0\MQTTnet.Extensions.Rpc.*" target="lib\netstandard2.0\"/>
|
||||
|
||||
<!-- Universal Windows -->
|
||||
<file src="..\Source\MQTTnet.Extensions.Rpc\bin\Release\uap10.0\MQTTnet.Extensions.Rpc.*" target="lib\uap10.0\"/>
|
||||
|
||||
<!-- .NET Framework -->
|
||||
<file src="..\Source\MQTTnet.Extensions.Rpc\bin\Release\net452\MQTTnet.Extensions.Rpc.*" target="lib\net452\"/>
|
||||
|
||||
<file src="..\Source\MQTTnet.Extensions.Rpc\bin\Release\netstandard2.0\MQTTnet.Extensions.Rpc.*" target="lib\net461\"/>
|
||||
<file src="..\Source\MQTTnet.Extensions.Rpc\bin\Release\netstandard2.0\MQTTnet.Extensions.Rpc.*" target="lib\net462\"/>
|
||||
<file src="..\Source\MQTTnet.Extensions.Rpc\bin\Release\netstandard2.0\MQTTnet.Extensions.Rpc.*" target="lib\net470\"/>
|
||||
<file src="..\Source\MQTTnet.Extensions.Rpc\bin\Release\netstandard2.0\MQTTnet.Extensions.Rpc.*" target="lib\net471\"/>
|
||||
<file src="..\Source\MQTTnet.Extensions.Rpc\bin\Release\netstandard2.0\MQTTnet.Extensions.Rpc.*" target="lib\net472\"/>
|
||||
</files>
|
||||
</package>
|
||||
+14
-23
@@ -2,7 +2,7 @@
|
||||
<package >
|
||||
<metadata>
|
||||
<id>MQTTnet</id>
|
||||
<version>2.7.4</version>
|
||||
<version>0.0.0</version>
|
||||
<authors>Christian Kratky</authors>
|
||||
<owners>Christian Kratky</owners>
|
||||
<licenseUrl>https://github.com/chkr1011/MQTTnet/blob/master/LICENSE</licenseUrl>
|
||||
@@ -10,59 +10,50 @@
|
||||
<iconUrl>https://raw.githubusercontent.com/chkr1011/MQTTnet/master/Images/Logo_128x128.png</iconUrl>
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<description>MQTTnet is a high performance .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker).</description>
|
||||
<releaseNotes> * [Client] Fixed a deadlock while the client disconnects.
|
||||
* [Client] Fixed broken support for protocol version 3.1.0.
|
||||
* [Server] The _MqttTcpServerAdapter_ is now added to the ASP.NET services.
|
||||
* [Server] _MqttServerAdapter_ is renamed to _MqttTcpServerAdapter_ (BREAKING CHANGE!).
|
||||
* [Server] The server no longer sends the will message of a client if the disconnect was clean (via _Disconnect_ packet).
|
||||
* [Server] The application message interceptor now allows closing the connection.
|
||||
* [Server] Added a new flag for the _ClientDisconnected_ event which contains a value indicating whether the disconnect was clean (via _Disconnect_ packet).
|
||||
<releaseNotes>* [Client] Fixed a deadlock when an exception is fired while connecting (thanks to @malibVB).
|
||||
</releaseNotes>
|
||||
<copyright>Copyright Christian Kratky 2016-2018</copyright>
|
||||
<tags>MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin</tags>
|
||||
<dependencies>
|
||||
|
||||
<group targetFramework="netstandard1.3">
|
||||
<dependency id="NETStandard.Library" version="1.3.0" />
|
||||
<dependency id="System.Net.Security" version="4.3.2" />
|
||||
<dependency id="System.Net.WebSockets" version="4.3.0" />
|
||||
<dependency id="System.Net.WebSockets.Client" version="4.3.1" />
|
||||
</group>
|
||||
|
||||
<group targetFramework="netstandard2.0">
|
||||
<dependency id="NETStandard.Library" version="2.0.0" />
|
||||
<dependency id="System.Net.Security" version="4.3.2" />
|
||||
<dependency id="System.Net.WebSockets" version="4.3.0" />
|
||||
<dependency id="System.Net.WebSockets.Client" version="4.3.1" />
|
||||
<dependency id="System.Net.WebSockets.Client" version="4.3.2" />
|
||||
</group>
|
||||
|
||||
<group targetFramework="uap10.0">
|
||||
<dependency id="Microsoft.NETCore.UniversalWindowsPlatform" version="5.4.1" />
|
||||
<dependency id="Microsoft.NETCore.UniversalWindowsPlatform" version="6.1.4" />
|
||||
</group>
|
||||
|
||||
<group targetFramework="net452">
|
||||
</group>
|
||||
|
||||
<group targetFramework="net461">
|
||||
</group>
|
||||
|
||||
</dependencies>
|
||||
</metadata>
|
||||
|
||||
<files>
|
||||
|
||||
<!-- .NET Standard 1.3 -->
|
||||
<file src="..\Frameworks\MQTTnet.Netstandard\bin\Release\netstandard1.3\MQTTnet.*" target="lib\netstandard1.3\"/>
|
||||
<file src="..\Source\MQTTnet\bin\Release\netstandard1.3\MQTTnet.*" target="lib\netstandard1.3\"/>
|
||||
|
||||
<!-- .NET Standard 2.0 -->
|
||||
<file src="..\Frameworks\MQTTnet.Netstandard\bin\Release\netstandard2.0\MQTTnet.*" target="lib\netstandard2.0\"/>
|
||||
<file src="..\Source\MQTTnet\bin\Release\netstandard2.0\MQTTnet.*" target="lib\netstandard2.0\"/>
|
||||
|
||||
<!-- Universal Windows -->
|
||||
<file src="..\Frameworks\MQTTnet.Netstandard\bin\Release\uap10.0\MQTTnet.*" target="lib\uap10.0\"/>
|
||||
<file src="..\Source\MQTTnet\bin\Release\uap10.0\MQTTnet.*" target="lib\uap10.0\"/>
|
||||
|
||||
<!-- .NET Framework -->
|
||||
<file src="..\Frameworks\MQTTnet.Netstandard\bin\Release\net452\MQTTnet.*" target="lib\net452\"/>
|
||||
<file src="..\Frameworks\MQTTnet.Netstandard\bin\Release\net461\MQTTnet.*" target="lib\net461\"/>
|
||||
|
||||
<file src="..\Source\MQTTnet\bin\Release\net452\MQTTnet.*" target="lib\net452\"/>
|
||||
|
||||
<file src="..\Source\MQTTnet\bin\Release\netstandard2.0\MQTTnet.*" target="lib\net461\"/>
|
||||
<file src="..\Source\MQTTnet\bin\Release\netstandard2.0\MQTTnet.*" target="lib\net462\"/>
|
||||
<file src="..\Source\MQTTnet\bin\Release\netstandard2.0\MQTTnet.*" target="lib\net470\"/>
|
||||
<file src="..\Source\MQTTnet\bin\Release\netstandard2.0\MQTTnet.*" target="lib\net471\"/>
|
||||
<file src="..\Source\MQTTnet\bin\Release\netstandard2.0\MQTTnet.*" target="lib\net472\"/>
|
||||
</files>
|
||||
</package>
|
||||
+27
-15
@@ -3,6 +3,10 @@ param([string]$assemblyVersion, [string]$nugetVersion)
|
||||
if ([string]::IsNullOrEmpty($assemblyVersion)) {$assemblyVersion = "0.0.1"}
|
||||
if ([string]::IsNullOrEmpty($nugetVersion)) {$nugetVersion = "0.0.1"}
|
||||
|
||||
Write-Host "Assembly version = $assemblyVersion"
|
||||
Write-Host "Nuget version = $nugetVersion"
|
||||
Write-Host
|
||||
|
||||
$vswhere = ${Env:\ProgramFiles(x86)} + '\Microsoft Visual Studio\Installer\vswhere'
|
||||
|
||||
$path = &$vswhere -latest -products * -requires Microsoft.Component.MSBuild -property installationPath
|
||||
@@ -10,26 +14,34 @@ if ($path) {
|
||||
$msbuild = join-path $path 'MSBuild\15.0\Bin\MSBuild.exe'
|
||||
|
||||
# Build the core library
|
||||
&$msbuild ..\Frameworks\MQTTnet.Netstandard\MQTTnet.Netstandard.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net452" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
|
||||
&$msbuild ..\Frameworks\MQTTnet.Netstandard\MQTTnet.Netstandard.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net461" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
|
||||
&$msbuild ..\Frameworks\MQTTnet.Netstandard\MQTTnet.Netstandard.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard1.3" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
|
||||
&$msbuild ..\Frameworks\MQTTnet.Netstandard\MQTTnet.Netstandard.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard2.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
|
||||
&$msbuild ..\Frameworks\MQTTnet.Netstandard\MQTTnet.Netstandard.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="uap10.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
|
||||
&$msbuild ..\Source\MQTTnet\MQTTnet.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net452" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
|
||||
&$msbuild ..\Source\MQTTnet\MQTTnet.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net461" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
|
||||
&$msbuild ..\Source\MQTTnet\MQTTnet.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard1.3" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
|
||||
&$msbuild ..\Source\MQTTnet\MQTTnet.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard2.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
|
||||
&$msbuild ..\Source\MQTTnet\MQTTnet.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="uap10.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
|
||||
|
||||
# Build the ASP.NET Core 2.0 extension
|
||||
&$msbuild ..\Frameworks\MQTTnet.AspNetCore\MQTTnet.AspNetCore.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard2.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
|
||||
&$msbuild ..\Source\MQTTnet.AspNetCore\MQTTnet.AspNetCore.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard2.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
|
||||
|
||||
# Build the RPC extension
|
||||
&$msbuild ..\Extensions\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net452" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m
|
||||
&$msbuild ..\Extensions\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net461" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m
|
||||
&$msbuild ..\Extensions\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard1.3" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m
|
||||
&$msbuild ..\Extensions\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard2.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m
|
||||
&$msbuild ..\Extensions\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="uap10.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m
|
||||
&$msbuild ..\Source\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net452" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
|
||||
&$msbuild ..\Source\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net461" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
|
||||
&$msbuild ..\Source\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard1.3" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
|
||||
&$msbuild ..\Source\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard2.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
|
||||
&$msbuild ..\Source\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="uap10.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
|
||||
|
||||
Remove-Item .\NuGet -Force -Recurse -ErrorAction SilentlyContinue
|
||||
# Build the Managed Client extension
|
||||
&$msbuild ..\Source\MQTTnet.Extensions.ManagedClient\MQTTnet.Extensions.ManagedClient.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net452" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
|
||||
&$msbuild ..\Source\MQTTnet.Extensions.ManagedClient\MQTTnet.Extensions.ManagedClient.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net461" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
|
||||
&$msbuild ..\Source\MQTTnet.Extensions.ManagedClient\MQTTnet.Extensions.ManagedClient.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard1.3" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
|
||||
&$msbuild ..\Source\MQTTnet.Extensions.ManagedClient\MQTTnet.Extensions.ManagedClient.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard2.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
|
||||
&$msbuild ..\Source\MQTTnet.Extensions.ManagedClient\MQTTnet.Extensions.ManagedClient.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="uap10.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
|
||||
|
||||
Remove-Item .\NuGet -Force -Recurse -ErrorAction SilentlyContinue
|
||||
|
||||
New-Item -ItemType Directory -Force -Path .\NuGet
|
||||
.\NuGet.exe pack MQTTnet.nuspec -Verbosity detailed -Symbols -OutputDir "NuGet" -Version $nugetVersion
|
||||
.\NuGet.exe pack MQTTnet.AspNetCore.nuspec -Verbosity detailed -Symbols -OutputDir "NuGet" -Version $nugetVersion
|
||||
.\NuGet.exe pack MQTTnet.Extensions.Rpc.nuspec -Verbosity detailed -Symbols -OutputDir "NuGet" -Version $nugetVersion
|
||||
.\nuget.exe pack MQTTnet.nuspec -Verbosity detailed -Symbols -OutputDir "NuGet" -Version $nugetVersion
|
||||
.\nuget.exe pack MQTTnet.AspNetCore.nuspec -Verbosity detailed -Symbols -OutputDir "NuGet" -Version $nugetVersion
|
||||
.\nuget.exe pack MQTTnet.Extensions.Rpc.nuspec -Verbosity detailed -Symbols -OutputDir "NuGet" -Version $nugetVersion
|
||||
.\nuget.exe pack MQTTnet.Extensions.ManagedClient.nuspec -Verbosity detailed -Symbols -OutputDir "NuGet" -Version $nugetVersion
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
param([string]$apiKey)
|
||||
|
||||
$files = Get-ChildItem -Path ".\NuGet" -Filter "*.nupkg"
|
||||
foreach ($file in $files)
|
||||
{
|
||||
Write-Host "Uploading: " $file
|
||||
|
||||
.\nuget.exe push $file.Fullname $apiKey -NoSymbols -Source https://api.nuget.org/v3/index.json
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Internal;
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
namespace MQTTnet.Extensions.Rpc
|
||||
{
|
||||
public sealed class MqttRpcClient : IDisposable
|
||||
{
|
||||
private const string ResponseTopic = "$MQTTnet.RPC/+/+/response";
|
||||
private readonly ConcurrentDictionary<string, TaskCompletionSource<byte[]>> _waitingCalls = new ConcurrentDictionary<string, TaskCompletionSource<byte[]>>();
|
||||
private readonly IMqttClient _mqttClient;
|
||||
private bool _isEnabled;
|
||||
|
||||
public MqttRpcClient(IMqttClient mqttClient)
|
||||
{
|
||||
_mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
|
||||
|
||||
_mqttClient.ApplicationMessageReceived += OnApplicationMessageReceived;
|
||||
}
|
||||
|
||||
public async Task EnableAsync()
|
||||
{
|
||||
await _mqttClient.SubscribeAsync(new TopicFilterBuilder().WithTopic(ResponseTopic).WithAtLeastOnceQoS().Build());
|
||||
_isEnabled = true;
|
||||
}
|
||||
|
||||
public async Task DisableAsync()
|
||||
{
|
||||
await _mqttClient.UnsubscribeAsync(ResponseTopic);
|
||||
_isEnabled = false;
|
||||
}
|
||||
|
||||
public async Task<byte[]> ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel)
|
||||
{
|
||||
if (methodName == null) throw new ArgumentNullException(nameof(methodName));
|
||||
|
||||
if (methodName.Contains("/") || methodName.Contains("+") || methodName.Contains("#"))
|
||||
{
|
||||
throw new ArgumentException("The method name cannot contain /, + or #.");
|
||||
}
|
||||
|
||||
if (!_isEnabled)
|
||||
{
|
||||
throw new InvalidOperationException("The RPC client is not enabled.");
|
||||
}
|
||||
|
||||
var requestTopic = $"$MQTTnet.RPC/{Guid.NewGuid():N}/{methodName}";
|
||||
var responseTopic = requestTopic + "/response";
|
||||
|
||||
var requestMessage = new MqttApplicationMessageBuilder()
|
||||
.WithTopic(requestTopic)
|
||||
.WithPayload(payload)
|
||||
.WithQualityOfServiceLevel(qualityOfServiceLevel)
|
||||
.Build();
|
||||
|
||||
try
|
||||
{
|
||||
var tcs = new TaskCompletionSource<byte[]>();
|
||||
if (!_waitingCalls.TryAdd(responseTopic, tcs))
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
await _mqttClient.PublishAsync(requestMessage);
|
||||
return await tcs.Task.TimeoutAfter(timeout);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_waitingCalls.TryRemove(responseTopic, out _);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnApplicationMessageReceived(object sender, MqttApplicationMessageReceivedEventArgs eventArgs)
|
||||
{
|
||||
if (!_waitingCalls.TryRemove(eventArgs.ApplicationMessage.Topic, out TaskCompletionSource<byte[]> tcs))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (tcs.Task.IsCompleted || tcs.Task.IsCanceled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
tcs.TrySetResult(eventArgs.ApplicationMessage.Payload);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var tcs in _waitingCalls)
|
||||
{
|
||||
tcs.Value.SetCanceled();
|
||||
}
|
||||
|
||||
_waitingCalls.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System.Linq;
|
||||
using MQTTnet.Server;
|
||||
|
||||
namespace MQTTnet.AspNetCore
|
||||
{
|
||||
public static class ApplicationBuilderExtensions
|
||||
{
|
||||
public static IApplicationBuilder UseMqttEndpoint(this IApplicationBuilder app, string path = "/mqtt")
|
||||
{
|
||||
app.UseWebSockets();
|
||||
app.Use(async (context, next) =>
|
||||
{
|
||||
if (context.Request.Path == path && context.WebSockets.IsWebSocketRequest)
|
||||
{
|
||||
string subprotocol = null;
|
||||
|
||||
if (context.Request.Headers.TryGetValue("Sec-WebSocket-Protocol", out var requestedSubProtocolValues)
|
||||
&& requestedSubProtocolValues.Count > 0
|
||||
&& requestedSubProtocolValues.Any(v => v.ToLower() == "mqtt")
|
||||
)
|
||||
{
|
||||
subprotocol = "mqtt";
|
||||
}
|
||||
|
||||
var adapter = app.ApplicationServices.GetRequiredService<MqttWebSocketServerAdapter>();
|
||||
using (var webSocket = await context.WebSockets.AcceptWebSocketAsync(subprotocol))
|
||||
{
|
||||
await adapter.AcceptWebSocketAsync(webSocket);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await next();
|
||||
}
|
||||
});
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
public static IApplicationBuilder UseMqttServer(this IApplicationBuilder app, Action<IMqttServer> configure)
|
||||
{
|
||||
var server = app.ApplicationServices.GetRequiredService<IMqttServer>();
|
||||
|
||||
configure(server);
|
||||
|
||||
return app;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net.WebSockets;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Channel;
|
||||
using MQTTnet.Implementations;
|
||||
|
||||
namespace MQTTnet.AspNetCore
|
||||
{
|
||||
public class MqttWebSocketServerChannel : IMqttChannel, IDisposable
|
||||
{
|
||||
private WebSocket _webSocket;
|
||||
|
||||
public MqttWebSocketServerChannel(WebSocket webSocket)
|
||||
{
|
||||
_webSocket = webSocket ?? throw new ArgumentNullException(nameof(webSocket));
|
||||
|
||||
SendStream = new WebSocketStream(_webSocket);
|
||||
ReceiveStream = SendStream;
|
||||
}
|
||||
|
||||
public Stream SendStream { get; private set; }
|
||||
public Stream ReceiveStream { get; private set; }
|
||||
|
||||
public Task ConnectAsync()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task DisconnectAsync()
|
||||
{
|
||||
if (_webSocket == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
SendStream?.Dispose();
|
||||
ReceiveStream?.Dispose();
|
||||
|
||||
_webSocket?.Dispose();
|
||||
|
||||
SendStream = null;
|
||||
ReceiveStream = null;
|
||||
_webSocket = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
using System;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Server;
|
||||
using MQTTnet.Implementations;
|
||||
|
||||
namespace MQTTnet.AspNetCore
|
||||
{
|
||||
public static class ServiceCollectionExtensions
|
||||
{
|
||||
public static IServiceCollection AddHostedMqttServer(this IServiceCollection services, IMqttServerOptions options)
|
||||
{
|
||||
if (options == null) throw new ArgumentNullException(nameof(options));
|
||||
|
||||
services.AddSingleton(options);
|
||||
services.AddSingleton<IMqttNetLogger>(new MqttNetLogger());
|
||||
services.AddSingleton<MqttHostedServer>();
|
||||
services.AddSingleton<IHostedService>(s => s.GetService<MqttHostedServer>());
|
||||
services.AddSingleton<IMqttServer>(s => s.GetService<MqttHostedServer>());
|
||||
|
||||
services.AddSingleton<MqttWebSocketServerAdapter>();
|
||||
services.AddSingleton<MqttTcpServerAdapter>();
|
||||
services.AddSingleton<IMqttServerAdapter>(s => s.GetService<MqttWebSocketServerAdapter>());
|
||||
services.AddSingleton<IMqttServerAdapter>(s => s.GetService<MqttTcpServerAdapter>());
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Packets;
|
||||
using MQTTnet.Serializer;
|
||||
|
||||
namespace MQTTnet.Adapter
|
||||
{
|
||||
public interface IMqttChannelAdapter : IDisposable
|
||||
{
|
||||
IMqttPacketSerializer PacketSerializer { get; }
|
||||
|
||||
Task ConnectAsync(TimeSpan timeout);
|
||||
|
||||
Task DisconnectAsync(TimeSpan timeout);
|
||||
|
||||
Task SendPacketsAsync(TimeSpan timeout, CancellationToken cancellationToken, IEnumerable<MqttBasePacket> packets);
|
||||
|
||||
Task<MqttBasePacket> ReceivePacketAsync(TimeSpan timeout, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -1,255 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net.Sockets;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Channel;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Exceptions;
|
||||
using MQTTnet.Internal;
|
||||
using MQTTnet.Packets;
|
||||
using MQTTnet.Serializer;
|
||||
|
||||
namespace MQTTnet.Adapter
|
||||
{
|
||||
public sealed class MqttChannelAdapter : IMqttChannelAdapter
|
||||
{
|
||||
private const uint ErrorOperationAborted = 0x800703E3;
|
||||
private const int ReadBufferSize = 4096; // TODO: Move buffer size to config
|
||||
|
||||
private bool _isDisposed;
|
||||
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
|
||||
private readonly IMqttNetLogger _logger;
|
||||
private readonly IMqttChannel _channel;
|
||||
|
||||
public MqttChannelAdapter(IMqttChannel channel, IMqttPacketSerializer serializer, IMqttNetLogger logger)
|
||||
{
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_channel = channel ?? throw new ArgumentNullException(nameof(channel));
|
||||
PacketSerializer = serializer ?? throw new ArgumentNullException(nameof(serializer));
|
||||
}
|
||||
|
||||
public IMqttPacketSerializer PacketSerializer { get; }
|
||||
|
||||
public Task ConnectAsync(TimeSpan timeout)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
_logger.Verbose<MqttChannelAdapter>("Connecting [Timeout={0}]", timeout);
|
||||
|
||||
return ExecuteAndWrapExceptionAsync(() => _channel.ConnectAsync().TimeoutAfter(timeout));
|
||||
}
|
||||
|
||||
public Task DisconnectAsync(TimeSpan timeout)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
_logger.Verbose<MqttChannelAdapter>("Disconnecting [Timeout={0}]", timeout);
|
||||
|
||||
return ExecuteAndWrapExceptionAsync(() => _channel.DisconnectAsync().TimeoutAfter(timeout));
|
||||
}
|
||||
|
||||
public Task SendPacketsAsync(TimeSpan timeout, CancellationToken cancellationToken, IEnumerable<MqttBasePacket> packets)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
return ExecuteAndWrapExceptionAsync(async () =>
|
||||
{
|
||||
await _semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
foreach (var packet in packets)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (packet == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_logger.Verbose<MqttChannelAdapter>("TX >>> {0} [Timeout={1}]", packet, timeout);
|
||||
|
||||
var chunks = PacketSerializer.Serialize(packet);
|
||||
foreach (var chunk in chunks)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await _channel.SendStream.WriteAsync(chunk.Array, chunk.Offset, chunk.Count, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (timeout > TimeSpan.Zero)
|
||||
{
|
||||
await _channel.SendStream.FlushAsync(cancellationToken).TimeoutAfter(timeout).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _channel.SendStream.FlushAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_semaphore.Release();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<MqttBasePacket> ReceivePacketAsync(TimeSpan timeout, CancellationToken cancellationToken)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
MqttBasePacket packet = null;
|
||||
await ExecuteAndWrapExceptionAsync(async () =>
|
||||
{
|
||||
ReceivedMqttPacket receivedMqttPacket = null;
|
||||
try
|
||||
{
|
||||
if (timeout > TimeSpan.Zero)
|
||||
{
|
||||
receivedMqttPacket = await ReceiveAsync(_channel.ReceiveStream, cancellationToken).TimeoutAfter(timeout).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
receivedMqttPacket = await ReceiveAsync(_channel.ReceiveStream, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (receivedMqttPacket == null || cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
throw new TaskCanceledException();
|
||||
}
|
||||
|
||||
packet = PacketSerializer.Deserialize(receivedMqttPacket.Header, receivedMqttPacket.Body);
|
||||
if (packet == null)
|
||||
{
|
||||
throw new MqttProtocolViolationException("Received malformed packet.");
|
||||
}
|
||||
|
||||
_logger.Verbose<MqttChannelAdapter>("RX <<< {0}", packet);
|
||||
}
|
||||
finally
|
||||
{
|
||||
receivedMqttPacket?.Dispose();
|
||||
}
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
private static async Task<ReceivedMqttPacket> ReceiveAsync(Stream stream, CancellationToken cancellationToken)
|
||||
{
|
||||
var header = await MqttPacketReader.ReadHeaderAsync(stream, cancellationToken).ConfigureAwait(false);
|
||||
if (header == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (header.BodyLength == 0)
|
||||
{
|
||||
return new ReceivedMqttPacket(header, new MemoryStream(new byte[0], false));
|
||||
}
|
||||
|
||||
var body = header.BodyLength <= ReadBufferSize ? new MemoryStream(header.BodyLength) : new MemoryStream();
|
||||
|
||||
var buffer = new byte[ReadBufferSize];
|
||||
while (body.Length < header.BodyLength)
|
||||
{
|
||||
var bytesLeft = header.BodyLength - (int)body.Length;
|
||||
if (bytesLeft > buffer.Length)
|
||||
{
|
||||
bytesLeft = buffer.Length;
|
||||
}
|
||||
|
||||
var readBytesCount = await stream.ReadAsync(buffer, 0, bytesLeft, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// Check if the client closed the connection before sending the full body.
|
||||
if (readBytesCount == 0)
|
||||
{
|
||||
throw new MqttCommunicationException("Connection closed while reading remaining packet body.");
|
||||
}
|
||||
|
||||
// Here is no need to await because internally only an array is used and no real I/O operation is made.
|
||||
// Using async here will only generate overhead.
|
||||
body.Write(buffer, 0, readBytesCount);
|
||||
}
|
||||
|
||||
body.Seek(0L, SeekOrigin.Begin);
|
||||
|
||||
return new ReceivedMqttPacket(header, body);
|
||||
}
|
||||
|
||||
private static async Task ExecuteAndWrapExceptionAsync(Func<Task> action)
|
||||
{
|
||||
try
|
||||
{
|
||||
await action().ConfigureAwait(false);
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (MqttCommunicationTimedOutException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (MqttCommunicationException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (COMException comException)
|
||||
{
|
||||
if ((uint)comException.HResult == ErrorOperationAborted)
|
||||
{
|
||||
throw new OperationCanceledException();
|
||||
}
|
||||
|
||||
throw new MqttCommunicationException(comException);
|
||||
}
|
||||
catch (IOException exception)
|
||||
{
|
||||
if (exception.InnerException is SocketException socketException)
|
||||
{
|
||||
if (socketException.SocketErrorCode == SocketError.ConnectionAborted)
|
||||
{
|
||||
throw new OperationCanceledException();
|
||||
}
|
||||
}
|
||||
|
||||
throw new MqttCommunicationException(exception);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
throw new MqttCommunicationException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_isDisposed = true;
|
||||
_semaphore?.Dispose();
|
||||
_channel?.Dispose();
|
||||
}
|
||||
|
||||
private void ThrowIfDisposed()
|
||||
{
|
||||
if (_isDisposed)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(MqttChannelAdapter));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using MQTTnet.Packets;
|
||||
|
||||
namespace MQTTnet.Adapter
|
||||
{
|
||||
public sealed class ReceivedMqttPacket : IDisposable
|
||||
{
|
||||
public ReceivedMqttPacket(MqttPacketHeader header, MemoryStream body)
|
||||
{
|
||||
Header = header ?? throw new ArgumentNullException(nameof(header));
|
||||
Body = body ?? throw new ArgumentNullException(nameof(body));
|
||||
}
|
||||
|
||||
public MqttPacketHeader Header { get; }
|
||||
|
||||
public MemoryStream Body { get; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Body?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
namespace MQTTnet
|
||||
{
|
||||
public static class ApplicationMessagePublisherExtensions
|
||||
{
|
||||
public static Task PublishAsync(this IApplicationMessagePublisher publisher, params MqttApplicationMessage[] applicationMessages)
|
||||
{
|
||||
if (publisher == null) throw new ArgumentNullException(nameof(publisher));
|
||||
if (applicationMessages == null) throw new ArgumentNullException(nameof(applicationMessages));
|
||||
|
||||
return publisher.PublishAsync(applicationMessages);
|
||||
}
|
||||
|
||||
public static Task PublishAsync(this IApplicationMessagePublisher publisher, string topic)
|
||||
{
|
||||
if (publisher == null) throw new ArgumentNullException(nameof(publisher));
|
||||
if (topic == null) throw new ArgumentNullException(nameof(topic));
|
||||
|
||||
return publisher.PublishAsync(new MqttApplicationMessageBuilder().WithTopic(topic).Build());
|
||||
}
|
||||
|
||||
public static Task PublishAsync(this IApplicationMessagePublisher publisher, string topic, string payload)
|
||||
{
|
||||
if (publisher == null) throw new ArgumentNullException(nameof(publisher));
|
||||
if (topic == null) throw new ArgumentNullException(nameof(topic));
|
||||
|
||||
return publisher.PublishAsync(new MqttApplicationMessageBuilder().WithTopic(topic).WithPayload(payload).Build());
|
||||
}
|
||||
|
||||
public static Task PublishAsync(this IApplicationMessagePublisher publisher, string topic, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel)
|
||||
{
|
||||
if (publisher == null) throw new ArgumentNullException(nameof(publisher));
|
||||
if (topic == null) throw new ArgumentNullException(nameof(topic));
|
||||
|
||||
return publisher.PublishAsync(new MqttApplicationMessageBuilder().WithTopic(topic).WithPayload(payload).WithQualityOfServiceLevel(qualityOfServiceLevel).Build());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MQTTnet.Channel
|
||||
{
|
||||
public interface IMqttChannel : IDisposable
|
||||
{
|
||||
Stream SendStream { get; }
|
||||
Stream ReceiveStream { get; }
|
||||
|
||||
Task ConnectAsync();
|
||||
Task DisconnectAsync();
|
||||
}
|
||||
}
|
||||
@@ -1,516 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Exceptions;
|
||||
using MQTTnet.Internal;
|
||||
using MQTTnet.Packets;
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
namespace MQTTnet.Client
|
||||
{
|
||||
public class MqttClient : IMqttClient
|
||||
{
|
||||
private readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider();
|
||||
private readonly Stopwatch _sendTracker = new Stopwatch();
|
||||
private readonly SemaphoreSlim _disconnectLock = new SemaphoreSlim(1, 1);
|
||||
private readonly IMqttClientAdapterFactory _adapterFactory;
|
||||
private readonly MqttPacketDispatcher _packetDispatcher;
|
||||
private readonly IMqttNetLogger _logger;
|
||||
|
||||
private IMqttClientOptions _options;
|
||||
private bool _isReceivingPackets;
|
||||
private CancellationTokenSource _cancellationTokenSource;
|
||||
private Task _packetReceiverTask;
|
||||
private Task _keepAliveMessageSenderTask;
|
||||
private IMqttChannelAdapter _adapter;
|
||||
|
||||
public MqttClient(IMqttClientAdapterFactory channelFactory, IMqttNetLogger logger)
|
||||
{
|
||||
_adapterFactory = channelFactory ?? throw new ArgumentNullException(nameof(channelFactory));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
|
||||
_packetDispatcher = new MqttPacketDispatcher(logger);
|
||||
}
|
||||
|
||||
public event EventHandler<MqttClientConnectedEventArgs> Connected;
|
||||
public event EventHandler<MqttClientDisconnectedEventArgs> Disconnected;
|
||||
public event EventHandler<MqttApplicationMessageReceivedEventArgs> ApplicationMessageReceived;
|
||||
|
||||
public bool IsConnected { get; private set; }
|
||||
|
||||
public async Task<MqttClientConnectResult> ConnectAsync(IMqttClientOptions options)
|
||||
{
|
||||
if (options == null) throw new ArgumentNullException(nameof(options));
|
||||
if (options.ChannelOptions == null) throw new ArgumentException("ChannelOptions are not set.");
|
||||
|
||||
ThrowIfConnected("It is not allowed to connect with a server after the connection is established.");
|
||||
|
||||
try
|
||||
{
|
||||
_options = options;
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
_packetIdentifierProvider.Reset();
|
||||
_packetDispatcher.Reset();
|
||||
|
||||
_adapter = _adapterFactory.CreateClientAdapter(options, _logger);
|
||||
|
||||
_logger.Verbose<MqttClient>("Trying to connect with server.");
|
||||
await _adapter.ConnectAsync(_options.CommunicationTimeout).ConfigureAwait(false);
|
||||
_logger.Verbose<MqttClient>("Connection with server established.");
|
||||
|
||||
await StartReceivingPacketsAsync().ConfigureAwait(false);
|
||||
|
||||
var connectResponse = await AuthenticateAsync(options.WillMessage).ConfigureAwait(false);
|
||||
_logger.Verbose<MqttClient>("MQTT connection with server established.");
|
||||
|
||||
_sendTracker.Restart();
|
||||
|
||||
if (_options.KeepAlivePeriod != TimeSpan.Zero)
|
||||
{
|
||||
StartSendingKeepAliveMessages();
|
||||
}
|
||||
|
||||
IsConnected = true;
|
||||
Connected?.Invoke(this, new MqttClientConnectedEventArgs(connectResponse.IsSessionPresent));
|
||||
|
||||
_logger.Info<MqttClient>("Connected.");
|
||||
return new MqttClientConnectResult(connectResponse.IsSessionPresent);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.Error<MqttClient>(exception, "Error while connecting with server.");
|
||||
await DisconnectInternalAsync(null, exception).ConfigureAwait(false);
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DisconnectAsync()
|
||||
{
|
||||
if (!IsConnected)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (!_cancellationTokenSource.IsCancellationRequested)
|
||||
{
|
||||
await SendAsync(new MqttDisconnectPacket()).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
await DisconnectInternalAsync(null, null).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IList<MqttSubscribeResult>> SubscribeAsync(IEnumerable<TopicFilter> topicFilters)
|
||||
{
|
||||
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
|
||||
|
||||
ThrowIfNotConnected();
|
||||
|
||||
var subscribePacket = new MqttSubscribePacket
|
||||
{
|
||||
PacketIdentifier = _packetIdentifierProvider.GetNewPacketIdentifier(),
|
||||
TopicFilters = topicFilters.ToList()
|
||||
};
|
||||
|
||||
var response = await SendAndReceiveAsync<MqttSubAckPacket>(subscribePacket).ConfigureAwait(false);
|
||||
|
||||
if (response.SubscribeReturnCodes.Count != subscribePacket.TopicFilters.Count)
|
||||
{
|
||||
throw new MqttProtocolViolationException("The return codes are not matching the topic filters [MQTT-3.9.3-1].");
|
||||
}
|
||||
|
||||
return subscribePacket.TopicFilters.Select((t, i) => new MqttSubscribeResult(t, response.SubscribeReturnCodes[i])).ToList();
|
||||
}
|
||||
|
||||
public async Task UnsubscribeAsync(IEnumerable<string> topicFilters)
|
||||
{
|
||||
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
|
||||
|
||||
ThrowIfNotConnected();
|
||||
|
||||
var unsubscribePacket = new MqttUnsubscribePacket
|
||||
{
|
||||
PacketIdentifier = _packetIdentifierProvider.GetNewPacketIdentifier(),
|
||||
TopicFilters = topicFilters.ToList()
|
||||
};
|
||||
|
||||
await SendAndReceiveAsync<MqttUnsubAckPacket>(unsubscribePacket).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task PublishAsync(IEnumerable<MqttApplicationMessage> applicationMessages)
|
||||
{
|
||||
ThrowIfNotConnected();
|
||||
|
||||
var publishPackets = applicationMessages.Select(m => m.ToPublishPacket());
|
||||
var packetGroups = publishPackets.GroupBy(p => p.QualityOfServiceLevel).OrderBy(g => g.Key);
|
||||
|
||||
foreach (var qosGroup in packetGroups)
|
||||
{
|
||||
switch (qosGroup.Key)
|
||||
{
|
||||
case MqttQualityOfServiceLevel.AtMostOnce:
|
||||
{
|
||||
// No packet identifier is used for QoS 0 [3.3.2.2 Packet Identifier]
|
||||
await SendAsync(qosGroup.Cast<MqttBasePacket>().ToArray()).ConfigureAwait(false);
|
||||
break;
|
||||
}
|
||||
case MqttQualityOfServiceLevel.AtLeastOnce:
|
||||
{
|
||||
foreach (var publishPacket in qosGroup)
|
||||
{
|
||||
publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNewPacketIdentifier();
|
||||
await SendAndReceiveAsync<MqttPubAckPacket>(publishPacket).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case MqttQualityOfServiceLevel.ExactlyOnce:
|
||||
{
|
||||
foreach (var publishPacket in qosGroup)
|
||||
{
|
||||
publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNewPacketIdentifier();
|
||||
|
||||
var pubRecPacket = await SendAndReceiveAsync<MqttPubRecPacket>(publishPacket).ConfigureAwait(false);
|
||||
var pubRelPacket = new MqttPubRelPacket
|
||||
{
|
||||
PacketIdentifier = pubRecPacket.PacketIdentifier
|
||||
};
|
||||
|
||||
await SendAndReceiveAsync<MqttPubCompPacket>(pubRelPacket).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_cancellationTokenSource?.Dispose();
|
||||
_cancellationTokenSource = null;
|
||||
|
||||
_adapter?.Dispose();
|
||||
}
|
||||
|
||||
private async Task<MqttConnAckPacket> AuthenticateAsync(MqttApplicationMessage willApplicationMessage)
|
||||
{
|
||||
var connectPacket = new MqttConnectPacket
|
||||
{
|
||||
ClientId = _options.ClientId,
|
||||
Username = _options.Credentials?.Username,
|
||||
Password = _options.Credentials?.Password,
|
||||
CleanSession = _options.CleanSession,
|
||||
KeepAlivePeriod = (ushort)_options.KeepAlivePeriod.TotalSeconds,
|
||||
WillMessage = willApplicationMessage
|
||||
};
|
||||
|
||||
var response = await SendAndReceiveAsync<MqttConnAckPacket>(connectPacket).ConfigureAwait(false);
|
||||
if (response.ConnectReturnCode != MqttConnectReturnCode.ConnectionAccepted)
|
||||
{
|
||||
throw new MqttConnectingFailedException(response.ConnectReturnCode);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
private void ThrowIfNotConnected()
|
||||
{
|
||||
if (!IsConnected) throw new MqttCommunicationException("The client is not connected.");
|
||||
}
|
||||
|
||||
private void ThrowIfConnected(string message)
|
||||
{
|
||||
if (IsConnected) throw new MqttProtocolViolationException(message);
|
||||
}
|
||||
|
||||
private async Task DisconnectInternalAsync(Task sender, Exception exception)
|
||||
{
|
||||
await _disconnectLock.WaitAsync();
|
||||
try
|
||||
{
|
||||
if (_cancellationTokenSource == null || _cancellationTokenSource.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_cancellationTokenSource.Cancel(false);
|
||||
}
|
||||
catch (Exception adapterException)
|
||||
{
|
||||
_logger.Warning<MqttClient>(adapterException, "Error while disconnecting from adapter.");
|
||||
}
|
||||
finally
|
||||
{
|
||||
_disconnectLock.Release();
|
||||
}
|
||||
|
||||
var clientWasConnected = IsConnected;
|
||||
IsConnected = false;
|
||||
|
||||
try
|
||||
{
|
||||
if (_packetReceiverTask != null && _packetReceiverTask != sender)
|
||||
{
|
||||
_packetReceiverTask.Wait();
|
||||
}
|
||||
|
||||
if (_keepAliveMessageSenderTask != null && _keepAliveMessageSenderTask != sender)
|
||||
{
|
||||
_keepAliveMessageSenderTask.Wait();
|
||||
}
|
||||
|
||||
if (_adapter != null)
|
||||
{
|
||||
await _adapter.DisconnectAsync(_options.CommunicationTimeout).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
_logger.Verbose<MqttClient>("Disconnected from adapter.");
|
||||
}
|
||||
catch (Exception adapterException)
|
||||
{
|
||||
_logger.Warning<MqttClient>(adapterException, "Error while disconnecting from adapter.");
|
||||
}
|
||||
finally
|
||||
{
|
||||
_adapter?.Dispose();
|
||||
_adapter = null;
|
||||
_cancellationTokenSource?.Dispose();
|
||||
_cancellationTokenSource = null;
|
||||
|
||||
_logger.Info<MqttClient>("Disconnected.");
|
||||
Disconnected?.Invoke(this, new MqttClientDisconnectedEventArgs(clientWasConnected, exception));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ProcessReceivedPacketAsync(MqttBasePacket packet)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (packet is MqttPublishPacket publishPacket)
|
||||
{
|
||||
await ProcessReceivedPublishPacketAsync(publishPacket).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (packet is MqttPingReqPacket)
|
||||
{
|
||||
await SendAsync(new MqttPingRespPacket()).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (packet is MqttDisconnectPacket)
|
||||
{
|
||||
await DisconnectAsync().ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (packet is MqttPubRelPacket pubRelPacket)
|
||||
{
|
||||
await ProcessReceivedPubRelPacket(pubRelPacket).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
_packetDispatcher.Dispatch(packet);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.Error<MqttClient>(exception, "Unhandled exception while processing received packet.");
|
||||
}
|
||||
}
|
||||
|
||||
private void FireApplicationMessageReceivedEvent(MqttPublishPacket publishPacket)
|
||||
{
|
||||
try
|
||||
{
|
||||
var applicationMessage = publishPacket.ToApplicationMessage();
|
||||
ApplicationMessageReceived?.Invoke(this, new MqttApplicationMessageReceivedEventArgs(_options.ClientId, applicationMessage));
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.Error<MqttClient>(exception, "Unhandled exception while handling application message.");
|
||||
}
|
||||
}
|
||||
|
||||
private Task ProcessReceivedPublishPacketAsync(MqttPublishPacket publishPacket)
|
||||
{
|
||||
if (_cancellationTokenSource.IsCancellationRequested)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce)
|
||||
{
|
||||
FireApplicationMessageReceivedEvent(publishPacket);
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtLeastOnce)
|
||||
{
|
||||
FireApplicationMessageReceivedEvent(publishPacket);
|
||||
return SendAsync(new MqttPubAckPacket { PacketIdentifier = publishPacket.PacketIdentifier });
|
||||
}
|
||||
|
||||
if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.ExactlyOnce)
|
||||
{
|
||||
// QoS 2 is implement as method "B" [4.3.3 QoS 2: Exactly once delivery]
|
||||
FireApplicationMessageReceivedEvent(publishPacket);
|
||||
return SendAsync(new MqttPubRecPacket { PacketIdentifier = publishPacket.PacketIdentifier });
|
||||
}
|
||||
|
||||
throw new MqttCommunicationException("Received a not supported QoS level.");
|
||||
}
|
||||
|
||||
private Task ProcessReceivedPubRelPacket(MqttPubRelPacket pubRelPacket)
|
||||
{
|
||||
var response = new MqttPubCompPacket
|
||||
{
|
||||
PacketIdentifier = pubRelPacket.PacketIdentifier
|
||||
};
|
||||
|
||||
return SendAsync(response);
|
||||
}
|
||||
|
||||
private Task SendAsync(params MqttBasePacket[] packets)
|
||||
{
|
||||
_sendTracker.Restart();
|
||||
return _adapter.SendPacketsAsync(_options.CommunicationTimeout, _cancellationTokenSource.Token, packets);
|
||||
}
|
||||
|
||||
private async Task<TResponsePacket> SendAndReceiveAsync<TResponsePacket>(MqttBasePacket requestPacket) where TResponsePacket : MqttBasePacket
|
||||
{
|
||||
ushort? identifier = null;
|
||||
if (requestPacket is IMqttPacketWithIdentifier requestPacketWithIdentifier)
|
||||
{
|
||||
identifier = requestPacketWithIdentifier.PacketIdentifier;
|
||||
}
|
||||
|
||||
var packetAwaiter = _packetDispatcher.WaitForPacketAsync(typeof(TResponsePacket), identifier, _options.CommunicationTimeout);
|
||||
await SendAsync(requestPacket).ConfigureAwait(false);
|
||||
|
||||
return (TResponsePacket)await packetAwaiter.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task SendKeepAliveMessagesAsync()
|
||||
{
|
||||
_logger.Verbose<MqttClient>("Start sending keep alive packets.");
|
||||
|
||||
try
|
||||
{
|
||||
while (!_cancellationTokenSource.Token.IsCancellationRequested)
|
||||
{
|
||||
var keepAliveSendInterval = TimeSpan.FromSeconds(_options.KeepAlivePeriod.TotalSeconds * 0.75);
|
||||
if (_options.KeepAliveSendInterval.HasValue)
|
||||
{
|
||||
keepAliveSendInterval = _options.KeepAliveSendInterval.Value;
|
||||
}
|
||||
|
||||
if (_sendTracker.Elapsed > keepAliveSendInterval)
|
||||
{
|
||||
await SendAndReceiveAsync<MqttPingRespPacket>(new MqttPingReqPacket()).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await Task.Delay(keepAliveSendInterval, _cancellationTokenSource.Token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
if (exception is OperationCanceledException)
|
||||
{
|
||||
}
|
||||
else if (exception is MqttCommunicationException)
|
||||
{
|
||||
_logger.Warning<MqttClient>(exception, "MQTT communication exception while sending/receiving keep alive packets.");
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Error<MqttClient>(exception, "Unhandled exception while sending/receiving keep alive packets.");
|
||||
}
|
||||
|
||||
await DisconnectInternalAsync(_keepAliveMessageSenderTask, exception).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_logger.Verbose<MqttClient>("Stopped sending keep alive packets.");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ReceivePacketsAsync()
|
||||
{
|
||||
_logger.Verbose<MqttClient>("Start receiving packets.");
|
||||
|
||||
try
|
||||
{
|
||||
while (!_cancellationTokenSource.Token.IsCancellationRequested)
|
||||
{
|
||||
_isReceivingPackets = true;
|
||||
|
||||
var packet = await _adapter.ReceivePacketAsync(TimeSpan.Zero, _cancellationTokenSource.Token).ConfigureAwait(false);
|
||||
|
||||
if (_cancellationTokenSource.Token.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
StartProcessReceivedPacket(packet);
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
if (exception is OperationCanceledException)
|
||||
{
|
||||
}
|
||||
else if (exception is MqttCommunicationException)
|
||||
{
|
||||
_logger.Warning<MqttClient>(exception, "MQTT communication exception while receiving packets.");
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Error<MqttClient>(exception, "Unhandled exception while receiving packets.");
|
||||
}
|
||||
|
||||
await DisconnectInternalAsync(_packetReceiverTask, exception).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_logger.Verbose<MqttClient>("Stopped receiving packets.");
|
||||
}
|
||||
}
|
||||
|
||||
private void StartProcessReceivedPacket(MqttBasePacket packet)
|
||||
{
|
||||
Task.Run(() => ProcessReceivedPacketAsync(packet), _cancellationTokenSource.Token);
|
||||
}
|
||||
|
||||
private async Task StartReceivingPacketsAsync()
|
||||
{
|
||||
_isReceivingPackets = false;
|
||||
|
||||
_packetReceiverTask = Task.Run(ReceivePacketsAsync, _cancellationTokenSource.Token);
|
||||
|
||||
while (!_isReceivingPackets && !_cancellationTokenSource.Token.IsCancellationRequested)
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromMilliseconds(100), _cancellationTokenSource.Token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void StartSendingKeepAliveMessages()
|
||||
{
|
||||
_keepAliveMessageSenderTask = Task.Run(SendKeepAliveMessagesAsync, _cancellationTokenSource.Token);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace MQTTnet.Client
|
||||
{
|
||||
public class MqttClientTlsOptions
|
||||
{
|
||||
public bool UseTls { get; set; }
|
||||
|
||||
public bool IgnoreCertificateRevocationErrors { get; set; }
|
||||
|
||||
public bool IgnoreCertificateChainErrors { get; set; }
|
||||
|
||||
public bool AllowUntrustedCertificates { get; set; }
|
||||
|
||||
public List<byte[]> Certificates { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Exceptions;
|
||||
using MQTTnet.Internal;
|
||||
using MQTTnet.Packets;
|
||||
|
||||
namespace MQTTnet.Client
|
||||
{
|
||||
public class MqttPacketDispatcher
|
||||
{
|
||||
private readonly ConcurrentDictionary<Type, ConcurrentDictionary<ushort, TaskCompletionSource<MqttBasePacket>>> _awaiters = new ConcurrentDictionary<Type, ConcurrentDictionary<ushort, TaskCompletionSource<MqttBasePacket>>>();
|
||||
private readonly IMqttNetLogger _logger;
|
||||
|
||||
public MqttPacketDispatcher(IMqttNetLogger logger)
|
||||
{
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
public async Task<MqttBasePacket> WaitForPacketAsync(Type responseType, ushort? identifier, TimeSpan timeout)
|
||||
{
|
||||
var packetAwaiter = AddPacketAwaiter(responseType, identifier);
|
||||
try
|
||||
{
|
||||
return await packetAwaiter.Task.TimeoutAfter(timeout).ConfigureAwait(false);
|
||||
}
|
||||
catch (MqttCommunicationTimedOutException)
|
||||
{
|
||||
_logger.Warning<MqttPacketDispatcher>("Timeout while waiting for packet of type '{0}'.", responseType.Name);
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
RemovePacketAwaiter(responseType, identifier);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispatch(MqttBasePacket packet)
|
||||
{
|
||||
if (packet == null) throw new ArgumentNullException(nameof(packet));
|
||||
|
||||
var type = packet.GetType();
|
||||
|
||||
if (_awaiters.TryGetValue(type, out var byId))
|
||||
{
|
||||
ushort? identifier = 0;
|
||||
if (packet is IMqttPacketWithIdentifier packetWithIdentifier)
|
||||
{
|
||||
identifier = packetWithIdentifier.PacketIdentifier;
|
||||
}
|
||||
|
||||
if (byId.TryRemove(identifier.Value, out var tcs))
|
||||
{
|
||||
tcs.TrySetResult(packet);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"Packet of type '{type.Name}' not handled or dispatched.");
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_awaiters.Clear();
|
||||
}
|
||||
|
||||
private TaskCompletionSource<MqttBasePacket> AddPacketAwaiter(Type responseType, ushort? identifier)
|
||||
{
|
||||
var tcs = new TaskCompletionSource<MqttBasePacket>();
|
||||
|
||||
if (!identifier.HasValue)
|
||||
{
|
||||
identifier = 0;
|
||||
}
|
||||
|
||||
var byId = _awaiters.GetOrAdd(responseType, key => new ConcurrentDictionary<ushort, TaskCompletionSource<MqttBasePacket>>());
|
||||
if (!byId.TryAdd(identifier.Value, tcs))
|
||||
{
|
||||
throw new InvalidOperationException($"The packet dispatcher already has an awaiter for packet of type '{responseType}' with identifier {identifier}.");
|
||||
}
|
||||
|
||||
return tcs;
|
||||
}
|
||||
|
||||
private void RemovePacketAwaiter(Type responseType, ushort? identifier)
|
||||
{
|
||||
if (!identifier.HasValue)
|
||||
{
|
||||
identifier = 0;
|
||||
}
|
||||
|
||||
var byId = _awaiters.GetOrAdd(responseType, key => new ConcurrentDictionary<ushort, TaskCompletionSource<MqttBasePacket>>());
|
||||
byId.TryRemove(identifier.Value, out var _);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace MQTTnet.Diagnostics
|
||||
{
|
||||
public interface IMqttNetLogger
|
||||
{
|
||||
event EventHandler<MqttNetLogMessagePublishedEventArgs> LogMessagePublished;
|
||||
|
||||
void Verbose<TSource>(string message, params object[] parameters);
|
||||
|
||||
void Info<TSource>(string message, params object[] parameters);
|
||||
|
||||
void Warning<TSource>(Exception exception, string message, params object[] parameters);
|
||||
|
||||
void Warning<TSource>(string message, params object[] parameters);
|
||||
|
||||
void Error<TSource>(Exception exception, string message, params object[] parameters);
|
||||
|
||||
void Error<TSource>(string message, params object[] parameters);
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace MQTTnet.Diagnostics
|
||||
{
|
||||
public class MqttNetLogger : IMqttNetLogger
|
||||
{
|
||||
private readonly string _logId;
|
||||
|
||||
public MqttNetLogger(string logId = null)
|
||||
{
|
||||
_logId = logId;
|
||||
}
|
||||
|
||||
public event EventHandler<MqttNetLogMessagePublishedEventArgs> LogMessagePublished;
|
||||
|
||||
public void Verbose<TSource>(string message, params object[] parameters)
|
||||
{
|
||||
Publish<TSource>(MqttNetLogLevel.Verbose, null, message, parameters);
|
||||
}
|
||||
|
||||
public void Info<TSource>(string message, params object[] parameters)
|
||||
{
|
||||
Publish<TSource>(MqttNetLogLevel.Info, null, message, parameters);
|
||||
}
|
||||
|
||||
public void Warning<TSource>(Exception exception, string message, params object[] parameters)
|
||||
{
|
||||
Publish<TSource>(MqttNetLogLevel.Warning, exception, message, parameters);
|
||||
}
|
||||
|
||||
public void Warning<TSource>(string message, params object[] parameters)
|
||||
{
|
||||
Warning<TSource>(null, message, parameters);
|
||||
}
|
||||
|
||||
public void Error<TSource>(Exception exception, string message, params object[] parameters)
|
||||
{
|
||||
Publish<TSource>(MqttNetLogLevel.Error, exception, message, parameters);
|
||||
}
|
||||
|
||||
public void Error<TSource>(string message, params object[] parameters)
|
||||
{
|
||||
Warning<TSource>(null, message, parameters);
|
||||
}
|
||||
|
||||
private void Publish<TSource>(MqttNetLogLevel logLevel, Exception exception, string message, object[] parameters)
|
||||
{
|
||||
var hasLocalListeners = LogMessagePublished != null;
|
||||
var hasGlobalListeners = MqttNetGlobalLogger.HasListeners;
|
||||
|
||||
if (!hasLocalListeners && !hasGlobalListeners)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (parameters.Length > 0)
|
||||
{
|
||||
message = string.Format(message, parameters);
|
||||
}
|
||||
|
||||
var traceMessage = new MqttNetLogMessage(_logId, DateTime.Now, Environment.CurrentManagedThreadId, typeof(TSource).Name, logLevel, message, exception);
|
||||
|
||||
if (hasGlobalListeners)
|
||||
{
|
||||
MqttNetGlobalLogger.Publish(traceMessage);
|
||||
}
|
||||
|
||||
if (hasLocalListeners)
|
||||
{
|
||||
LogMessagePublished?.Invoke(this, new MqttNetLogMessagePublishedEventArgs(traceMessage));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
namespace MQTTnet.Exceptions
|
||||
{
|
||||
public sealed class MqttCommunicationTimedOutException : MqttCommunicationException
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MQTTnet
|
||||
{
|
||||
public interface IApplicationMessagePublisher
|
||||
{
|
||||
Task PublishAsync(IEnumerable<MqttApplicationMessage> applicationMessages);
|
||||
}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
#if WINDOWS_UWP
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Networking.Sockets;
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Serializer;
|
||||
using MQTTnet.Server;
|
||||
|
||||
namespace MQTTnet.Implementations
|
||||
{
|
||||
public class MqttTcpServerAdapter : IMqttServerAdapter, IDisposable
|
||||
{
|
||||
private readonly IMqttNetLogger _logger;
|
||||
private StreamSocketListener _defaultEndpointSocket;
|
||||
|
||||
public MqttTcpServerAdapter(IMqttNetLogger logger)
|
||||
{
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
public event EventHandler<MqttServerAdapterClientAcceptedEventArgs> ClientAccepted;
|
||||
|
||||
public async Task StartAsync(IMqttServerOptions options)
|
||||
{
|
||||
if (options == null) throw new ArgumentNullException(nameof(options));
|
||||
|
||||
if (_defaultEndpointSocket != null) throw new InvalidOperationException("Server is already started.");
|
||||
|
||||
if (options.DefaultEndpointOptions.IsEnabled)
|
||||
{
|
||||
_defaultEndpointSocket = new StreamSocketListener();
|
||||
await _defaultEndpointSocket.BindServiceNameAsync(options.GetDefaultEndpointPort().ToString(), SocketProtectionLevel.PlainSocket);
|
||||
_defaultEndpointSocket.ConnectionReceived += AcceptDefaultEndpointConnectionsAsync;
|
||||
}
|
||||
|
||||
if (options.TlsEndpointOptions.IsEnabled)
|
||||
{
|
||||
throw new NotSupportedException("TLS servers are not supported for UWP apps.");
|
||||
}
|
||||
}
|
||||
|
||||
public Task StopAsync()
|
||||
{
|
||||
if (_defaultEndpointSocket != null)
|
||||
{
|
||||
_defaultEndpointSocket.ConnectionReceived -= AcceptDefaultEndpointConnectionsAsync;
|
||||
}
|
||||
|
||||
_defaultEndpointSocket?.Dispose();
|
||||
_defaultEndpointSocket = null;
|
||||
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
StopAsync();
|
||||
}
|
||||
|
||||
private void AcceptDefaultEndpointConnectionsAsync(StreamSocketListener sender, StreamSocketListenerConnectionReceivedEventArgs args)
|
||||
{
|
||||
try
|
||||
{
|
||||
var clientAdapter = new MqttChannelAdapter(new MqttTcpChannel(args.Socket), new MqttPacketSerializer(), _logger);
|
||||
ClientAccepted?.Invoke(this, new MqttServerAdapterClientAcceptedEventArgs(clientAdapter));
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.Error<MqttTcpServerAdapter>(exception, "Error while accepting connection at default endpoint.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -1,162 +0,0 @@
|
||||
#if NET452 || NET461 || NETSTANDARD1_3 || NETSTANDARD2_0
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Security;
|
||||
using System.Net.Sockets;
|
||||
using System.Security.Authentication;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Serializer;
|
||||
using MQTTnet.Server;
|
||||
|
||||
namespace MQTTnet.Implementations
|
||||
{
|
||||
public class MqttTcpServerAdapter : IMqttServerAdapter, IDisposable
|
||||
{
|
||||
private readonly IMqttNetLogger _logger;
|
||||
|
||||
private CancellationTokenSource _cancellationTokenSource;
|
||||
private Socket _defaultEndpointSocket;
|
||||
private Socket _tlsEndpointSocket;
|
||||
private X509Certificate2 _tlsCertificate;
|
||||
|
||||
public MqttTcpServerAdapter(IMqttNetLogger logger)
|
||||
{
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
public event EventHandler<MqttServerAdapterClientAcceptedEventArgs> ClientAccepted;
|
||||
|
||||
public Task StartAsync(IMqttServerOptions options)
|
||||
{
|
||||
if (_cancellationTokenSource != null) throw new InvalidOperationException("Server is already started.");
|
||||
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
if (options.DefaultEndpointOptions.IsEnabled)
|
||||
{
|
||||
_defaultEndpointSocket = new Socket(SocketType.Stream, ProtocolType.Tcp);
|
||||
_defaultEndpointSocket.Bind(new IPEndPoint(options.DefaultEndpointOptions.BoundIPAddress, options.GetDefaultEndpointPort()));
|
||||
_defaultEndpointSocket.Listen(options.ConnectionBacklog);
|
||||
|
||||
Task.Run(async () => await AcceptDefaultEndpointConnectionsAsync(_cancellationTokenSource.Token).ConfigureAwait(false), _cancellationTokenSource.Token).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (options.TlsEndpointOptions.IsEnabled)
|
||||
{
|
||||
if (options.TlsEndpointOptions.Certificate == null)
|
||||
{
|
||||
throw new ArgumentException("TLS certificate is not set.");
|
||||
}
|
||||
|
||||
_tlsCertificate = new X509Certificate2(options.TlsEndpointOptions.Certificate);
|
||||
if (!_tlsCertificate.HasPrivateKey)
|
||||
{
|
||||
throw new InvalidOperationException("The certificate for TLS encryption must contain the private key.");
|
||||
}
|
||||
|
||||
_tlsEndpointSocket = new Socket(SocketType.Stream, ProtocolType.Tcp);
|
||||
_tlsEndpointSocket.Bind(new IPEndPoint(options.TlsEndpointOptions.BoundIPAddress, options.GetTlsEndpointPort()));
|
||||
_tlsEndpointSocket.Listen(options.ConnectionBacklog);
|
||||
|
||||
Task.Run(async () => await AcceptTlsEndpointConnectionsAsync(_cancellationTokenSource.Token).ConfigureAwait(false), _cancellationTokenSource.Token).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task StopAsync()
|
||||
{
|
||||
_cancellationTokenSource?.Cancel(false);
|
||||
_cancellationTokenSource?.Dispose();
|
||||
_cancellationTokenSource = null;
|
||||
|
||||
_defaultEndpointSocket?.Dispose();
|
||||
_defaultEndpointSocket = null;
|
||||
|
||||
_tlsCertificate = null;
|
||||
|
||||
_tlsEndpointSocket?.Dispose();
|
||||
_tlsEndpointSocket = null;
|
||||
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
StopAsync();
|
||||
}
|
||||
|
||||
private async Task AcceptDefaultEndpointConnectionsAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
//todo: else branch can be used with min dependency NET46
|
||||
#if NET452 || NET461
|
||||
var clientSocket = await Task.Factory.FromAsync(_defaultEndpointSocket.BeginAccept, _defaultEndpointSocket.EndAccept, null).ConfigureAwait(false);
|
||||
#else
|
||||
var clientSocket = await _defaultEndpointSocket.AcceptAsync().ConfigureAwait(false);
|
||||
#endif
|
||||
|
||||
var clientAdapter = new MqttChannelAdapter(new MqttTcpChannel(clientSocket, null), new MqttPacketSerializer(), _logger);
|
||||
ClientAccepted?.Invoke(this, new MqttServerAdapterClientAcceptedEventArgs(clientAdapter));
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
// It can happen that the listener socket is accessed after the cancellation token is already set and the listener socket is disposed.
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
if (exception is SocketException s && s.SocketErrorCode == SocketError.OperationAborted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.Error<MqttTcpServerAdapter>(exception, "Error while accepting connection at default endpoint.");
|
||||
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task AcceptTlsEndpointConnectionsAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
#if NET452 || NET461
|
||||
var clientSocket = await Task.Factory.FromAsync(_tlsEndpointSocket.BeginAccept, _tlsEndpointSocket.EndAccept, null).ConfigureAwait(false);
|
||||
#else
|
||||
var clientSocket = await _tlsEndpointSocket.AcceptAsync().ConfigureAwait(false);
|
||||
#endif
|
||||
|
||||
var sslStream = new SslStream(new NetworkStream(clientSocket));
|
||||
await sslStream.AuthenticateAsServerAsync(_tlsCertificate, false, SslProtocols.Tls12, false).ConfigureAwait(false);
|
||||
|
||||
var clientAdapter = new MqttChannelAdapter(new MqttTcpChannel(clientSocket, sslStream), new MqttPacketSerializer(), _logger);
|
||||
ClientAccepted?.Invoke(this, new MqttServerAdapterClientAcceptedEventArgs(clientAdapter));
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
// It can happen that the listener socket is accessed after the cancellation token is already set and the listener socket is disposed.
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
if (exception is SocketException s && s.SocketErrorCode == SocketError.OperationAborted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.Error<MqttTcpServerAdapter>(exception, "Error while accepting connection at TLS endpoint.");
|
||||
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -1,112 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net.WebSockets;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Channel;
|
||||
using MQTTnet.Client;
|
||||
|
||||
namespace MQTTnet.Implementations
|
||||
{
|
||||
public sealed class MqttWebSocketChannel : IMqttChannel
|
||||
{
|
||||
// ReSharper disable once MemberCanBePrivate.Global
|
||||
// ReSharper disable once AutoPropertyCanBeMadeGetOnly.Global
|
||||
public static int BufferSize { get; set; } = 4096 * 20; // Can be changed for fine tuning by library user.
|
||||
|
||||
private readonly MqttClientWebSocketOptions _options;
|
||||
private ClientWebSocket _webSocket;
|
||||
|
||||
public MqttWebSocketChannel(MqttClientWebSocketOptions options)
|
||||
{
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
public Stream SendStream { get; private set; }
|
||||
public Stream ReceiveStream { get; private set; }
|
||||
|
||||
public async Task ConnectAsync()
|
||||
{
|
||||
var uri = _options.Uri;
|
||||
if (!uri.StartsWith("ws://", StringComparison.OrdinalIgnoreCase) && !uri.StartsWith("wss://", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (_options.TlsOptions?.UseTls == false)
|
||||
{
|
||||
uri = "ws://" + uri;
|
||||
}
|
||||
else
|
||||
{
|
||||
uri = "wss://" + uri;
|
||||
}
|
||||
}
|
||||
|
||||
_webSocket = new ClientWebSocket();
|
||||
|
||||
if (_options.RequestHeaders != null)
|
||||
{
|
||||
foreach (var requestHeader in _options.RequestHeaders)
|
||||
{
|
||||
_webSocket.Options.SetRequestHeader(requestHeader.Key, requestHeader.Value);
|
||||
}
|
||||
}
|
||||
|
||||
if (_options.SubProtocols != null)
|
||||
{
|
||||
foreach (var subProtocol in _options.SubProtocols)
|
||||
{
|
||||
_webSocket.Options.AddSubProtocol(subProtocol);
|
||||
}
|
||||
}
|
||||
|
||||
if (_options.CookieContainer != null)
|
||||
{
|
||||
_webSocket.Options.Cookies = _options.CookieContainer;
|
||||
}
|
||||
|
||||
if (_options.TlsOptions?.UseTls == true && _options.TlsOptions?.Certificates != null)
|
||||
{
|
||||
_webSocket.Options.ClientCertificates = new X509CertificateCollection();
|
||||
foreach (var certificate in _options.TlsOptions.Certificates)
|
||||
{
|
||||
_webSocket.Options.ClientCertificates.Add(new X509Certificate(certificate));
|
||||
}
|
||||
}
|
||||
|
||||
await _webSocket.ConnectAsync(new Uri(uri), CancellationToken.None).ConfigureAwait(false);
|
||||
|
||||
SendStream = new WebSocketStream(_webSocket);
|
||||
ReceiveStream = SendStream;
|
||||
}
|
||||
|
||||
public async Task DisconnectAsync()
|
||||
{
|
||||
if (_webSocket == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_webSocket.State == WebSocketState.Open || _webSocket.State == WebSocketState.Connecting)
|
||||
{
|
||||
await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
Dispose();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
try
|
||||
{
|
||||
_webSocket?.Dispose();
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
}
|
||||
finally
|
||||
{
|
||||
_webSocket = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.WebSockets;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Exceptions;
|
||||
|
||||
namespace MQTTnet.Implementations
|
||||
{
|
||||
public class WebSocketStream : Stream
|
||||
{
|
||||
private readonly byte[] _chunckBuffer = new byte[MqttWebSocketChannel.BufferSize];
|
||||
private readonly Queue<byte> _buffer = new Queue<byte>(MqttWebSocketChannel.BufferSize);
|
||||
private readonly WebSocket _webSocket;
|
||||
|
||||
public WebSocketStream(WebSocket webSocket)
|
||||
{
|
||||
_webSocket = webSocket ?? throw new ArgumentNullException(nameof(webSocket));
|
||||
}
|
||||
|
||||
public override bool CanRead => true;
|
||||
|
||||
public override bool CanSeek => false;
|
||||
|
||||
public override bool CanWrite => true;
|
||||
|
||||
public override long Length => throw new NotSupportedException();
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get => throw new NotSupportedException();
|
||||
set => throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
}
|
||||
|
||||
public override Task FlushAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
return ReadAsync(buffer, offset, count).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
var bytesRead = 0;
|
||||
|
||||
// Use existing date from buffer.
|
||||
while (count > 0 && _buffer.Any())
|
||||
{
|
||||
buffer[offset] = _buffer.Dequeue();
|
||||
count--;
|
||||
bytesRead++;
|
||||
offset++;
|
||||
}
|
||||
|
||||
if (count == 0)
|
||||
{
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
// Fetch new data if the buffer is not full.
|
||||
while (_webSocket.State == WebSocketState.Open)
|
||||
{
|
||||
await FetchChunkAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
while (count > 0 && _buffer.Any())
|
||||
{
|
||||
buffer[offset] = _buffer.Dequeue();
|
||||
count--;
|
||||
bytesRead++;
|
||||
offset++;
|
||||
}
|
||||
|
||||
if (count == 0)
|
||||
{
|
||||
return bytesRead;
|
||||
}
|
||||
}
|
||||
|
||||
if (_webSocket.State == WebSocketState.Closed)
|
||||
{
|
||||
throw new MqttCommunicationException("WebSocket connection closed.");
|
||||
}
|
||||
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
WriteAsync(buffer, offset, count).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
return _webSocket.SendAsync(new ArraySegment<byte>(buffer, offset, count), WebSocketMessageType.Binary, true, cancellationToken);
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
private async Task FetchChunkAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var response = await _webSocket.ReceiveAsync(new ArraySegment<byte>(_chunckBuffer, 0, _chunckBuffer.Length), cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (response.MessageType == WebSocketMessageType.Close)
|
||||
{
|
||||
await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else if (response.MessageType == WebSocketMessageType.Binary)
|
||||
{
|
||||
for (var i = 0; i < response.Count; i++)
|
||||
{
|
||||
_buffer.Enqueue(_chunckBuffer[i]);
|
||||
}
|
||||
}
|
||||
else if (response.MessageType == WebSocketMessageType.Text)
|
||||
{
|
||||
throw new MqttProtocolViolationException("WebSocket channel received TEXT message.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Exceptions;
|
||||
|
||||
namespace MQTTnet.Internal
|
||||
{
|
||||
public static class TaskExtensions
|
||||
{
|
||||
public static async Task TimeoutAfter(this Task task, TimeSpan timeout)
|
||||
{
|
||||
if (task == null) throw new ArgumentNullException(nameof(task));
|
||||
|
||||
using (var timeoutCts = new CancellationTokenSource())
|
||||
{
|
||||
try
|
||||
{
|
||||
var timeoutTask = Task.Delay(timeout, timeoutCts.Token);
|
||||
var finishedTask = await Task.WhenAny(timeoutTask, task).ConfigureAwait(false);
|
||||
|
||||
if (finishedTask == timeoutTask)
|
||||
{
|
||||
throw new MqttCommunicationTimedOutException();
|
||||
}
|
||||
|
||||
if (task.IsCanceled)
|
||||
{
|
||||
throw new TaskCanceledException();
|
||||
}
|
||||
|
||||
if (task.IsFaulted)
|
||||
{
|
||||
throw new MqttCommunicationException(task.Exception?.GetBaseException());
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
timeoutCts.Cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<TResult> TimeoutAfter<TResult>(this Task<TResult> task, TimeSpan timeout)
|
||||
{
|
||||
if (task == null) throw new ArgumentNullException(nameof(task));
|
||||
|
||||
using (var timeoutCts = new CancellationTokenSource())
|
||||
{
|
||||
try
|
||||
{
|
||||
var timeoutTask = Task.Delay(timeout, timeoutCts.Token);
|
||||
var finishedTask = await Task.WhenAny(timeoutTask, task).ConfigureAwait(false);
|
||||
|
||||
if (finishedTask == timeoutTask)
|
||||
{
|
||||
throw new MqttCommunicationTimedOutException();
|
||||
}
|
||||
|
||||
if (task.IsCanceled)
|
||||
{
|
||||
throw new TaskCanceledException();
|
||||
}
|
||||
|
||||
if (task.IsFaulted)
|
||||
{
|
||||
throw new MqttCommunicationException(task.Exception.GetBaseException());
|
||||
}
|
||||
|
||||
return task.Result;
|
||||
}
|
||||
finally
|
||||
{
|
||||
timeoutCts.Cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">netstandard1.3;netstandard2.0;net452;net461;uap10.0</TargetFrameworks>
|
||||
<TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">netstandard1.3;netstandard2.0</TargetFrameworks>
|
||||
<AssemblyName>MQTTnet</AssemblyName>
|
||||
<RootNamespace>MQTTnet</RootNamespace>
|
||||
<GeneratePackageOnBuild>False</GeneratePackageOnBuild>
|
||||
<DebugType>Full</DebugType>
|
||||
<AssemblyVersion>0.0.0.0</AssemblyVersion>
|
||||
<FileVersion>0.0.0.0</FileVersion>
|
||||
<Version>0.0.0.0</Version>
|
||||
<Company />
|
||||
<Product />
|
||||
<Description />
|
||||
<Authors />
|
||||
<PackageId />
|
||||
<SignAssembly>false</SignAssembly>
|
||||
<DelaySign>false</DelaySign>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0'">
|
||||
<CopyLocalLockFileAssemblies>false</CopyLocalLockFileAssemblies>
|
||||
<NugetTargetMoniker>UAP,Version=v10.0</NugetTargetMoniker>
|
||||
<TargetPlatformIdentifier>UAP</TargetPlatformIdentifier>
|
||||
<TargetPlatformVersion>10.0.16299.0</TargetPlatformVersion>
|
||||
<TargetPlatformMinVersion>10.0.10240.0</TargetPlatformMinVersion>
|
||||
<TargetFrameworkIdentifier>.NETCore</TargetFrameworkIdentifier>
|
||||
<TargetFrameworkVersion>v5.0</TargetFrameworkVersion>
|
||||
<DefineConstants>$(DefineConstants);WINDOWS_UWP</DefineConstants>
|
||||
<DefaultLanguage>en</DefaultLanguage>
|
||||
<LanguageTargets>$(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets</LanguageTargets>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x86'" />
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" />
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|netstandard1.3|AnyCPU'">
|
||||
<DefineConstants>RELEASE;NETSTANDARD1_3</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)'=='netstandard2.0'">
|
||||
<PackageReference Include="System.Net.Security" Version="4.3.2" />
|
||||
<PackageReference Include="System.Net.WebSockets" Version="4.3.0" />
|
||||
<PackageReference Include="System.Net.WebSockets.Client" Version="4.3.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)'=='netstandard1.3'">
|
||||
<PackageReference Include="System.Net.Security" Version="4.3.2" />
|
||||
<PackageReference Include="System.Net.WebSockets" Version="4.3.0" />
|
||||
<PackageReference Include="System.Net.WebSockets.Client" Version="4.3.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)'=='uap10.0'">
|
||||
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform" Version="5.4.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)'=='net452'">
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)'=='net461'">
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,12 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MQTTnet.ManagedClient
|
||||
{
|
||||
public interface IManagedMqttClientStorage
|
||||
{
|
||||
Task SaveQueuedMessagesAsync(IList<MqttApplicationMessage> messages);
|
||||
|
||||
Task<IList<MqttApplicationMessage>> LoadQueuedMessagesAsync();
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MQTTnet.ManagedClient
|
||||
{
|
||||
public class ManagedMqttClientStorageManager
|
||||
{
|
||||
private readonly List<MqttApplicationMessage> _applicationMessages = new List<MqttApplicationMessage>();
|
||||
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
|
||||
private readonly IManagedMqttClientStorage _storage;
|
||||
|
||||
public ManagedMqttClientStorageManager(IManagedMqttClientStorage storage)
|
||||
{
|
||||
_storage = storage ?? throw new ArgumentNullException(nameof(storage));
|
||||
}
|
||||
|
||||
public async Task LoadQueuedMessagesAsync()
|
||||
{
|
||||
var loadedMessages = await _storage.LoadQueuedMessagesAsync().ConfigureAwait(false);
|
||||
foreach (var loadedMessage in loadedMessages)
|
||||
{
|
||||
_applicationMessages.Add(loadedMessage);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task AddAsync(MqttApplicationMessage applicationMessage)
|
||||
{
|
||||
await _semaphore.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
_applicationMessages.Add(applicationMessage);
|
||||
await SaveAsync().ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_semaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task RemoveAsync(MqttApplicationMessage applicationMessage)
|
||||
{
|
||||
await _semaphore.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
var index = _applicationMessages.IndexOf(applicationMessage);
|
||||
if (index == -1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_applicationMessages.RemoveAt(index);
|
||||
await SaveAsync().ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_semaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private Task SaveAsync()
|
||||
{
|
||||
return _storage.SaveQueuedMessagesAsync(_applicationMessages);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
using System;
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
namespace MQTTnet
|
||||
{
|
||||
public sealed class MqttApplicationMessage
|
||||
{
|
||||
public MqttApplicationMessage()
|
||||
{
|
||||
}
|
||||
|
||||
[Obsolete("Use object initializer or _MqttApplicationMessageBuilder_ instead.")]
|
||||
public MqttApplicationMessage(string topic, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, bool retain)
|
||||
{
|
||||
Topic = topic ?? throw new ArgumentNullException(nameof(topic));
|
||||
Payload = payload ?? throw new ArgumentNullException(nameof(payload));
|
||||
QualityOfServiceLevel = qualityOfServiceLevel;
|
||||
Retain = retain;
|
||||
}
|
||||
|
||||
public string Topic { get; set; }
|
||||
|
||||
public byte[] Payload { get; set; }
|
||||
|
||||
public MqttQualityOfServiceLevel QualityOfServiceLevel { get; set; }
|
||||
|
||||
public bool Retain { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
namespace MQTTnet.Packets
|
||||
{
|
||||
public class MqttPacketHeader
|
||||
{
|
||||
public MqttControlPacketType ControlPacketType { get; set; }
|
||||
|
||||
public byte FixedHeader { get; set; }
|
||||
|
||||
public int BodyLength { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
namespace MQTTnet.Packets
|
||||
{
|
||||
public sealed class MqttPubAckPacket : MqttBasePublishPacket
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return "PubAck";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace MQTTnet.Serializer
|
||||
{
|
||||
public sealed class ByteReader
|
||||
{
|
||||
private readonly int _source;
|
||||
private int _index;
|
||||
|
||||
public ByteReader(int source)
|
||||
{
|
||||
_source = source;
|
||||
}
|
||||
|
||||
public bool Read()
|
||||
{
|
||||
if (_index >= 8)
|
||||
{
|
||||
throw new InvalidOperationException("End of byte reached.");
|
||||
}
|
||||
|
||||
var result = ((1 << _index) & _source) > 0;
|
||||
_index++;
|
||||
return result;
|
||||
}
|
||||
|
||||
public int Read(int count)
|
||||
{
|
||||
if (_index + count > 8)
|
||||
{
|
||||
throw new InvalidOperationException("End of byte will be reached.");
|
||||
}
|
||||
|
||||
var result = 0;
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
if (((1 << _index) & _source) > 0)
|
||||
{
|
||||
result |= 1 << i;
|
||||
}
|
||||
|
||||
_index++;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace MQTTnet.Serializer
|
||||
{
|
||||
public sealed class ByteWriter
|
||||
{
|
||||
private int _index;
|
||||
private int _byte;
|
||||
|
||||
public byte Value => (byte)_byte;
|
||||
|
||||
public void Write(int @byte, int count)
|
||||
{
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var value = ((1 << i) & @byte) > 0;
|
||||
Write(value);
|
||||
}
|
||||
}
|
||||
|
||||
public void Write(bool bit)
|
||||
{
|
||||
if (_index >= 8)
|
||||
{
|
||||
throw new InvalidOperationException("End of the byte reached.");
|
||||
}
|
||||
|
||||
if (bit)
|
||||
{
|
||||
_byte |= 1 << _index;
|
||||
}
|
||||
|
||||
_index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using MQTTnet.Packets;
|
||||
|
||||
namespace MQTTnet.Serializer
|
||||
{
|
||||
public interface IMqttPacketSerializer
|
||||
{
|
||||
MqttProtocolVersion ProtocolVersion { get; set; }
|
||||
|
||||
ICollection<ArraySegment<byte>> Serialize(MqttBasePacket mqttPacket);
|
||||
|
||||
MqttBasePacket Deserialize(MqttPacketHeader header, MemoryStream body);
|
||||
}
|
||||
}
|
||||
@@ -1,129 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Exceptions;
|
||||
using MQTTnet.Packets;
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
namespace MQTTnet.Serializer
|
||||
{
|
||||
public sealed class MqttPacketReader : BinaryReader
|
||||
{
|
||||
private readonly MqttPacketHeader _header;
|
||||
|
||||
public MqttPacketReader(MqttPacketHeader header, Stream bodyStream)
|
||||
: base(bodyStream, Encoding.UTF8, true)
|
||||
{
|
||||
_header = header;
|
||||
}
|
||||
|
||||
public bool EndOfRemainingData => BaseStream.Position == _header.BodyLength;
|
||||
|
||||
public static async Task<MqttPacketHeader> ReadHeaderAsync(Stream stream, CancellationToken cancellationToken)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Wait for the next package which starts with the header. At this point there will probably
|
||||
// some large delay and thus the thread should be put back to the pool (await). So ReadByte()
|
||||
// is not an option here.
|
||||
var buffer = new byte[1];
|
||||
var readCount = await stream.ReadAsync(buffer, 0, 1, cancellationToken).ConfigureAwait(false);
|
||||
if (readCount <= 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var fixedHeader = buffer[0];
|
||||
var controlPacketType = (MqttControlPacketType)(fixedHeader >> 4);
|
||||
var bodyLength = await ReadBodyLengthAsync(stream, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return new MqttPacketHeader
|
||||
{
|
||||
FixedHeader = fixedHeader,
|
||||
ControlPacketType = controlPacketType,
|
||||
BodyLength = bodyLength
|
||||
};
|
||||
}
|
||||
|
||||
public override ushort ReadUInt16()
|
||||
{
|
||||
var buffer = ReadBytes(2);
|
||||
|
||||
var temp = buffer[0];
|
||||
buffer[0] = buffer[1];
|
||||
buffer[1] = temp;
|
||||
|
||||
return BitConverter.ToUInt16(buffer, 0);
|
||||
}
|
||||
|
||||
public string ReadStringWithLengthPrefix()
|
||||
{
|
||||
var buffer = ReadWithLengthPrefix();
|
||||
if (buffer.Length == 0)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return Encoding.UTF8.GetString(buffer, 0, buffer.Length);
|
||||
}
|
||||
|
||||
public byte[] ReadWithLengthPrefix()
|
||||
{
|
||||
var length = ReadUInt16();
|
||||
if (length == 0)
|
||||
{
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
return ReadBytes(length);
|
||||
}
|
||||
|
||||
public byte[] ReadRemainingData()
|
||||
{
|
||||
return ReadBytes(_header.BodyLength - (int)BaseStream.Position);
|
||||
}
|
||||
|
||||
private static async Task<int> ReadBodyLengthAsync(Stream stream, CancellationToken cancellationToken)
|
||||
{
|
||||
// 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 buffer = new byte[1];
|
||||
var readBytes = new List<byte>();
|
||||
do
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
throw new TaskCanceledException();
|
||||
}
|
||||
|
||||
var readCount = await stream.ReadAsync(buffer, 0, 1, cancellationToken).ConfigureAwait(false);
|
||||
if (readCount <= 0)
|
||||
{
|
||||
throw new MqttCommunicationException("Connection closed while reading remaining length data.");
|
||||
}
|
||||
|
||||
encodedByte = buffer[0];
|
||||
readBytes.Add(encodedByte);
|
||||
|
||||
value += (byte)(encodedByte & 127) * multiplier;
|
||||
if (multiplier > 128 * 128 * 128)
|
||||
{
|
||||
throw new MqttProtocolViolationException($"Remaining length is invalid (Data={string.Join(",", readBytes)}).");
|
||||
}
|
||||
|
||||
multiplier *= 128;
|
||||
} while ((encodedByte & 128) != 0);
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
namespace MQTTnet.Serializer
|
||||
{
|
||||
public sealed class MqttPacketWriter : BinaryWriter
|
||||
{
|
||||
public MqttPacketWriter(Stream stream)
|
||||
: base(stream, Encoding.UTF8, true)
|
||||
{
|
||||
}
|
||||
|
||||
public static byte BuildFixedHeader(MqttControlPacketType packetType, byte flags = 0)
|
||||
{
|
||||
var fixedHeader = (int)packetType << 4;
|
||||
fixedHeader |= flags;
|
||||
return (byte)fixedHeader;
|
||||
}
|
||||
|
||||
public override void Write(ushort value)
|
||||
{
|
||||
var buffer = BitConverter.GetBytes(value);
|
||||
Write(buffer[1], buffer[0]);
|
||||
}
|
||||
|
||||
public new void Write(params byte[] values)
|
||||
{
|
||||
base.Write(values);
|
||||
}
|
||||
|
||||
public new void Write(byte value)
|
||||
{
|
||||
base.Write(value);
|
||||
}
|
||||
|
||||
public void Write(ByteWriter value)
|
||||
{
|
||||
if (value == null) throw new ArgumentNullException(nameof(value));
|
||||
|
||||
Write(value.Value);
|
||||
}
|
||||
|
||||
public void WriteWithLengthPrefix(string value)
|
||||
{
|
||||
WriteWithLengthPrefix(Encoding.UTF8.GetBytes(value ?? string.Empty));
|
||||
}
|
||||
|
||||
public void WriteWithLengthPrefix(byte[] value)
|
||||
{
|
||||
var length = (ushort)value.Length;
|
||||
|
||||
Write(length);
|
||||
Write(value);
|
||||
}
|
||||
|
||||
public static void WriteRemainingLength(int length, BinaryWriter target)
|
||||
{
|
||||
if (length == 0)
|
||||
{
|
||||
target.Write((byte)0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Alorithm taken from http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html.
|
||||
var x = length;
|
||||
do
|
||||
{
|
||||
var encodedByte = x % 128;
|
||||
x = x / 128;
|
||||
if (x > 0)
|
||||
{
|
||||
encodedByte = encodedByte | 128;
|
||||
}
|
||||
|
||||
target.Write((byte)encodedByte);
|
||||
} while (x > 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
using System;
|
||||
using MQTTnet.Serializer;
|
||||
|
||||
namespace MQTTnet.Server
|
||||
{
|
||||
public class ConnectedMqttClient
|
||||
{
|
||||
public string ClientId { get; set; }
|
||||
|
||||
public MqttProtocolVersion ProtocolVersion { get; set; }
|
||||
|
||||
public TimeSpan LastPacketReceived { get; set; }
|
||||
|
||||
public TimeSpan LastNonKeepAlivePacketReceived { get; set; }
|
||||
|
||||
public int PendingApplicationMessages { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace MQTTnet.Server
|
||||
{
|
||||
public class MqttClientConnectedEventArgs : EventArgs
|
||||
{
|
||||
public MqttClientConnectedEventArgs(ConnectedMqttClient client)
|
||||
{
|
||||
Client = client ?? throw new ArgumentNullException(nameof(client));
|
||||
}
|
||||
|
||||
public ConnectedMqttClient Client { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Packets;
|
||||
|
||||
namespace MQTTnet.Server
|
||||
{
|
||||
public sealed class MqttClientKeepAliveMonitor
|
||||
{
|
||||
private readonly Stopwatch _lastPacketReceivedTracker = new Stopwatch();
|
||||
private readonly Stopwatch _lastNonKeepAlivePacketReceivedTracker = new Stopwatch();
|
||||
|
||||
private readonly string _clientId;
|
||||
private readonly Func<Task> _timeoutCallback;
|
||||
private readonly IMqttNetLogger _logger;
|
||||
|
||||
private Task _workerTask;
|
||||
|
||||
public MqttClientKeepAliveMonitor(string clientId, Func<Task> timeoutCallback, IMqttNetLogger logger)
|
||||
{
|
||||
_clientId = clientId;
|
||||
_timeoutCallback = timeoutCallback;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public TimeSpan LastPacketReceived => _lastPacketReceivedTracker.Elapsed;
|
||||
|
||||
public TimeSpan LastNonKeepAlivePacketReceived => _lastNonKeepAlivePacketReceivedTracker.Elapsed;
|
||||
|
||||
public void Start(int keepAlivePeriod, CancellationToken cancellationToken)
|
||||
{
|
||||
if (keepAlivePeriod == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_workerTask = Task.Run(() => RunAsync(keepAlivePeriod, cancellationToken).ConfigureAwait(false), cancellationToken);
|
||||
}
|
||||
|
||||
public void WaitForCompletion()
|
||||
{
|
||||
if (_workerTask != null)
|
||||
{
|
||||
Task.WaitAll(_workerTask);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RunAsync(int keepAlivePeriod, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
_lastPacketReceivedTracker.Restart();
|
||||
_lastNonKeepAlivePacketReceivedTracker.Restart();
|
||||
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
// Values described here: [MQTT-3.1.2-24].
|
||||
if (_lastPacketReceivedTracker.Elapsed.TotalSeconds > keepAlivePeriod * 1.5D)
|
||||
{
|
||||
_logger.Warning<MqttClientSession>("Client '{0}': Did not receive any packet or keep alive signal.", _clientId);
|
||||
|
||||
if (_timeoutCallback != null)
|
||||
{
|
||||
await _timeoutCallback().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await Task.Delay(keepAlivePeriod, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.Error<MqttClientSession>(exception, "Client '{0}': Unhandled exception while checking keep alive timeouts.", _clientId);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_logger.Verbose<MqttClientSession>("Client {0}: Stopped checking keep alive timeout.", _clientId);
|
||||
}
|
||||
}
|
||||
|
||||
public void PacketReceived(MqttBasePacket packet)
|
||||
{
|
||||
_lastPacketReceivedTracker.Restart();
|
||||
|
||||
if (!(packet is MqttPingReqPacket))
|
||||
{
|
||||
_lastNonKeepAlivePacketReceivedTracker.Restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Exceptions;
|
||||
using MQTTnet.Packets;
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
namespace MQTTnet.Server
|
||||
{
|
||||
public sealed class MqttClientPendingMessagesQueue : IDisposable
|
||||
{
|
||||
private readonly ConcurrentQueue<MqttBasePacket> _queue = new ConcurrentQueue<MqttBasePacket>();
|
||||
private readonly SemaphoreSlim _queueWaitSemaphore = new SemaphoreSlim(0);
|
||||
private readonly IMqttServerOptions _options;
|
||||
private readonly MqttClientSession _clientSession;
|
||||
private readonly IMqttNetLogger _logger;
|
||||
|
||||
private Task _workerTask;
|
||||
|
||||
public MqttClientPendingMessagesQueue(IMqttServerOptions options, MqttClientSession clientSession, IMqttNetLogger logger)
|
||||
{
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
_clientSession = clientSession ?? throw new ArgumentNullException(nameof(clientSession));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
public int Count => _queue.Count;
|
||||
|
||||
public void Start(IMqttChannelAdapter adapter, CancellationToken cancellationToken)
|
||||
{
|
||||
if (adapter == null) throw new ArgumentNullException(nameof(adapter));
|
||||
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_workerTask = Task.Run(() => SendQueuedPacketsAsync(adapter, cancellationToken), cancellationToken);
|
||||
}
|
||||
|
||||
public void WaitForCompletion()
|
||||
{
|
||||
if (_workerTask != null)
|
||||
{
|
||||
Task.WaitAll(_workerTask);
|
||||
}
|
||||
}
|
||||
|
||||
public void Enqueue(MqttBasePacket packet)
|
||||
{
|
||||
if (packet == null) throw new ArgumentNullException(nameof(packet));
|
||||
|
||||
_queue.Enqueue(packet);
|
||||
_queueWaitSemaphore.Release();
|
||||
|
||||
_logger.Verbose<MqttClientPendingMessagesQueue>("Enqueued packet (ClientId: {0}).", _clientSession.ClientId);
|
||||
}
|
||||
|
||||
private async Task SendQueuedPacketsAsync(IMqttChannelAdapter adapter, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
await SendNextQueuedPacketAsync(adapter, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.Error<MqttClientPendingMessagesQueue>(exception, "Unhandled exception while sending enqueued packet (ClientId: {0}).", _clientSession.ClientId);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SendNextQueuedPacketAsync(IMqttChannelAdapter adapter, CancellationToken cancellationToken)
|
||||
{
|
||||
MqttBasePacket packet = null;
|
||||
try
|
||||
{
|
||||
await _queueWaitSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
if (!_queue.TryDequeue(out packet))
|
||||
{
|
||||
throw new InvalidOperationException(); // should not happen
|
||||
}
|
||||
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { packet }).ConfigureAwait(false);
|
||||
|
||||
_logger.Verbose<MqttClientPendingMessagesQueue>("Enqueued packet sent (ClientId: {0}).", _clientSession.ClientId);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
if (exception is MqttCommunicationTimedOutException)
|
||||
{
|
||||
_logger.Warning<MqttClientPendingMessagesQueue>(exception, "Sending publish packet failed due to timeout (ClientId: {0}).", _clientSession.ClientId);
|
||||
}
|
||||
else if (exception is MqttCommunicationException)
|
||||
{
|
||||
_logger.Warning<MqttClientPendingMessagesQueue>(exception, "Sending publish packet failed due to communication exception (ClientId: {0}).", _clientSession.ClientId);
|
||||
}
|
||||
else if (exception is OperationCanceledException)
|
||||
{
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Error<MqttClientPendingMessagesQueue>(exception, "Sending publish packet failed (ClientId: {0}).", _clientSession.ClientId);
|
||||
}
|
||||
|
||||
if (packet is MqttPublishPacket publishPacket)
|
||||
{
|
||||
if (publishPacket.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce)
|
||||
{
|
||||
publishPacket.Dup = true;
|
||||
_queue.Enqueue(packet);
|
||||
_queueWaitSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
if (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
await _clientSession.StopAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_queueWaitSemaphore?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,346 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Exceptions;
|
||||
using MQTTnet.Internal;
|
||||
using MQTTnet.Packets;
|
||||
using MQTTnet.Protocol;
|
||||
using MQTTnet.Serializer;
|
||||
|
||||
namespace MQTTnet.Server
|
||||
{
|
||||
public sealed class MqttClientSession : IDisposable
|
||||
{
|
||||
private readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider();
|
||||
private readonly IMqttServerOptions _options;
|
||||
private readonly IMqttNetLogger _logger;
|
||||
private readonly MqttRetainedMessagesManager _retainedMessagesManager;
|
||||
|
||||
private IMqttChannelAdapter _adapter;
|
||||
private CancellationTokenSource _cancellationTokenSource;
|
||||
private MqttApplicationMessage _willMessage;
|
||||
private bool _wasCleanDisconnect;
|
||||
|
||||
public MqttClientSession(
|
||||
string clientId,
|
||||
IMqttServerOptions options,
|
||||
MqttRetainedMessagesManager retainedMessagesManager,
|
||||
IMqttNetLogger logger)
|
||||
{
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
_retainedMessagesManager = retainedMessagesManager ?? throw new ArgumentNullException(nameof(retainedMessagesManager));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
|
||||
ClientId = clientId;
|
||||
|
||||
KeepAliveMonitor = new MqttClientKeepAliveMonitor(clientId, StopDueToKeepAliveTimeoutAsync, _logger);
|
||||
SubscriptionsManager = new MqttClientSubscriptionsManager(_options, clientId);
|
||||
PendingMessagesQueue = new MqttClientPendingMessagesQueue(_options, this, _logger);
|
||||
}
|
||||
|
||||
public Func<MqttClientSession, MqttApplicationMessage, Task> ApplicationMessageReceivedCallback { get; set; }
|
||||
|
||||
public MqttClientSubscriptionsManager SubscriptionsManager { get; }
|
||||
|
||||
public MqttClientPendingMessagesQueue PendingMessagesQueue { get; }
|
||||
|
||||
public MqttClientKeepAliveMonitor KeepAliveMonitor { get; }
|
||||
|
||||
public string ClientId { get; }
|
||||
|
||||
public MqttProtocolVersion? ProtocolVersion => _adapter?.PacketSerializer.ProtocolVersion;
|
||||
|
||||
public bool IsConnected => _adapter != null;
|
||||
|
||||
public async Task<bool> RunAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter adapter)
|
||||
{
|
||||
if (connectPacket == null) throw new ArgumentNullException(nameof(connectPacket));
|
||||
if (adapter == null) throw new ArgumentNullException(nameof(adapter));
|
||||
|
||||
try
|
||||
{
|
||||
var cancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
_wasCleanDisconnect = false;
|
||||
_willMessage = connectPacket.WillMessage;
|
||||
_adapter = adapter;
|
||||
_cancellationTokenSource = cancellationTokenSource;
|
||||
|
||||
PendingMessagesQueue.Start(adapter, cancellationTokenSource.Token);
|
||||
KeepAliveMonitor.Start(connectPacket.KeepAlivePeriod, cancellationTokenSource.Token);
|
||||
|
||||
await ReceivePacketsAsync(adapter, cancellationTokenSource.Token).ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
catch (MqttCommunicationException exception)
|
||||
{
|
||||
_logger.Warning<MqttClientSession>(exception, "Client '{0}': Communication exception while processing client packets.", ClientId);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.Error<MqttClientSession>(exception, "Client '{0}': Unhandled exception while processing client packets.", ClientId);
|
||||
}
|
||||
|
||||
return _wasCleanDisconnect;
|
||||
}
|
||||
|
||||
public async Task StopAsync(bool wasCleanDisconnect = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_cancellationTokenSource == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_wasCleanDisconnect = wasCleanDisconnect;
|
||||
|
||||
_cancellationTokenSource?.Cancel(false);
|
||||
|
||||
PendingMessagesQueue.WaitForCompletion();
|
||||
KeepAliveMonitor.WaitForCompletion();
|
||||
|
||||
_cancellationTokenSource?.Dispose();
|
||||
_cancellationTokenSource = null;
|
||||
|
||||
_adapter = null;
|
||||
|
||||
_logger.Info<MqttClientSession>("Client '{0}': Session stopped.", ClientId);
|
||||
}
|
||||
finally
|
||||
{
|
||||
var willMessage = _willMessage;
|
||||
_willMessage = null; // clear willmessage so it is send just once
|
||||
|
||||
if (willMessage != null && !wasCleanDisconnect)
|
||||
{
|
||||
await ApplicationMessageReceivedCallback(this, willMessage).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task EnqueueApplicationMessageAsync(MqttApplicationMessage applicationMessage)
|
||||
{
|
||||
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
|
||||
|
||||
var result = await SubscriptionsManager.CheckSubscriptionsAsync(applicationMessage);
|
||||
if (!result.IsSubscribed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var publishPacket = applicationMessage.ToPublishPacket();
|
||||
publishPacket.QualityOfServiceLevel = result.QualityOfServiceLevel;
|
||||
|
||||
if (publishPacket.QualityOfServiceLevel > 0)
|
||||
{
|
||||
publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNewPacketIdentifier();
|
||||
}
|
||||
|
||||
PendingMessagesQueue.Enqueue(publishPacket);
|
||||
}
|
||||
|
||||
public async Task SubscribeAsync(IList<TopicFilter> topicFilters)
|
||||
{
|
||||
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
|
||||
|
||||
await SubscriptionsManager.SubscribeAsync(new MqttSubscribePacket
|
||||
{
|
||||
TopicFilters = topicFilters
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
await EnqueueSubscribedRetainedMessagesAsync(topicFilters).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public Task UnsubscribeAsync(IList<string> topicFilters)
|
||||
{
|
||||
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
|
||||
|
||||
return SubscriptionsManager.UnsubscribeAsync(new MqttUnsubscribePacket
|
||||
{
|
||||
TopicFilters = topicFilters
|
||||
});
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
ApplicationMessageReceivedCallback = null;
|
||||
|
||||
SubscriptionsManager?.Dispose();
|
||||
PendingMessagesQueue?.Dispose();
|
||||
|
||||
_cancellationTokenSource?.Dispose();
|
||||
}
|
||||
|
||||
private Task StopDueToKeepAliveTimeoutAsync()
|
||||
{
|
||||
_logger.Info<MqttClientSession>("Client '{0}': Timeout while waiting for KeepAlive packet.", ClientId);
|
||||
return StopAsync();
|
||||
}
|
||||
|
||||
private async Task ReceivePacketsAsync(IMqttChannelAdapter adapter, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
var packet = await adapter.ReceivePacketAsync(TimeSpan.Zero, cancellationToken).ConfigureAwait(false);
|
||||
KeepAliveMonitor.PacketReceived(packet);
|
||||
await ProcessReceivedPacketAsync(adapter, packet, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
catch (MqttCommunicationException exception)
|
||||
{
|
||||
_logger.Warning<MqttClientSession>(exception, "Client '{0}': Communication exception while processing client packets.", ClientId);
|
||||
await StopAsync().ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.Error<MqttClientSession>(exception, "Client '{0}': Unhandled exception while processing client packets.", ClientId);
|
||||
await StopAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private Task ProcessReceivedPacketAsync(IMqttChannelAdapter adapter, MqttBasePacket packet, CancellationToken cancellationToken)
|
||||
{
|
||||
if (packet is MqttPublishPacket publishPacket)
|
||||
{
|
||||
return HandleIncomingPublishPacketAsync(adapter, publishPacket, cancellationToken);
|
||||
}
|
||||
|
||||
if (packet is MqttPingReqPacket)
|
||||
{
|
||||
return adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { new MqttPingRespPacket() });
|
||||
}
|
||||
|
||||
if (packet is MqttPubRelPacket pubRelPacket)
|
||||
{
|
||||
return HandleIncomingPubRelPacketAsync(adapter, pubRelPacket, cancellationToken);
|
||||
}
|
||||
|
||||
if (packet is MqttPubRecPacket pubRecPacket)
|
||||
{
|
||||
var responsePacket = new MqttPubRelPacket
|
||||
{
|
||||
PacketIdentifier = pubRecPacket.PacketIdentifier
|
||||
};
|
||||
|
||||
return adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { responsePacket });
|
||||
}
|
||||
|
||||
if (packet is MqttPubAckPacket || packet is MqttPubCompPacket)
|
||||
{
|
||||
// Discard message.
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
if (packet is MqttSubscribePacket subscribePacket)
|
||||
{
|
||||
return HandleIncomingSubscribePacketAsync(adapter, subscribePacket, cancellationToken);
|
||||
}
|
||||
|
||||
if (packet is MqttUnsubscribePacket unsubscribePacket)
|
||||
{
|
||||
return HandleIncomingUnsubscribePacketAsync(adapter, unsubscribePacket, cancellationToken);
|
||||
}
|
||||
|
||||
if (packet is MqttDisconnectPacket)
|
||||
{
|
||||
return StopAsync(true);
|
||||
}
|
||||
|
||||
if (packet is MqttConnectPacket)
|
||||
{
|
||||
return StopAsync();
|
||||
}
|
||||
|
||||
_logger.Warning<MqttClientSession>("Client '{0}': Received not supported packet ({1}). Closing connection.", ClientId, packet);
|
||||
return StopAsync();
|
||||
}
|
||||
|
||||
private async Task HandleIncomingSubscribePacketAsync(IMqttChannelAdapter adapter, MqttSubscribePacket subscribePacket, CancellationToken cancellationToken)
|
||||
{
|
||||
var subscribeResult = await SubscriptionsManager.SubscribeAsync(subscribePacket).ConfigureAwait(false);
|
||||
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { subscribeResult.ResponsePacket }).ConfigureAwait(false);
|
||||
|
||||
if (subscribeResult.CloseConnection)
|
||||
{
|
||||
await StopAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await EnqueueSubscribedRetainedMessagesAsync(subscribePacket.TopicFilters).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task HandleIncomingUnsubscribePacketAsync(IMqttChannelAdapter adapter, MqttUnsubscribePacket unsubscribePacket, CancellationToken cancellationToken)
|
||||
{
|
||||
var unsubscribeResult = await SubscriptionsManager.UnsubscribeAsync(unsubscribePacket).ConfigureAwait(false);
|
||||
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { unsubscribeResult });
|
||||
}
|
||||
|
||||
private async Task EnqueueSubscribedRetainedMessagesAsync(ICollection<TopicFilter> topicFilters)
|
||||
{
|
||||
var retainedMessages = await _retainedMessagesManager.GetSubscribedMessagesAsync(topicFilters);
|
||||
foreach (var applicationMessage in retainedMessages)
|
||||
{
|
||||
await EnqueueApplicationMessageAsync(applicationMessage).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private Task HandleIncomingPublishPacketAsync(IMqttChannelAdapter adapter, MqttPublishPacket publishPacket, CancellationToken cancellationToken)
|
||||
{
|
||||
var applicationMessage = publishPacket.ToApplicationMessage();
|
||||
|
||||
switch (applicationMessage.QualityOfServiceLevel)
|
||||
{
|
||||
case MqttQualityOfServiceLevel.AtMostOnce:
|
||||
{
|
||||
return ApplicationMessageReceivedCallback?.Invoke(this, applicationMessage);
|
||||
}
|
||||
case MqttQualityOfServiceLevel.AtLeastOnce:
|
||||
{
|
||||
return HandleIncomingPublishPacketWithQoS1(adapter, applicationMessage, publishPacket, cancellationToken);
|
||||
}
|
||||
case MqttQualityOfServiceLevel.ExactlyOnce:
|
||||
{
|
||||
return HandleIncomingPublishPacketWithQoS2(adapter, applicationMessage, publishPacket, cancellationToken);
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new MqttCommunicationException("Received a not supported QoS level.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleIncomingPublishPacketWithQoS1(IMqttChannelAdapter adapter, MqttApplicationMessage applicationMessage, MqttPublishPacket publishPacket, CancellationToken cancellationToken)
|
||||
{
|
||||
await ApplicationMessageReceivedCallback(this, applicationMessage).ConfigureAwait(false);
|
||||
|
||||
var response = new MqttPubAckPacket { PacketIdentifier = publishPacket.PacketIdentifier };
|
||||
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { response }).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task HandleIncomingPublishPacketWithQoS2(IMqttChannelAdapter adapter, MqttApplicationMessage applicationMessage, MqttPublishPacket publishPacket, CancellationToken cancellationToken)
|
||||
{
|
||||
// QoS 2 is implement as method "B" [4.3.3 QoS 2: Exactly once delivery]
|
||||
await ApplicationMessageReceivedCallback(this, applicationMessage).ConfigureAwait(false);
|
||||
|
||||
var response = new MqttPubRecPacket { PacketIdentifier = publishPacket.PacketIdentifier };
|
||||
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { response }).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private Task HandleIncomingPubRelPacketAsync(IMqttChannelAdapter adapter, MqttPubRelPacket pubRelPacket, CancellationToken cancellationToken)
|
||||
{
|
||||
var response = new MqttPubCompPacket { PacketIdentifier = pubRelPacket.PacketIdentifier };
|
||||
return adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { response });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,335 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Exceptions;
|
||||
using MQTTnet.Packets;
|
||||
using MQTTnet.Protocol;
|
||||
using MQTTnet.Serializer;
|
||||
|
||||
namespace MQTTnet.Server
|
||||
{
|
||||
public sealed class MqttClientSessionsManager : IDisposable
|
||||
{
|
||||
private readonly Dictionary<string, MqttClientSession> _sessions = new Dictionary<string, MqttClientSession>();
|
||||
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
|
||||
|
||||
private readonly IMqttServerOptions _options;
|
||||
private readonly MqttRetainedMessagesManager _retainedMessagesManager;
|
||||
private readonly IMqttNetLogger _logger;
|
||||
|
||||
public MqttClientSessionsManager(IMqttServerOptions options, MqttRetainedMessagesManager retainedMessagesManager, IMqttNetLogger logger)
|
||||
{
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
_retainedMessagesManager = retainedMessagesManager ?? throw new ArgumentNullException(nameof(retainedMessagesManager));
|
||||
}
|
||||
|
||||
public Action<ConnectedMqttClient> ClientConnectedCallback { get; set; }
|
||||
public Action<ConnectedMqttClient, bool> ClientDisconnectedCallback { get; set; }
|
||||
public Action<string, TopicFilter> ClientSubscribedTopicCallback { get; set; }
|
||||
public Action<string, string> ClientUnsubscribedTopicCallback { get; set; }
|
||||
public Action<string, MqttApplicationMessage> ApplicationMessageReceivedCallback { get; set; }
|
||||
|
||||
public async Task RunSessionAsync(IMqttChannelAdapter clientAdapter, CancellationToken cancellationToken)
|
||||
{
|
||||
var clientId = string.Empty;
|
||||
var wasCleanDisconnect = false;
|
||||
MqttClientSession clientSession = null;
|
||||
|
||||
try
|
||||
{
|
||||
if (!(await clientAdapter.ReceivePacketAsync(_options.DefaultCommunicationTimeout, cancellationToken)
|
||||
.ConfigureAwait(false) is MqttConnectPacket connectPacket))
|
||||
{
|
||||
throw new MqttProtocolViolationException(
|
||||
"The first packet from a client must be a 'CONNECT' packet [MQTT-3.1.0-1].");
|
||||
}
|
||||
|
||||
clientId = connectPacket.ClientId;
|
||||
|
||||
// Switch to the required protocol version before sending any response.
|
||||
clientAdapter.PacketSerializer.ProtocolVersion = connectPacket.ProtocolVersion;
|
||||
|
||||
var connectReturnCode = ValidateConnection(connectPacket);
|
||||
if (connectReturnCode != MqttConnectReturnCode.ConnectionAccepted)
|
||||
{
|
||||
await clientAdapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[]
|
||||
{
|
||||
new MqttConnAckPacket
|
||||
{
|
||||
ConnectReturnCode = connectReturnCode
|
||||
}
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var result = await GetOrCreateClientSessionAsync(connectPacket).ConfigureAwait(false);
|
||||
clientSession = result.Session;
|
||||
|
||||
await clientAdapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[]
|
||||
{
|
||||
new MqttConnAckPacket
|
||||
{
|
||||
ConnectReturnCode = connectReturnCode,
|
||||
IsSessionPresent = result.IsExistingSession
|
||||
}
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
ClientConnectedCallback?.Invoke(new ConnectedMqttClient
|
||||
{
|
||||
ClientId = clientId,
|
||||
ProtocolVersion = clientAdapter.PacketSerializer.ProtocolVersion
|
||||
});
|
||||
|
||||
wasCleanDisconnect = await clientSession.RunAsync(connectPacket, clientAdapter).ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.Error<MqttClientSessionsManager>(exception, exception.Message);
|
||||
}
|
||||
finally
|
||||
{
|
||||
try
|
||||
{
|
||||
await clientAdapter.DisconnectAsync(_options.DefaultCommunicationTimeout).ConfigureAwait(false);
|
||||
clientAdapter.Dispose();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.Error<MqttClientSessionsManager>(exception, exception.Message);
|
||||
}
|
||||
|
||||
ClientDisconnectedCallback?.Invoke(new ConnectedMqttClient
|
||||
{
|
||||
ClientId = clientId,
|
||||
ProtocolVersion = clientAdapter.PacketSerializer.ProtocolVersion,
|
||||
PendingApplicationMessages = clientSession?.PendingMessagesQueue.Count ?? 0
|
||||
},
|
||||
wasCleanDisconnect);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task StopAsync()
|
||||
{
|
||||
await _semaphore.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
foreach (var session in _sessions)
|
||||
{
|
||||
await session.Value.StopAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
_sessions.Clear();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_semaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IList<ConnectedMqttClient>> GetConnectedClientsAsync()
|
||||
{
|
||||
await _semaphore.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
return _sessions.Where(s => s.Value.IsConnected).Select(s => new ConnectedMqttClient
|
||||
{
|
||||
ClientId = s.Value.ClientId,
|
||||
ProtocolVersion = s.Value.ProtocolVersion ?? MqttProtocolVersion.V311,
|
||||
LastPacketReceived = s.Value.KeepAliveMonitor.LastPacketReceived,
|
||||
LastNonKeepAlivePacketReceived = s.Value.KeepAliveMonitor.LastNonKeepAlivePacketReceived,
|
||||
PendingApplicationMessages = s.Value.PendingMessagesQueue.Count
|
||||
}).ToList();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_semaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DispatchApplicationMessageAsync(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage)
|
||||
{
|
||||
try
|
||||
{
|
||||
var interceptorContext = InterceptApplicationMessage(senderClientSession, applicationMessage);
|
||||
if (interceptorContext.CloseConnection)
|
||||
{
|
||||
await senderClientSession.StopAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (interceptorContext.ApplicationMessage == null || !interceptorContext.AcceptPublish)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (applicationMessage.Retain)
|
||||
{
|
||||
await _retainedMessagesManager.HandleMessageAsync(senderClientSession?.ClientId, applicationMessage).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
ApplicationMessageReceivedCallback?.Invoke(senderClientSession?.ClientId, applicationMessage);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.Error<MqttClientSessionsManager>(exception, "Error while processing application message");
|
||||
}
|
||||
|
||||
await _semaphore.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
foreach (var clientSession in _sessions.Values)
|
||||
{
|
||||
await clientSession.EnqueueApplicationMessageAsync(applicationMessage);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_semaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SubscribeAsync(string clientId, IList<TopicFilter> topicFilters)
|
||||
{
|
||||
if (clientId == null) throw new ArgumentNullException(nameof(clientId));
|
||||
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
|
||||
|
||||
await _semaphore.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
if (!_sessions.TryGetValue(clientId, out var session))
|
||||
{
|
||||
throw new InvalidOperationException($"Client session {clientId} is unknown.");
|
||||
}
|
||||
|
||||
await session.SubscribeAsync(topicFilters);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_semaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task UnsubscribeAsync(string clientId, IList<string> topicFilters)
|
||||
{
|
||||
if (clientId == null) throw new ArgumentNullException(nameof(clientId));
|
||||
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
|
||||
|
||||
await _semaphore.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
if (!_sessions.TryGetValue(clientId, out var session))
|
||||
{
|
||||
throw new InvalidOperationException($"Client session {clientId} is unknown.");
|
||||
}
|
||||
|
||||
await session.UnsubscribeAsync(topicFilters);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_semaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private MqttApplicationMessageInterceptorContext InterceptApplicationMessage(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage)
|
||||
{
|
||||
var interceptorContext = new MqttApplicationMessageInterceptorContext(
|
||||
senderClientSession?.ClientId,
|
||||
applicationMessage);
|
||||
|
||||
var interceptor = _options.ApplicationMessageInterceptor;
|
||||
if (interceptor == null)
|
||||
{
|
||||
return interceptorContext;
|
||||
}
|
||||
|
||||
interceptor(interceptorContext);
|
||||
return interceptorContext;
|
||||
}
|
||||
|
||||
private MqttConnectReturnCode ValidateConnection(MqttConnectPacket connectPacket)
|
||||
{
|
||||
if (_options.ConnectionValidator == null)
|
||||
{
|
||||
return MqttConnectReturnCode.ConnectionAccepted;
|
||||
}
|
||||
|
||||
var context = new MqttConnectionValidatorContext(
|
||||
connectPacket.ClientId,
|
||||
connectPacket.Username,
|
||||
connectPacket.Password,
|
||||
connectPacket.WillMessage);
|
||||
|
||||
_options.ConnectionValidator(context);
|
||||
return context.ReturnCode;
|
||||
}
|
||||
|
||||
private async Task<GetOrCreateClientSessionResult> GetOrCreateClientSessionAsync(MqttConnectPacket connectPacket)
|
||||
{
|
||||
await _semaphore.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
var isSessionPresent = _sessions.TryGetValue(connectPacket.ClientId, out var clientSession);
|
||||
if (isSessionPresent)
|
||||
{
|
||||
if (connectPacket.CleanSession)
|
||||
{
|
||||
_sessions.Remove(connectPacket.ClientId);
|
||||
|
||||
await clientSession.StopAsync().ConfigureAwait(false);
|
||||
clientSession.Dispose();
|
||||
clientSession = null;
|
||||
|
||||
_logger.Verbose<MqttClientSessionsManager>("Stopped existing session of client '{0}'.", connectPacket.ClientId);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Verbose<MqttClientSessionsManager>("Reusing existing session of client '{0}'.", connectPacket.ClientId);
|
||||
}
|
||||
}
|
||||
|
||||
var isExistingSession = true;
|
||||
if (clientSession == null)
|
||||
{
|
||||
isExistingSession = false;
|
||||
|
||||
clientSession = new MqttClientSession(connectPacket.ClientId, _options, _retainedMessagesManager, _logger)
|
||||
{
|
||||
ApplicationMessageReceivedCallback = DispatchApplicationMessageAsync
|
||||
};
|
||||
|
||||
clientSession.SubscriptionsManager.TopicSubscribedCallback = ClientSubscribedTopicCallback;
|
||||
clientSession.SubscriptionsManager.TopicUnsubscribedCallback = ClientUnsubscribedTopicCallback;
|
||||
|
||||
_sessions[connectPacket.ClientId] = clientSession;
|
||||
|
||||
_logger.Verbose<MqttClientSessionsManager>("Created a new session for client '{0}'.", connectPacket.ClientId);
|
||||
}
|
||||
|
||||
return new GetOrCreateClientSessionResult { IsExistingSession = isExistingSession, Session = clientSession };
|
||||
}
|
||||
finally
|
||||
{
|
||||
_semaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
ClientConnectedCallback = null;
|
||||
ClientDisconnectedCallback = null;
|
||||
ClientSubscribedTopicCallback = null;
|
||||
ClientUnsubscribedTopicCallback = null;
|
||||
ApplicationMessageReceivedCallback = null;
|
||||
|
||||
_semaphore?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,144 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Diagnostics;
|
||||
|
||||
namespace MQTTnet.Server
|
||||
{
|
||||
public sealed class MqttRetainedMessagesManager : IDisposable
|
||||
{
|
||||
private readonly Dictionary<string, MqttApplicationMessage> _retainedMessages = new Dictionary<string, MqttApplicationMessage>();
|
||||
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
|
||||
private readonly IMqttNetLogger _logger;
|
||||
private readonly IMqttServerOptions _options;
|
||||
|
||||
public MqttRetainedMessagesManager(IMqttServerOptions options, IMqttNetLogger logger)
|
||||
{
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
public async Task LoadMessagesAsync()
|
||||
{
|
||||
if (_options.Storage == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await _semaphore.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
var retainedMessages = await _options.Storage.LoadRetainedMessagesAsync();
|
||||
|
||||
_retainedMessages.Clear();
|
||||
foreach (var retainedMessage in retainedMessages)
|
||||
{
|
||||
_retainedMessages[retainedMessage.Topic] = retainedMessage;
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.Error<MqttRetainedMessagesManager>(exception, "Unhandled exception while loading retained messages.");
|
||||
}
|
||||
finally
|
||||
{
|
||||
_semaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task HandleMessageAsync(string clientId, MqttApplicationMessage applicationMessage)
|
||||
{
|
||||
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
|
||||
|
||||
await _semaphore.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
await HandleMessageInternalAsync(clientId, applicationMessage);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.Error<MqttRetainedMessagesManager>(exception, "Unhandled exception while handling retained messages.");
|
||||
}
|
||||
finally
|
||||
{
|
||||
_semaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<MqttApplicationMessage>> GetSubscribedMessagesAsync(ICollection<TopicFilter> topicFilters)
|
||||
{
|
||||
var retainedMessages = new List<MqttApplicationMessage>();
|
||||
|
||||
await _semaphore.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
foreach (var retainedMessage in _retainedMessages.Values)
|
||||
{
|
||||
foreach (var topicFilter in topicFilters)
|
||||
{
|
||||
if (!MqttTopicFilterComparer.IsMatch(retainedMessage.Topic, topicFilter.Topic))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
retainedMessages.Add(retainedMessage);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_semaphore.Release();
|
||||
}
|
||||
|
||||
return retainedMessages;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_semaphore?.Dispose();
|
||||
}
|
||||
|
||||
private async Task HandleMessageInternalAsync(string clientId, MqttApplicationMessage applicationMessage)
|
||||
{
|
||||
var saveIsRequired = false;
|
||||
|
||||
if (applicationMessage.Payload?.Any() == false)
|
||||
{
|
||||
saveIsRequired = _retainedMessages.Remove(applicationMessage.Topic);
|
||||
_logger.Info<MqttRetainedMessagesManager>("Client '{0}' cleared retained message for topic '{1}'.", clientId, applicationMessage.Topic);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!_retainedMessages.ContainsKey(applicationMessage.Topic))
|
||||
{
|
||||
_retainedMessages[applicationMessage.Topic] = applicationMessage;
|
||||
saveIsRequired = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var existingMessage = _retainedMessages[applicationMessage.Topic];
|
||||
if (existingMessage.QualityOfServiceLevel != applicationMessage.QualityOfServiceLevel || !existingMessage.Payload.SequenceEqual(applicationMessage.Payload ?? new byte[0]))
|
||||
{
|
||||
_retainedMessages[applicationMessage.Topic] = applicationMessage;
|
||||
saveIsRequired = true;
|
||||
}
|
||||
}
|
||||
|
||||
_logger.Info<MqttRetainedMessagesManager>("Client '{0}' set retained message for topic '{1}'.", clientId, applicationMessage.Topic);
|
||||
}
|
||||
|
||||
if (!saveIsRequired)
|
||||
{
|
||||
_logger.Verbose<MqttRetainedMessagesManager>("Skipped saving retained messages because no changes were detected.");
|
||||
}
|
||||
|
||||
if (saveIsRequired && _options.Storage != null)
|
||||
{
|
||||
await _options.Storage.SaveRetainedMessagesAsync(_retainedMessages.Values.ToList());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
using System.Net;
|
||||
|
||||
namespace MQTTnet.Server
|
||||
{
|
||||
public sealed class MqttServerDefaultEndpointOptions
|
||||
{
|
||||
public bool IsEnabled { get; set; } = true;
|
||||
|
||||
public int? Port { get; set; }
|
||||
|
||||
public IPAddress BoundIPAddress { get; set; } = IPAddress.Any;
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace MQTTnet.Server
|
||||
{
|
||||
public class MqttServerOptions : IMqttServerOptions
|
||||
{
|
||||
public MqttServerDefaultEndpointOptions DefaultEndpointOptions { get; } = new MqttServerDefaultEndpointOptions();
|
||||
|
||||
public MqttServerTlsEndpointOptions TlsEndpointOptions { get; } = new MqttServerTlsEndpointOptions();
|
||||
|
||||
public int ConnectionBacklog { get; set; } = 10;
|
||||
|
||||
public TimeSpan DefaultCommunicationTimeout { get; set; } = TimeSpan.FromSeconds(15);
|
||||
|
||||
public Action<MqttConnectionValidatorContext> ConnectionValidator { get; set; }
|
||||
|
||||
public Action<MqttApplicationMessageInterceptorContext> ApplicationMessageInterceptor { get; set; }
|
||||
|
||||
public Action<MqttSubscriptionInterceptorContext> SubscriptionInterceptor { get; set; }
|
||||
|
||||
public IMqttServerStorage Storage { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace MQTTnet.Server
|
||||
{
|
||||
public static class MqttServerOptionsExtensions
|
||||
{
|
||||
public static int GetTlsEndpointPort(this IMqttServerOptions options)
|
||||
{
|
||||
if (options == null) throw new ArgumentNullException(nameof(options));
|
||||
|
||||
if (!options.TlsEndpointOptions.Port.HasValue)
|
||||
{
|
||||
return 8883;
|
||||
}
|
||||
|
||||
return options.TlsEndpointOptions.Port.Value;
|
||||
}
|
||||
|
||||
public static int GetDefaultEndpointPort(this IMqttServerOptions options)
|
||||
{
|
||||
if (options == null) throw new ArgumentNullException(nameof(options));
|
||||
|
||||
if (!options.DefaultEndpointOptions.Port.HasValue)
|
||||
{
|
||||
return 1883;
|
||||
}
|
||||
|
||||
return options.DefaultEndpointOptions.Port.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace MQTTnet.Server
|
||||
{
|
||||
public class MqttServerStartedEventArgs : EventArgs
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
using System.Net;
|
||||
|
||||
namespace MQTTnet.Server
|
||||
{
|
||||
public sealed class MqttServerTlsEndpointOptions
|
||||
{
|
||||
public bool IsEnabled { get; set; }
|
||||
|
||||
public int? Port { get; set; }
|
||||
|
||||
public byte[] Certificate { get; set; }
|
||||
|
||||
public IPAddress BoundIPAddress { get; set; } = IPAddress.Any;
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace MQTTnet.Server
|
||||
{
|
||||
public static class MqttTopicFilterComparer
|
||||
{
|
||||
private static readonly char[] TopicLevelSeparator = { '/' };
|
||||
|
||||
public static bool IsMatch(string topic, string filter)
|
||||
{
|
||||
if (topic == null) throw new ArgumentNullException(nameof(topic));
|
||||
if (filter == null) throw new ArgumentNullException(nameof(filter));
|
||||
|
||||
if (string.Equals(topic, filter, StringComparison.Ordinal))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var fragmentsTopic = topic.Split(TopicLevelSeparator, StringSplitOptions.None);
|
||||
var fragmentsFilter = filter.Split(TopicLevelSeparator, StringSplitOptions.None);
|
||||
|
||||
for (var i = 0; i < fragmentsFilter.Length; i++)
|
||||
{
|
||||
switch (fragmentsFilter[i])
|
||||
{
|
||||
case "+": continue;
|
||||
case "#" when i == fragmentsFilter.Length - 1: return true;
|
||||
}
|
||||
|
||||
if (i >= fragmentsTopic.Length)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!string.Equals(fragmentsFilter[i], fragmentsTopic[i], StringComparison.Ordinal))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return fragmentsTopic.Length <= fragmentsFilter.Length;
|
||||
}
|
||||
}
|
||||
}
|
||||
+67
-5
@@ -7,22 +7,26 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Core.Tests", "Tests
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{9248C2E1-B9D6-40BF-81EC-86004D7765B4}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Frameworks", "Frameworks", "{32A630A7-2598-41D7-B625-204CD906F5FB}"
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Source", "Source", "{32A630A7-2598-41D7-B625-204CD906F5FB}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MQTTnet.TestApp.UniversalWindows", "Tests\MQTTnet.TestApp.UniversalWindows\MQTTnet.TestApp.UniversalWindows.csproj", "{FF1F72D6-9524-4422-9497-3CC0002216ED}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.NetStandard", "Frameworks\MQTTnet.Netstandard\MQTTnet.NetStandard.csproj", "{3587E506-55A2-4EB3-99C7-DC01E42D25D2}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet", "Source\MQTTnet\MQTTnet.csproj", "{3587E506-55A2-4EB3-99C7-DC01E42D25D2}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{67C28AC1-BC3A-420A-BE9C-FA2401431CF9}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
appveyor.yml = appveyor.yml
|
||||
Build\build.ps1 = Build\build.ps1
|
||||
Build\MQTTnet.AspNetCore.nuspec = Build\MQTTnet.AspNetCore.nuspec
|
||||
Build\MQTTnet.Extensions.ManagedClient.nuspec = Build\MQTTnet.Extensions.ManagedClient.nuspec
|
||||
Build\MQTTnet.Extensions.Rpc.nuspec = Build\MQTTnet.Extensions.Rpc.nuspec
|
||||
Build\MQTTnet.nuspec = Build\MQTTnet.nuspec
|
||||
Build\upload.ps1 = Build\upload.ps1
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{B3F60ECB-45BA-4C66-8903-8BB89CA67998}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
.bettercodehub.yml = .bettercodehub.yml
|
||||
appveyor.yml = appveyor.yml
|
||||
LICENSE = LICENSE
|
||||
README.md = README.md
|
||||
EndProjectSection
|
||||
@@ -31,11 +35,17 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.TestApp.NetCore", "
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.TestApp.AspNetCore2", "Tests\MQTTnet.TestApp.AspNetCore2\MQTTnet.TestApp.AspNetCore2.csproj", "{C6FF8AEA-0855-41EC-A1F3-AC262225BAB9}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.AspNetCore", "Frameworks\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj", "{F10C4060-F7EE-4A83-919F-FF723E72F94A}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.AspNetCore", "Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj", "{F10C4060-F7EE-4A83-919F-FF723E72F94A}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Extensions", "Extensions", "{12816BCC-AF9E-44A9-9AE5-C246AF2A0587}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Extensions.Rpc", "Extensions\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj", "{C444E9C8-95FA-430E-9126-274129DE16CD}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Extensions.Rpc", "Source\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj", "{C444E9C8-95FA-430E-9126-274129DE16CD}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Benchmarks", "Tests\MQTTnet.Benchmarks\MQTTnet.Benchmarks.csproj", "{998D04DD-7CB0-45F5-A393-E2495C16399E}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Extensions.ManagedClient", "Source\MQTTnet.Extensions.ManagedClient\MQTTnet.Extensions.ManagedClient.csproj", "{C400533A-8EBA-4F0B-BF4D-295C3708604B}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.AspNetCore.Tests", "Tests\MQTTnet.AspNetCore.Tests\MQTTnet.AspNetCore.Tests.csproj", "{61B62223-F5D0-48E4-BBD6-2CBA9353CB5E}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
@@ -67,6 +77,7 @@ Global
|
||||
{A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Release|x86.Build.0 = Release|Any CPU
|
||||
{FF1F72D6-9524-4422-9497-3CC0002216ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{FF1F72D6-9524-4422-9497-3CC0002216ED}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{FF1F72D6-9524-4422-9497-3CC0002216ED}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
|
||||
{FF1F72D6-9524-4422-9497-3CC0002216ED}.Debug|ARM.ActiveCfg = Debug|ARM
|
||||
{FF1F72D6-9524-4422-9497-3CC0002216ED}.Debug|ARM.Build.0 = Debug|ARM
|
||||
{FF1F72D6-9524-4422-9497-3CC0002216ED}.Debug|ARM.Deploy.0 = Debug|ARM
|
||||
@@ -167,6 +178,54 @@ Global
|
||||
{C444E9C8-95FA-430E-9126-274129DE16CD}.Release|x64.Build.0 = Release|Any CPU
|
||||
{C444E9C8-95FA-430E-9126-274129DE16CD}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{C444E9C8-95FA-430E-9126-274129DE16CD}.Release|x86.Build.0 = Release|Any CPU
|
||||
{998D04DD-7CB0-45F5-A393-E2495C16399E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{998D04DD-7CB0-45F5-A393-E2495C16399E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{998D04DD-7CB0-45F5-A393-E2495C16399E}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{998D04DD-7CB0-45F5-A393-E2495C16399E}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{998D04DD-7CB0-45F5-A393-E2495C16399E}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{998D04DD-7CB0-45F5-A393-E2495C16399E}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{998D04DD-7CB0-45F5-A393-E2495C16399E}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{998D04DD-7CB0-45F5-A393-E2495C16399E}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{998D04DD-7CB0-45F5-A393-E2495C16399E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{998D04DD-7CB0-45F5-A393-E2495C16399E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{998D04DD-7CB0-45F5-A393-E2495C16399E}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{998D04DD-7CB0-45F5-A393-E2495C16399E}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{998D04DD-7CB0-45F5-A393-E2495C16399E}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{998D04DD-7CB0-45F5-A393-E2495C16399E}.Release|x64.Build.0 = Release|Any CPU
|
||||
{998D04DD-7CB0-45F5-A393-E2495C16399E}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{998D04DD-7CB0-45F5-A393-E2495C16399E}.Release|x86.Build.0 = Release|Any CPU
|
||||
{C400533A-8EBA-4F0B-BF4D-295C3708604B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{C400533A-8EBA-4F0B-BF4D-295C3708604B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C400533A-8EBA-4F0B-BF4D-295C3708604B}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{C400533A-8EBA-4F0B-BF4D-295C3708604B}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{C400533A-8EBA-4F0B-BF4D-295C3708604B}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{C400533A-8EBA-4F0B-BF4D-295C3708604B}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{C400533A-8EBA-4F0B-BF4D-295C3708604B}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{C400533A-8EBA-4F0B-BF4D-295C3708604B}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{C400533A-8EBA-4F0B-BF4D-295C3708604B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{C400533A-8EBA-4F0B-BF4D-295C3708604B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{C400533A-8EBA-4F0B-BF4D-295C3708604B}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{C400533A-8EBA-4F0B-BF4D-295C3708604B}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{C400533A-8EBA-4F0B-BF4D-295C3708604B}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{C400533A-8EBA-4F0B-BF4D-295C3708604B}.Release|x64.Build.0 = Release|Any CPU
|
||||
{C400533A-8EBA-4F0B-BF4D-295C3708604B}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{C400533A-8EBA-4F0B-BF4D-295C3708604B}.Release|x86.Build.0 = Release|Any CPU
|
||||
{61B62223-F5D0-48E4-BBD6-2CBA9353CB5E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{61B62223-F5D0-48E4-BBD6-2CBA9353CB5E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{61B62223-F5D0-48E4-BBD6-2CBA9353CB5E}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{61B62223-F5D0-48E4-BBD6-2CBA9353CB5E}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{61B62223-F5D0-48E4-BBD6-2CBA9353CB5E}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{61B62223-F5D0-48E4-BBD6-2CBA9353CB5E}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{61B62223-F5D0-48E4-BBD6-2CBA9353CB5E}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{61B62223-F5D0-48E4-BBD6-2CBA9353CB5E}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{61B62223-F5D0-48E4-BBD6-2CBA9353CB5E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{61B62223-F5D0-48E4-BBD6-2CBA9353CB5E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{61B62223-F5D0-48E4-BBD6-2CBA9353CB5E}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{61B62223-F5D0-48E4-BBD6-2CBA9353CB5E}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{61B62223-F5D0-48E4-BBD6-2CBA9353CB5E}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{61B62223-F5D0-48E4-BBD6-2CBA9353CB5E}.Release|x64.Build.0 = Release|Any CPU
|
||||
{61B62223-F5D0-48E4-BBD6-2CBA9353CB5E}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{61B62223-F5D0-48E4-BBD6-2CBA9353CB5E}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -179,6 +238,9 @@ Global
|
||||
{C6FF8AEA-0855-41EC-A1F3-AC262225BAB9} = {9248C2E1-B9D6-40BF-81EC-86004D7765B4}
|
||||
{F10C4060-F7EE-4A83-919F-FF723E72F94A} = {32A630A7-2598-41D7-B625-204CD906F5FB}
|
||||
{C444E9C8-95FA-430E-9126-274129DE16CD} = {12816BCC-AF9E-44A9-9AE5-C246AF2A0587}
|
||||
{998D04DD-7CB0-45F5-A393-E2495C16399E} = {9248C2E1-B9D6-40BF-81EC-86004D7765B4}
|
||||
{C400533A-8EBA-4F0B-BF4D-295C3708604B} = {12816BCC-AF9E-44A9-9AE5-C246AF2A0587}
|
||||
{61B62223-F5D0-48E4-BBD6-2CBA9353CB5E} = {9248C2E1-B9D6-40BF-81EC-86004D7765B4}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {07536672-5CBC-4BE3-ACE0-708A431A7894}
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
<p align="center">
|
||||
<img src="https://github.com/chkr1011/MQTTnet/blob/master/Images/Logo_128x128.png?raw=true" width="128">
|
||||
<br/>
|
||||
<br/>
|
||||
</p>
|
||||
|
||||
[](https://www.nuget.org/packages/MQTTnet)
|
||||
[](https://ci.appveyor.com/project/chkr1011/mqttnet/branch/develop)
|
||||
[](https://ci.appveyor.com/project/chkr1011/mqttnet)
|
||||
[](https://bettercodehub.com/)
|
||||
[](https://opencollective.com/mqttnet)
|
||||
[](https://opencollective.com/mqttnet)
|
||||
|
||||
# MQTTnet
|
||||
|
||||
@@ -18,10 +22,10 @@ MQTTnet is a high performance .NET library for MQTT based communication. It prov
|
||||
* TLS 1.2 support for client and server (but not UWP servers)
|
||||
* Extensible communication channels (i.e. In-Memory, TCP, TCP+TLS, WS)
|
||||
* Lightweight (only the low level implementation of MQTT, no overhead)
|
||||
* Performance optimized (processing ~40.000 messages / second)*
|
||||
* Performance optimized (processing ~70.000 messages / second)*
|
||||
* Interfaces included for mocking and testing
|
||||
* Access to internal trace messages
|
||||
* Unit tested (70+ tests)
|
||||
* Unit tested (~90 tests)
|
||||
|
||||
\* Tested on local machine (Intel i7 8700K) with MQTTnet client and server running in the same process using the TCP channel. The app for verification is part of this repository and stored in _/Tests/MQTTnet.TestApp.NetCore_.
|
||||
|
||||
@@ -50,14 +54,15 @@ MQTTnet is a high performance .NET library for MQTT based communication. It prov
|
||||
* .NET Standard 1.3+
|
||||
* .NET Core 1.1+
|
||||
* .NET Core App 1.1+
|
||||
* Universal Windows Platform (UWP) 10.0.10240+ (x86, x64, ARM, AnyCPU, Windows 10 IoT Core)
|
||||
* .NET Framework 4.5.2+ (x86, x64, AnyCPU)
|
||||
* Mono 5.2+
|
||||
* Universal Windows Platform (UWP) 10.0.10240+ (x86, x64, ARM, AnyCPU, Windows 10 IoT Core)
|
||||
* Xamarin.Android 7.5+
|
||||
* Xamarin.iOS 10.14+
|
||||
|
||||
## Supported MQTT versions
|
||||
|
||||
* 5.0.0 (planned)
|
||||
* 3.1.1
|
||||
* 3.1.0
|
||||
|
||||
@@ -72,21 +77,24 @@ Please find examples and the documentation at the Wiki of this repository (<http
|
||||
## Contributions
|
||||
|
||||
If you want to contribute to this project just create a pull request. But only pull requests which are matching the code style of this library will be accepted. Before creating a pull request please have a look at the library to get an overview of the required style.
|
||||
Also additions and updates in the Wiki are welcome.
|
||||
|
||||
This project also listed at Open Collective (https://opencollective.com/mqttnet).
|
||||
|
||||
## References
|
||||
|
||||
This library is used in the following projects:
|
||||
|
||||
* 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 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))
|
||||
* Wirehome (Open Source Home Automation system for .NET, <https://github.com/chkr1011/Wirehome>)
|
||||
|
||||
* Wirehome.Core (Open Source Home Automation system for .NET Core, <https://github.com/chkr1011/Wirehome.Core>)
|
||||
|
||||
If you use this library and want to see your project here please let me know.
|
||||
|
||||
## MIT License
|
||||
|
||||
Copyright (c) 2017 Christian Kratky
|
||||
Copyright (c) 2017-2018 Christian Kratky
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System.Linq;
|
||||
using MQTTnet.Server;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace MQTTnet.AspNetCore
|
||||
{
|
||||
public static class ApplicationBuilderExtensions
|
||||
{
|
||||
public static IApplicationBuilder UseMqttEndpoint(this IApplicationBuilder app, string path = "/mqtt")
|
||||
{
|
||||
app.UseWebSockets();
|
||||
app.Use(async (context, next) =>
|
||||
{
|
||||
if (!context.WebSockets.IsWebSocketRequest || context.Request.Path != path)
|
||||
{
|
||||
await next();
|
||||
return;
|
||||
}
|
||||
|
||||
string subProtocol = null;
|
||||
|
||||
if (context.Request.Headers.TryGetValue("Sec-WebSocket-Protocol", out var requestedSubProtocolValues))
|
||||
{
|
||||
subProtocol = SelectSubProtocol(requestedSubProtocolValues);
|
||||
}
|
||||
|
||||
var adapter = app.ApplicationServices.GetRequiredService<MqttWebSocketServerAdapter>();
|
||||
using (var webSocket = await context.WebSockets.AcceptWebSocketAsync(subProtocol))
|
||||
{
|
||||
var endpoint = $"{context.Connection.RemoteIpAddress}:{context.Connection.RemotePort}";
|
||||
await adapter.RunWebSocketConnectionAsync(webSocket, endpoint);
|
||||
}
|
||||
});
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
public static string SelectSubProtocol(IList<string> requestedSubProtocolValues)
|
||||
{
|
||||
// Order the protocols to also match "mqtt", "mqttv-3.1", "mqttv-3.11" etc.
|
||||
return requestedSubProtocolValues
|
||||
.OrderByDescending(p => p.Length)
|
||||
.FirstOrDefault(p => p.ToLower().StartsWith("mqtt"));
|
||||
}
|
||||
|
||||
public static IApplicationBuilder UseMqttServer(this IApplicationBuilder app, Action<IMqttServer> configure)
|
||||
{
|
||||
var server = app.ApplicationServices.GetRequiredService<IMqttServer>();
|
||||
|
||||
configure(server);
|
||||
|
||||
return app;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using MQTTnet.Server;
|
||||
using System;
|
||||
|
||||
namespace MQTTnet.AspNetCore
|
||||
{
|
||||
public class AspNetMqttServerOptionsBuilder : MqttServerOptionsBuilder
|
||||
{
|
||||
public AspNetMqttServerOptionsBuilder(IServiceProvider serviceProvider)
|
||||
{
|
||||
ServiceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
public IServiceProvider ServiceProvider { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.AspNetCore.Client.Tcp;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Serializer;
|
||||
|
||||
namespace MQTTnet.AspNetCore.Client
|
||||
{
|
||||
public class MqttClientConnectionContextFactory : IMqttClientAdapterFactory
|
||||
{
|
||||
public IMqttChannelAdapter CreateClientAdapter(IMqttClientOptions options, IMqttNetChildLogger logger)
|
||||
{
|
||||
if (options == null) throw new ArgumentNullException(nameof(options));
|
||||
|
||||
var serializer = new MqttPacketSerializer { ProtocolVersion = options.ProtocolVersion };
|
||||
|
||||
switch (options.ChannelOptions)
|
||||
{
|
||||
case MqttClientTcpOptions tcpOptions:
|
||||
{
|
||||
var endpoint = new DnsEndPoint(tcpOptions.Server, tcpOptions.GetPort());
|
||||
var tcpConnection = new TcpConnection(endpoint);
|
||||
return new MqttConnectionContext(serializer, tcpConnection);
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace MQTTnet.AspNetCore.Client.Tcp
|
||||
{
|
||||
public static class BufferExtensions
|
||||
{
|
||||
public static ArraySegment<byte> GetArray(this Memory<byte> memory)
|
||||
{
|
||||
return ((ReadOnlyMemory<byte>)memory).GetArray();
|
||||
}
|
||||
|
||||
public static ArraySegment<byte> GetArray(this ReadOnlyMemory<byte> memory)
|
||||
{
|
||||
if (!MemoryMarshal.TryGetArray(memory, out var result))
|
||||
{
|
||||
throw new InvalidOperationException("Buffer backed by array was expected");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using System.IO.Pipelines;
|
||||
|
||||
namespace MQTTnet.AspNetCore.Client.Tcp
|
||||
{
|
||||
public class DuplexPipe : IDuplexPipe
|
||||
{
|
||||
public DuplexPipe(PipeReader reader, PipeWriter writer)
|
||||
{
|
||||
Input = reader;
|
||||
Output = writer;
|
||||
}
|
||||
|
||||
public PipeReader Input { get; }
|
||||
|
||||
public PipeWriter Output { get; }
|
||||
|
||||
public static DuplexPipePair CreateConnectionPair(PipeOptions inputOptions, PipeOptions outputOptions)
|
||||
{
|
||||
var input = new Pipe(inputOptions);
|
||||
var output = new Pipe(outputOptions);
|
||||
|
||||
var transportToApplication = new DuplexPipe(output.Reader, input.Writer);
|
||||
var applicationToTransport = new DuplexPipe(input.Reader, output.Writer);
|
||||
|
||||
return new DuplexPipePair(applicationToTransport, transportToApplication);
|
||||
}
|
||||
|
||||
// This class exists to work around issues with value tuple on .NET Framework
|
||||
public readonly struct DuplexPipePair
|
||||
{
|
||||
public IDuplexPipe Transport { get; }
|
||||
public IDuplexPipe Application { get; }
|
||||
|
||||
public DuplexPipePair(IDuplexPipe transport, IDuplexPipe application)
|
||||
{
|
||||
Transport = transport;
|
||||
Application = application;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO.Pipelines;
|
||||
using System.Net.Sockets;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MQTTnet.AspNetCore.Client.Tcp
|
||||
{
|
||||
public class SocketAwaitable : ICriticalNotifyCompletion
|
||||
{
|
||||
private static readonly Action _callbackCompleted = () => { };
|
||||
|
||||
private readonly PipeScheduler _ioScheduler;
|
||||
|
||||
private Action _callback;
|
||||
private int _bytesTransferred;
|
||||
private SocketError _error;
|
||||
|
||||
public SocketAwaitable(PipeScheduler ioScheduler)
|
||||
{
|
||||
_ioScheduler = ioScheduler;
|
||||
}
|
||||
|
||||
public bool IsCompleted => ReferenceEquals(_callback, _callbackCompleted);
|
||||
|
||||
public SocketAwaitable GetAwaiter() => this;
|
||||
|
||||
public int GetResult()
|
||||
{
|
||||
Debug.Assert(ReferenceEquals(_callback, _callbackCompleted));
|
||||
|
||||
_callback = null;
|
||||
|
||||
if (_error != SocketError.Success)
|
||||
{
|
||||
throw new SocketException((int)_error);
|
||||
}
|
||||
|
||||
return _bytesTransferred;
|
||||
}
|
||||
|
||||
public void OnCompleted(Action continuation)
|
||||
{
|
||||
if (ReferenceEquals(_callback, _callbackCompleted) ||
|
||||
ReferenceEquals(Interlocked.CompareExchange(ref _callback, continuation, null), _callbackCompleted))
|
||||
{
|
||||
Task.Run(continuation);
|
||||
}
|
||||
}
|
||||
|
||||
public void UnsafeOnCompleted(Action continuation)
|
||||
{
|
||||
OnCompleted(continuation);
|
||||
}
|
||||
|
||||
public void Complete(int bytesTransferred, SocketError socketError)
|
||||
{
|
||||
_error = socketError;
|
||||
_bytesTransferred = bytesTransferred;
|
||||
var continuation = Interlocked.Exchange(ref _callback, _callbackCompleted);
|
||||
|
||||
if (continuation != null)
|
||||
{
|
||||
_ioScheduler.Schedule(state => ((Action)state)(), continuation);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using System.IO.Pipelines;
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace MQTTnet.AspNetCore.Client.Tcp
|
||||
{
|
||||
public class SocketReceiver
|
||||
{
|
||||
private readonly Socket _socket;
|
||||
private readonly SocketAsyncEventArgs _eventArgs = new SocketAsyncEventArgs();
|
||||
private readonly SocketAwaitable _awaitable;
|
||||
|
||||
public SocketReceiver(Socket socket, PipeScheduler scheduler)
|
||||
{
|
||||
_socket = socket;
|
||||
_awaitable = new SocketAwaitable(scheduler);
|
||||
_eventArgs.UserToken = _awaitable;
|
||||
_eventArgs.Completed += (_, e) => ((SocketAwaitable)e.UserToken).Complete(e.BytesTransferred, e.SocketError);
|
||||
}
|
||||
|
||||
public SocketAwaitable ReceiveAsync(Memory<byte> buffer)
|
||||
{
|
||||
#if NETCOREAPP2_1
|
||||
_eventArgs.SetBuffer(buffer);
|
||||
#else
|
||||
var segment = buffer.GetArray();
|
||||
_eventArgs.SetBuffer(segment.Array, segment.Offset, segment.Count);
|
||||
#endif
|
||||
if (!_socket.ReceiveAsync(_eventArgs))
|
||||
{
|
||||
_awaitable.Complete(_eventArgs.BytesTransferred, _eventArgs.SocketError);
|
||||
}
|
||||
|
||||
return _awaitable;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO.Pipelines;
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace MQTTnet.AspNetCore.Client.Tcp
|
||||
{
|
||||
public class SocketSender
|
||||
{
|
||||
private readonly Socket _socket;
|
||||
private readonly SocketAsyncEventArgs _eventArgs = new SocketAsyncEventArgs();
|
||||
private readonly SocketAwaitable _awaitable;
|
||||
|
||||
private List<ArraySegment<byte>> _bufferList;
|
||||
|
||||
public SocketSender(Socket socket, PipeScheduler scheduler)
|
||||
{
|
||||
_socket = socket;
|
||||
_awaitable = new SocketAwaitable(scheduler);
|
||||
_eventArgs.UserToken = _awaitable;
|
||||
_eventArgs.Completed += (_, e) => ((SocketAwaitable)e.UserToken).Complete(e.BytesTransferred, e.SocketError);
|
||||
}
|
||||
|
||||
public SocketAwaitable SendAsync(in ReadOnlySequence<byte> buffers)
|
||||
{
|
||||
if (buffers.IsSingleSegment)
|
||||
{
|
||||
return SendAsync(buffers.First);
|
||||
}
|
||||
|
||||
#if NETCOREAPP2_1
|
||||
if (!_eventArgs.MemoryBuffer.Equals(Memory<byte>.Empty))
|
||||
#else
|
||||
if (_eventArgs.Buffer != null)
|
||||
#endif
|
||||
{
|
||||
_eventArgs.SetBuffer(null, 0, 0);
|
||||
}
|
||||
|
||||
_eventArgs.BufferList = GetBufferList(buffers);
|
||||
|
||||
if (!_socket.SendAsync(_eventArgs))
|
||||
{
|
||||
_awaitable.Complete(_eventArgs.BytesTransferred, _eventArgs.SocketError);
|
||||
}
|
||||
|
||||
return _awaitable;
|
||||
}
|
||||
|
||||
private SocketAwaitable SendAsync(ReadOnlyMemory<byte> memory)
|
||||
{
|
||||
// The BufferList getter is much less expensive then the setter.
|
||||
if (_eventArgs.BufferList != null)
|
||||
{
|
||||
_eventArgs.BufferList = null;
|
||||
}
|
||||
|
||||
#if NETCOREAPP2_1
|
||||
_eventArgs.SetBuffer(MemoryMarshal.AsMemory(memory));
|
||||
#else
|
||||
var segment = memory.GetArray();
|
||||
_eventArgs.SetBuffer(segment.Array, segment.Offset, segment.Count);
|
||||
#endif
|
||||
if (!_socket.SendAsync(_eventArgs))
|
||||
{
|
||||
_awaitable.Complete(_eventArgs.BytesTransferred, _eventArgs.SocketError);
|
||||
}
|
||||
|
||||
return _awaitable;
|
||||
}
|
||||
|
||||
private List<ArraySegment<byte>> GetBufferList(in ReadOnlySequence<byte> buffer)
|
||||
{
|
||||
Debug.Assert(!buffer.IsEmpty);
|
||||
Debug.Assert(!buffer.IsSingleSegment);
|
||||
|
||||
if (_bufferList == null)
|
||||
{
|
||||
_bufferList = new List<ArraySegment<byte>>();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Buffers are pooled, so it's OK to root them until the next multi-buffer write.
|
||||
_bufferList.Clear();
|
||||
}
|
||||
|
||||
foreach (var b in buffer)
|
||||
{
|
||||
_bufferList.Add(b.GetArray());
|
||||
}
|
||||
|
||||
return _bufferList;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Pipelines;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using MQTTnet.Exceptions;
|
||||
|
||||
namespace MQTTnet.AspNetCore.Client.Tcp
|
||||
{
|
||||
public class TcpConnection : ConnectionContext
|
||||
{
|
||||
private volatile bool _aborted;
|
||||
private readonly EndPoint _endPoint;
|
||||
private SocketSender _sender;
|
||||
private SocketReceiver _receiver;
|
||||
|
||||
private Socket _socket;
|
||||
private IDuplexPipe _application;
|
||||
|
||||
public bool IsConnected { get; private set; }
|
||||
public override string ConnectionId { get; set; }
|
||||
public override IFeatureCollection Features { get; }
|
||||
public override IDictionary<object, object> Items { get; set; }
|
||||
public override IDuplexPipe Transport { get; set; }
|
||||
|
||||
public TcpConnection(EndPoint endPoint)
|
||||
{
|
||||
_endPoint = endPoint;
|
||||
}
|
||||
|
||||
public TcpConnection(Socket socket)
|
||||
{
|
||||
_socket = socket;
|
||||
_endPoint = socket.RemoteEndPoint;
|
||||
|
||||
_sender = new SocketSender(_socket, PipeScheduler.ThreadPool);
|
||||
_receiver = new SocketReceiver(_socket, PipeScheduler.ThreadPool);
|
||||
}
|
||||
|
||||
public Task DisposeAsync()
|
||||
{
|
||||
IsConnected = false;
|
||||
|
||||
Transport?.Output.Complete();
|
||||
Transport?.Input.Complete();
|
||||
|
||||
_socket?.Dispose();
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task StartAsync()
|
||||
{
|
||||
if (_socket == null)
|
||||
{
|
||||
_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
|
||||
_sender = new SocketSender(_socket, PipeScheduler.ThreadPool);
|
||||
_receiver = new SocketReceiver(_socket, PipeScheduler.ThreadPool);
|
||||
await _socket.ConnectAsync(_endPoint);
|
||||
}
|
||||
|
||||
var pair = DuplexPipe.CreateConnectionPair(PipeOptions.Default, PipeOptions.Default);
|
||||
|
||||
Transport = pair.Transport;
|
||||
_application = pair.Application;
|
||||
|
||||
_ = ExecuteAsync();
|
||||
|
||||
IsConnected = true;
|
||||
}
|
||||
|
||||
private async Task ExecuteAsync()
|
||||
{
|
||||
Exception sendError = null;
|
||||
try
|
||||
{
|
||||
// Spawn send and receive logic
|
||||
var receiveTask = DoReceive();
|
||||
var sendTask = DoSend();
|
||||
|
||||
// If the sending task completes then close the receive
|
||||
// We don't need to do this in the other direction because the kestrel
|
||||
// will trigger the output closing once the input is complete.
|
||||
if (await Task.WhenAny(receiveTask, sendTask) == sendTask)
|
||||
{
|
||||
// Tell the reader it's being aborted
|
||||
_socket.Dispose();
|
||||
}
|
||||
|
||||
// Now wait for both to complete
|
||||
await receiveTask;
|
||||
sendError = await sendTask;
|
||||
|
||||
// Dispose the socket(should noop if already called)
|
||||
_socket.Dispose();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Unexpected exception in {nameof(TcpConnection)}.{nameof(StartAsync)}: " + ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Complete the output after disposing the socket
|
||||
_application.Input.Complete(sendError);
|
||||
}
|
||||
}
|
||||
private async Task DoReceive()
|
||||
{
|
||||
Exception error = null;
|
||||
|
||||
try
|
||||
{
|
||||
await ProcessReceives();
|
||||
}
|
||||
catch (SocketException ex) when (ex.SocketErrorCode == SocketError.ConnectionReset)
|
||||
{
|
||||
error = new MqttCommunicationException(ex);
|
||||
}
|
||||
catch (SocketException ex) when (ex.SocketErrorCode == SocketError.OperationAborted ||
|
||||
ex.SocketErrorCode == SocketError.ConnectionAborted ||
|
||||
ex.SocketErrorCode == SocketError.Interrupted ||
|
||||
ex.SocketErrorCode == SocketError.InvalidArgument)
|
||||
{
|
||||
if (!_aborted)
|
||||
{
|
||||
// Calling Dispose after ReceiveAsync can cause an "InvalidArgument" error on *nix.
|
||||
error = ConnectionAborted();
|
||||
}
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
if (!_aborted)
|
||||
{
|
||||
error = ConnectionAborted();
|
||||
}
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
error = ex;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
error = new IOException(ex.Message, ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (_aborted)
|
||||
{
|
||||
error = error ?? ConnectionAborted();
|
||||
}
|
||||
|
||||
_application.Output.Complete(error);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ProcessReceives()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
// Ensure we have some reasonable amount of buffer space
|
||||
var buffer = _application.Output.GetMemory();
|
||||
|
||||
var bytesReceived = await _receiver.ReceiveAsync(buffer);
|
||||
|
||||
if (bytesReceived == 0)
|
||||
{
|
||||
// FIN
|
||||
break;
|
||||
}
|
||||
|
||||
_application.Output.Advance(bytesReceived);
|
||||
|
||||
var flushTask = _application.Output.FlushAsync();
|
||||
|
||||
if (!flushTask.IsCompleted)
|
||||
{
|
||||
await flushTask;
|
||||
}
|
||||
|
||||
var result = flushTask.GetAwaiter().GetResult();
|
||||
if (result.IsCompleted)
|
||||
{
|
||||
// Pipe consumer is shut down, do we stop writing
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Exception ConnectionAborted()
|
||||
{
|
||||
return new MqttCommunicationException("Connection Aborted");
|
||||
}
|
||||
|
||||
private async Task<Exception> DoSend()
|
||||
{
|
||||
Exception error = null;
|
||||
|
||||
try
|
||||
{
|
||||
await ProcessSends();
|
||||
}
|
||||
catch (SocketException ex) when (ex.SocketErrorCode == SocketError.OperationAborted)
|
||||
{
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
error = ex;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
error = new IOException(ex.Message, ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_aborted = true;
|
||||
_socket.Shutdown(SocketShutdown.Both);
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
private async Task ProcessSends()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
// Wait for data to write from the pipe producer
|
||||
var result = await _application.Input.ReadAsync();
|
||||
var buffer = result.Buffer;
|
||||
|
||||
if (result.IsCanceled)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
var end = buffer.End;
|
||||
var isCompleted = result.IsCompleted;
|
||||
if (!buffer.IsEmpty)
|
||||
{
|
||||
await _sender.SendAsync(buffer);
|
||||
}
|
||||
|
||||
_application.Input.AdvanceTo(end);
|
||||
|
||||
if (isCompleted)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
|
||||
namespace MQTTnet.AspNetCore
|
||||
{
|
||||
public static class ConnectionBuilderExtensions
|
||||
{
|
||||
public static IConnectionBuilder UseMqtt(this IConnectionBuilder builder)
|
||||
{
|
||||
return builder.UseConnectionHandler<MqttConnectionHandler>();
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
-6
@@ -1,14 +1,12 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<AssemblyVersion>0.0.0.0</AssemblyVersion>
|
||||
<FileVersion>0.0.0.0</FileVersion>
|
||||
<Version>0.0.0.0</Version>
|
||||
<Product />
|
||||
<Company />
|
||||
<Authors />
|
||||
<PackageId />
|
||||
<LangVersion>7.2</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
@@ -16,13 +14,13 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.0.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Connections.Abstractions" Version="2.1.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.WebSockets" Version="2.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="2.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MQTTnet.Netstandard\MQTTnet.NetStandard.csproj" />
|
||||
<ProjectReference Include="..\MQTTnet\MQTTnet.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,117 @@
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.AspNetCore.Client.Tcp;
|
||||
using MQTTnet.Packets;
|
||||
using MQTTnet.Serializer;
|
||||
using System;
|
||||
using System.IO.Pipelines;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MQTTnet.AspNetCore
|
||||
{
|
||||
public class MqttConnectionContext : IMqttChannelAdapter
|
||||
{
|
||||
public MqttConnectionContext(
|
||||
IMqttPacketSerializer packetSerializer,
|
||||
ConnectionContext connection)
|
||||
{
|
||||
PacketSerializer = packetSerializer;
|
||||
Connection = connection;
|
||||
}
|
||||
|
||||
public string Endpoint => Connection.ConnectionId;
|
||||
public ConnectionContext Connection { get; }
|
||||
public IMqttPacketSerializer PacketSerializer { get; }
|
||||
public event EventHandler ReadingPacketStarted;
|
||||
public event EventHandler ReadingPacketCompleted;
|
||||
|
||||
public Task ConnectAsync(TimeSpan timeout, CancellationToken cancellationToken)
|
||||
{
|
||||
if (Connection is TcpConnection tcp && !tcp.IsConnected)
|
||||
{
|
||||
return tcp.StartAsync();
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task DisconnectAsync(TimeSpan timeout, CancellationToken cancellationToken)
|
||||
{
|
||||
Connection.Transport.Input.Complete();
|
||||
Connection.Transport.Output.Complete();
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task<MqttBasePacket> ReceivePacketAsync(TimeSpan timeout, CancellationToken cancellationToken)
|
||||
{
|
||||
var input = Connection.Transport.Input;
|
||||
|
||||
try
|
||||
{
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
ReadResult readResult;
|
||||
var readTask = input.ReadAsync(cancellationToken);
|
||||
if (readTask.IsCompleted)
|
||||
{
|
||||
readResult = readTask.Result;
|
||||
}
|
||||
else
|
||||
{
|
||||
readResult = await readTask;
|
||||
}
|
||||
|
||||
var buffer = readResult.Buffer;
|
||||
|
||||
var consumed = buffer.Start;
|
||||
var observed = buffer.Start;
|
||||
|
||||
try
|
||||
{
|
||||
if (!buffer.IsEmpty)
|
||||
{
|
||||
if (PacketSerializer.TryDeserialize(buffer, out var packet, out consumed, out observed))
|
||||
{
|
||||
return packet;
|
||||
}
|
||||
else
|
||||
{
|
||||
// we did receive something but the message is not yet complete
|
||||
ReadingPacketStarted?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
else if (readResult.IsCompleted)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
// The buffer was sliced up to where it was consumed, so we can just advance to the start.
|
||||
// We mark examined as buffer.End so that if we didn't receive a full frame, we'll wait for more data
|
||||
// before yielding the read again.
|
||||
input.AdvanceTo(consumed, observed);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
ReadingPacketCompleted?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
return null;
|
||||
}
|
||||
|
||||
public Task SendPacketAsync(MqttBasePacket packet, CancellationToken cancellationToken)
|
||||
{
|
||||
var buffer = PacketSerializer.Serialize(packet);
|
||||
return Connection.Transport.Output.WriteAsync(buffer.AsMemory(), cancellationToken).AsTask();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
using Microsoft.AspNetCore.Connections.Features;
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.Serializer;
|
||||
using MQTTnet.Server;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MQTTnet.AspNetCore
|
||||
{
|
||||
public class MqttConnectionHandler : ConnectionHandler, IMqttServerAdapter
|
||||
{
|
||||
public event EventHandler<MqttServerAdapterClientAcceptedEventArgs> ClientAccepted;
|
||||
|
||||
public override async Task OnConnectedAsync(ConnectionContext connection)
|
||||
{
|
||||
// required for websocket transport to work
|
||||
var transferFormatFeature = connection.Features.Get<ITransferFormatFeature>();
|
||||
if (transferFormatFeature != null)
|
||||
{
|
||||
transferFormatFeature.ActiveFormat = TransferFormat.Binary;
|
||||
}
|
||||
|
||||
|
||||
var serializer = new MqttPacketSerializer();
|
||||
using (var adapter = new MqttConnectionContext(serializer, connection))
|
||||
{
|
||||
var args = new MqttServerAdapterClientAcceptedEventArgs(adapter);
|
||||
ClientAccepted?.Invoke(this, args);
|
||||
|
||||
await args.SessionTask;
|
||||
}
|
||||
}
|
||||
|
||||
public Task StartAsync(IMqttServerOptions options)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task StopAsync()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -13,7 +13,8 @@ namespace MQTTnet.AspNetCore
|
||||
{
|
||||
private readonly IMqttServerOptions _options;
|
||||
|
||||
public MqttHostedServer(IMqttServerOptions options, IEnumerable<IMqttServerAdapter> adapters, IMqttNetLogger logger) : base(adapters, logger)
|
||||
public MqttHostedServer(IMqttServerOptions options, IEnumerable<IMqttServerAdapter> adapters, IMqttNetLogger logger)
|
||||
: base(adapters, logger.CreateChildLogger(nameof(MqttHostedServer)))
|
||||
{
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
+10
-6
@@ -3,12 +3,13 @@ using System.Net.WebSockets;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Implementations;
|
||||
using MQTTnet.Serializer;
|
||||
using MQTTnet.Server;
|
||||
|
||||
namespace MQTTnet.AspNetCore
|
||||
{
|
||||
public sealed class MqttWebSocketServerAdapter : IMqttServerAdapter, IDisposable
|
||||
public class MqttWebSocketServerAdapter : IMqttServerAdapter
|
||||
{
|
||||
public event EventHandler<MqttServerAdapterClientAcceptedEventArgs> ClientAccepted;
|
||||
|
||||
@@ -22,21 +23,24 @@ namespace MQTTnet.AspNetCore
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task AcceptWebSocketAsync(WebSocket webSocket)
|
||||
public async Task RunWebSocketConnectionAsync(WebSocket webSocket, string endpoint)
|
||||
{
|
||||
if (webSocket == null) throw new ArgumentNullException(nameof(webSocket));
|
||||
|
||||
var channel = new MqttWebSocketServerChannel(webSocket);
|
||||
var clientAdapter = new MqttChannelAdapter(channel, new MqttPacketSerializer(), new MqttNetLogger());
|
||||
var clientAdapter = new MqttChannelAdapter(new MqttWebSocketChannel(webSocket, endpoint), new MqttPacketSerializer(), new MqttNetLogger().CreateChildLogger(nameof(MqttWebSocketServerAdapter)));
|
||||
|
||||
var eventArgs = new MqttServerAdapterClientAcceptedEventArgs(clientAdapter);
|
||||
ClientAccepted?.Invoke(this, eventArgs);
|
||||
return eventArgs.SessionTask;
|
||||
|
||||
if (eventArgs.SessionTask != null)
|
||||
{
|
||||
await eventArgs.SessionTask.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
StopAsync();
|
||||
StopAsync().GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.Exceptions;
|
||||
using MQTTnet.Packets;
|
||||
using MQTTnet.Serializer;
|
||||
|
||||
namespace MQTTnet.AspNetCore
|
||||
{
|
||||
public static class ReaderExtensions
|
||||
{
|
||||
public static bool TryDeserialize(this IMqttPacketSerializer serializer, in ReadOnlySequence<byte> input, out MqttBasePacket packet, out SequencePosition consumed, out SequencePosition observed)
|
||||
{
|
||||
packet = null;
|
||||
consumed = input.Start;
|
||||
observed = input.End;
|
||||
var copy = input;
|
||||
if (copy.Length < 2)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var fixedheader = copy.First.Span[0];
|
||||
if (!TryReadBodyLength(ref copy, out var bodyLength))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (copy.Length < bodyLength)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var bodySlice = copy.Slice(0, bodyLength);
|
||||
var buffer = bodySlice.GetArray();
|
||||
packet = serializer.Deserialize(new ReceivedMqttPacket(fixedheader, new MqttPacketBodyReader(buffer, 0, buffer.Length)));
|
||||
consumed = bodySlice.End;
|
||||
observed = bodySlice.End;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static byte[] GetArray(this in ReadOnlySequence<byte> input)
|
||||
{
|
||||
if (input.IsSingleSegment)
|
||||
{
|
||||
return input.First.Span.ToArray();
|
||||
}
|
||||
|
||||
// Should be rare
|
||||
return input.ToArray();
|
||||
}
|
||||
|
||||
private static bool TryReadBodyLength(ref ReadOnlySequence<byte> input, out int result)
|
||||
{
|
||||
// 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;
|
||||
result = 0;
|
||||
|
||||
var temp = input.Slice(0, Math.Min(5, input.Length)).GetArray();
|
||||
|
||||
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.AsSpan(1, index).ToArray())}).");
|
||||
}
|
||||
|
||||
multiplier *= 128;
|
||||
} while ((encodedByte & 128) != 0);
|
||||
|
||||
input = input.Slice(index);
|
||||
|
||||
result = value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
using System;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Server;
|
||||
using MQTTnet.Implementations;
|
||||
|
||||
namespace MQTTnet.AspNetCore
|
||||
{
|
||||
public static class ServiceCollectionExtensions
|
||||
{
|
||||
public static IServiceCollection AddHostedMqttServer(this IServiceCollection services, IMqttServerOptions options)
|
||||
{
|
||||
if (options == null) throw new ArgumentNullException(nameof(options));
|
||||
|
||||
services.AddSingleton(options);
|
||||
|
||||
services.AddHostedMqttServer();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddHostedMqttServer(this IServiceCollection services, Action<MqttServerOptionsBuilder> configure)
|
||||
{
|
||||
services.AddSingleton<IMqttServerOptions>(s => {
|
||||
var builder = new MqttServerOptionsBuilder();
|
||||
configure(builder);
|
||||
return builder.Build();
|
||||
});
|
||||
|
||||
services.AddHostedMqttServer();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddHostedMqttServerWithServices(this IServiceCollection services, Action<AspNetMqttServerOptionsBuilder> configure)
|
||||
{
|
||||
services.AddSingleton<IMqttServerOptions>(s => {
|
||||
var builder = new AspNetMqttServerOptionsBuilder(s);
|
||||
configure(builder);
|
||||
return builder.Build();
|
||||
});
|
||||
|
||||
services.AddHostedMqttServer();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddHostedMqttServer<TOptions>(this IServiceCollection services)
|
||||
where TOptions : class, IMqttServerOptions
|
||||
{
|
||||
services.AddSingleton<IMqttServerOptions, TOptions>();
|
||||
|
||||
services.AddHostedMqttServer();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
private static IServiceCollection AddHostedMqttServer(this IServiceCollection services)
|
||||
{
|
||||
var logger = new MqttNetLogger();
|
||||
var childLogger = logger.CreateChildLogger();
|
||||
|
||||
services.AddSingleton<IMqttNetLogger>(logger);
|
||||
services.AddSingleton(childLogger);
|
||||
services.AddSingleton<MqttHostedServer>();
|
||||
services.AddSingleton<IHostedService>(s => s.GetService<MqttHostedServer>());
|
||||
services.AddSingleton<IMqttServer>(s => s.GetService<MqttHostedServer>());
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddMqttWebSocketServerAdapter(this IServiceCollection services)
|
||||
{
|
||||
services.AddSingleton<MqttWebSocketServerAdapter>();
|
||||
services.AddSingleton<IMqttServerAdapter>(s => s.GetService<MqttWebSocketServerAdapter>());
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddMqttTcpServerAdapter(this IServiceCollection services)
|
||||
{
|
||||
services.AddSingleton<MqttTcpServerAdapter>();
|
||||
services.AddSingleton<IMqttServerAdapter>(s => s.GetService<MqttTcpServerAdapter>());
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddMqttConnectionHandler(this IServiceCollection services)
|
||||
{
|
||||
services.AddSingleton<MqttConnectionHandler>();
|
||||
services.AddSingleton<IMqttServerAdapter>(s => s.GetService<MqttConnectionHandler>());
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -1,16 +1,16 @@
|
||||
using System;
|
||||
|
||||
namespace MQTTnet.ManagedClient
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public class ApplicationMessageProcessedEventArgs : EventArgs
|
||||
{
|
||||
public ApplicationMessageProcessedEventArgs(MqttApplicationMessage applicationMessage, Exception exception)
|
||||
public ApplicationMessageProcessedEventArgs(ManagedMqttApplicationMessage applicationMessage, Exception exception)
|
||||
{
|
||||
ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
|
||||
Exception = exception;
|
||||
}
|
||||
|
||||
public MqttApplicationMessage ApplicationMessage { get; }
|
||||
public ManagedMqttApplicationMessage ApplicationMessage { get; }
|
||||
public Exception Exception { get; }
|
||||
|
||||
public bool HasFailed => Exception != null;
|
||||
+7
-1
@@ -3,22 +3,28 @@ using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Client;
|
||||
|
||||
namespace MQTTnet.ManagedClient
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public interface IManagedMqttClient : IApplicationMessageReceiver, IApplicationMessagePublisher, IDisposable
|
||||
{
|
||||
bool IsStarted { get; }
|
||||
bool IsConnected { get; }
|
||||
int PendingApplicationMessagesCount { get; }
|
||||
|
||||
event EventHandler<MqttClientConnectedEventArgs> Connected;
|
||||
event EventHandler<MqttClientDisconnectedEventArgs> Disconnected;
|
||||
|
||||
event EventHandler<ApplicationMessageProcessedEventArgs> ApplicationMessageProcessed;
|
||||
|
||||
event EventHandler<MqttManagedProcessFailedEventArgs> ConnectingFailed;
|
||||
event EventHandler<MqttManagedProcessFailedEventArgs> SynchronizingSubscriptionsFailed;
|
||||
|
||||
Task StartAsync(IManagedMqttClientOptions options);
|
||||
Task StopAsync();
|
||||
|
||||
Task SubscribeAsync(IEnumerable<TopicFilter> topicFilters);
|
||||
Task UnsubscribeAsync(IEnumerable<string> topics);
|
||||
|
||||
Task PublishAsync(ManagedMqttApplicationMessage applicationMessages);
|
||||
}
|
||||
}
|
||||
+3
-1
@@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using MQTTnet.Client;
|
||||
|
||||
namespace MQTTnet.ManagedClient
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public interface IManagedMqttClientOptions
|
||||
{
|
||||
@@ -9,6 +9,8 @@ namespace MQTTnet.ManagedClient
|
||||
|
||||
TimeSpan AutoReconnectDelay { get; }
|
||||
|
||||
TimeSpan ConnectionCheckInterval { get; }
|
||||
|
||||
IManagedMqttClientStorage Storage { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public interface IManagedMqttClientStorage
|
||||
{
|
||||
Task SaveQueuedMessagesAsync(IList<ManagedMqttApplicationMessage> messages);
|
||||
|
||||
Task<IList<ManagedMqttApplicationMessage>> LoadQueuedMessagesAsync();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard1.3;netstandard2.0;net452;net461</TargetFrameworks>
|
||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core'">$(TargetFrameworks);uap10.0</TargetFrameworks>
|
||||
<Product />
|
||||
<Company />
|
||||
<Authors />
|
||||
<PackageId />
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0'">
|
||||
<CopyLocalLockFileAssemblies>false</CopyLocalLockFileAssemblies>
|
||||
<NugetTargetMoniker>UAP,Version=v10.0</NugetTargetMoniker>
|
||||
<TargetPlatformIdentifier>UAP</TargetPlatformIdentifier>
|
||||
<TargetPlatformVersion>10.0.17134.0</TargetPlatformVersion>
|
||||
<TargetPlatformMinVersion>10.0.10240.0</TargetPlatformMinVersion>
|
||||
<TargetFrameworkIdentifier>.NETCore</TargetFrameworkIdentifier>
|
||||
<TargetFrameworkVersion>v5.0</TargetFrameworkVersion>
|
||||
<DefineConstants>$(DefineConstants);WINDOWS_UWP</DefineConstants>
|
||||
<DefaultLanguage>en</DefaultLanguage>
|
||||
<LanguageTargets>$(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets</LanguageTargets>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MQTTnet\MQTTnet.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public class ManagedMqttApplicationMessage
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
|
||||
public MqttApplicationMessage ApplicationMessage { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
using System;
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public class ManagedMqttApplicationMessageBuilder
|
||||
{
|
||||
private Guid _id = Guid.NewGuid();
|
||||
private MqttApplicationMessage _applicationMessage;
|
||||
|
||||
public ManagedMqttApplicationMessageBuilder WithId(Guid id)
|
||||
{
|
||||
_id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ManagedMqttApplicationMessageBuilder WithApplicationMessage(MqttApplicationMessage applicationMessage)
|
||||
{
|
||||
_applicationMessage = applicationMessage;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ManagedMqttApplicationMessageBuilder WithApplicationMessage(Action<MqttApplicationMessageBuilder> builder)
|
||||
{
|
||||
if (builder == null) throw new ArgumentNullException(nameof(builder));
|
||||
|
||||
var internalBuilder = new MqttApplicationMessageBuilder();
|
||||
builder(internalBuilder);
|
||||
|
||||
_applicationMessage = internalBuilder.Build();
|
||||
return this;
|
||||
}
|
||||
|
||||
public ManagedMqttApplicationMessage Build()
|
||||
{
|
||||
if (_applicationMessage == null)
|
||||
{
|
||||
throw new InvalidOperationException("The ApplicationMessage cannot be null.");
|
||||
}
|
||||
|
||||
return new ManagedMqttApplicationMessage
|
||||
{
|
||||
Id = _id,
|
||||
ApplicationMessage = _applicationMessage
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
+79
-86
@@ -9,17 +9,16 @@ using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Exceptions;
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
namespace MQTTnet.ManagedClient
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public class ManagedMqttClient : IManagedMqttClient
|
||||
{
|
||||
private readonly BlockingCollection<MqttApplicationMessage> _messageQueue = new BlockingCollection<MqttApplicationMessage>();
|
||||
private readonly BlockingCollection<ManagedMqttApplicationMessage> _messageQueue = new BlockingCollection<ManagedMqttApplicationMessage>();
|
||||
private readonly Dictionary<string, MqttQualityOfServiceLevel> _subscriptions = new Dictionary<string, MqttQualityOfServiceLevel>();
|
||||
private readonly SemaphoreSlim _subscriptionsSemaphore = new SemaphoreSlim(1, 1);
|
||||
private readonly List<string> _unsubscriptions = new List<string>();
|
||||
private readonly HashSet<string> _unsubscriptions = new HashSet<string>();
|
||||
|
||||
private readonly IMqttClient _mqttClient;
|
||||
private readonly IMqttNetLogger _logger;
|
||||
private readonly IMqttNetChildLogger _logger;
|
||||
|
||||
private CancellationTokenSource _connectionCancellationToken;
|
||||
private CancellationTokenSource _publishingCancellationToken;
|
||||
@@ -29,24 +28,31 @@ namespace MQTTnet.ManagedClient
|
||||
|
||||
private bool _subscriptionsNotPushed;
|
||||
|
||||
public ManagedMqttClient(IMqttClient mqttClient, IMqttNetLogger logger)
|
||||
public ManagedMqttClient(IMqttClient mqttClient, IMqttNetChildLogger logger)
|
||||
{
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
if (logger == null) throw new ArgumentNullException(nameof(logger));
|
||||
|
||||
_mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
|
||||
|
||||
_mqttClient.Connected += OnConnected;
|
||||
_mqttClient.Disconnected += OnDisconnected;
|
||||
_mqttClient.ApplicationMessageReceived += OnApplicationMessageReceived;
|
||||
|
||||
_logger = logger.CreateChildLogger(nameof(ManagedMqttClient));
|
||||
}
|
||||
|
||||
public bool IsConnected => _mqttClient.IsConnected;
|
||||
public bool IsStarted => _connectionCancellationToken != null;
|
||||
public int PendingApplicationMessagesCount => _messageQueue.Count;
|
||||
|
||||
public event EventHandler<MqttClientConnectedEventArgs> Connected;
|
||||
public event EventHandler<MqttClientDisconnectedEventArgs> Disconnected;
|
||||
|
||||
public event EventHandler<MqttApplicationMessageReceivedEventArgs> ApplicationMessageReceived;
|
||||
public event EventHandler<ApplicationMessageProcessedEventArgs> ApplicationMessageProcessed;
|
||||
public event EventHandler SynchronizingSubscriptionsFailed;
|
||||
|
||||
public event EventHandler<MqttManagedProcessFailedEventArgs> ConnectingFailed;
|
||||
public event EventHandler<MqttManagedProcessFailedEventArgs> SynchronizingSubscriptionsFailed;
|
||||
|
||||
public async Task StartAsync(IManagedMqttClientOptions options)
|
||||
{
|
||||
@@ -65,16 +71,21 @@ namespace MQTTnet.ManagedClient
|
||||
if (_options.Storage != null)
|
||||
{
|
||||
_storageManager = new ManagedMqttClientStorageManager(_options.Storage);
|
||||
await _storageManager.LoadQueuedMessagesAsync().ConfigureAwait(false);
|
||||
var messages = await _storageManager.LoadQueuedMessagesAsync().ConfigureAwait(false);
|
||||
|
||||
foreach (var message in messages)
|
||||
{
|
||||
_messageQueue.Add(message);
|
||||
}
|
||||
}
|
||||
|
||||
_connectionCancellationToken = new CancellationTokenSource();
|
||||
|
||||
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
|
||||
Task.Run(async () => await MaintainConnectionAsync(_connectionCancellationToken.Token).ConfigureAwait(false), _connectionCancellationToken.Token).ConfigureAwait(false);
|
||||
Task.Run(() => MaintainConnectionAsync(_connectionCancellationToken.Token), _connectionCancellationToken.Token);
|
||||
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
|
||||
|
||||
_logger.Info<ManagedMqttClient>("Started");
|
||||
_logger.Info("Started");
|
||||
}
|
||||
|
||||
public Task StopAsync()
|
||||
@@ -90,27 +101,30 @@ namespace MQTTnet.ManagedClient
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public async Task PublishAsync(IEnumerable<MqttApplicationMessage> applicationMessages)
|
||||
public Task PublishAsync(MqttApplicationMessage applicationMessage)
|
||||
{
|
||||
if (applicationMessages == null) throw new ArgumentNullException(nameof(applicationMessages));
|
||||
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
|
||||
|
||||
foreach (var applicationMessage in applicationMessages)
|
||||
{
|
||||
if (_storageManager != null)
|
||||
{
|
||||
await _storageManager.AddAsync(applicationMessage).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
_messageQueue.Add(applicationMessage);
|
||||
}
|
||||
return PublishAsync(new ManagedMqttApplicationMessageBuilder().WithApplicationMessage(applicationMessage).Build());
|
||||
}
|
||||
|
||||
public async Task SubscribeAsync(IEnumerable<TopicFilter> topicFilters)
|
||||
public async Task PublishAsync(ManagedMqttApplicationMessage applicationMessage)
|
||||
{
|
||||
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
|
||||
|
||||
if (_storageManager != null)
|
||||
{
|
||||
await _storageManager.AddAsync(applicationMessage).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
_messageQueue.Add(applicationMessage);
|
||||
}
|
||||
|
||||
public Task SubscribeAsync(IEnumerable<TopicFilter> topicFilters)
|
||||
{
|
||||
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
|
||||
|
||||
await _subscriptionsSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
lock (_subscriptions)
|
||||
{
|
||||
foreach (var topicFilter in topicFilters)
|
||||
{
|
||||
@@ -118,16 +132,15 @@ namespace MQTTnet.ManagedClient
|
||||
_subscriptionsNotPushed = true;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_subscriptionsSemaphore.Release();
|
||||
}
|
||||
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public async Task UnsubscribeAsync(IEnumerable<string> topics)
|
||||
public Task UnsubscribeAsync(IEnumerable<string> topics)
|
||||
{
|
||||
await _subscriptionsSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
if (topics == null) throw new ArgumentNullException(nameof(topics));
|
||||
|
||||
lock (_subscriptions)
|
||||
{
|
||||
foreach (var topic in topics)
|
||||
{
|
||||
@@ -138,16 +151,13 @@ namespace MQTTnet.ManagedClient
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_subscriptionsSemaphore.Release();
|
||||
}
|
||||
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_messageQueue?.Dispose();
|
||||
_subscriptionsSemaphore?.Dispose();
|
||||
_connectionCancellationToken?.Dispose();
|
||||
_publishingCancellationToken?.Dispose();
|
||||
}
|
||||
@@ -166,12 +176,12 @@ namespace MQTTnet.ManagedClient
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.Error<ManagedMqttClient>(exception, "Unhandled exception while maintaining connection.");
|
||||
_logger.Error(exception, "Unhandled exception while maintaining connection.");
|
||||
}
|
||||
finally
|
||||
{
|
||||
await _mqttClient.DisconnectAsync().ConfigureAwait(false);
|
||||
_logger.Info<ManagedMqttClient>("Stopped");
|
||||
_logger.Info("Stopped");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,16 +200,13 @@ namespace MQTTnet.ManagedClient
|
||||
if (connectionState == ReconnectionResult.Reconnected || _subscriptionsNotPushed)
|
||||
{
|
||||
await SynchronizeSubscriptionsAsync().ConfigureAwait(false);
|
||||
|
||||
StartPublishing();
|
||||
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (connectionState == ReconnectionResult.StillConnected)
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false);
|
||||
await Task.Delay(_options.ConnectionCheckInterval, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
@@ -207,19 +214,19 @@ namespace MQTTnet.ManagedClient
|
||||
}
|
||||
catch (MqttCommunicationException exception)
|
||||
{
|
||||
_logger.Warning<ManagedMqttClient>(exception, "Communication exception while maintaining connection.");
|
||||
_logger.Warning(exception, "Communication exception while maintaining connection.");
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.Error<ManagedMqttClient>(exception, "Unhandled exception while maintaining connection.");
|
||||
_logger.Error(exception, "Unhandled exception while maintaining connection.");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task PublishQueuedMessagesAsync(CancellationToken cancellationToken)
|
||||
private void PublishQueuedMessages(CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
while (!cancellationToken.IsCancellationRequested && _mqttClient.IsConnected)
|
||||
{
|
||||
var message = _messageQueue.Take(cancellationToken);
|
||||
if (message == null)
|
||||
@@ -227,12 +234,9 @@ namespace MQTTnet.ManagedClient
|
||||
continue;
|
||||
}
|
||||
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
await TryPublishQueuedMessageAsync(message).ConfigureAwait(false);
|
||||
TryPublishQueuedMessage(message);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
@@ -240,33 +244,29 @@ namespace MQTTnet.ManagedClient
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.Error<ManagedMqttClient>(exception, "Unhandled exception while publishing queued application messages.");
|
||||
_logger.Error(exception, "Unhandled exception while publishing queued application messages.");
|
||||
}
|
||||
finally
|
||||
{
|
||||
_logger.Verbose<ManagedMqttClient>("Stopped publishing messages.");
|
||||
_logger.Verbose("Stopped publishing messages.");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task TryPublishQueuedMessageAsync(MqttApplicationMessage message)
|
||||
private void TryPublishQueuedMessage(ManagedMqttApplicationMessage message)
|
||||
{
|
||||
Exception transmitException = null;
|
||||
try
|
||||
{
|
||||
await _mqttClient.PublishAsync(message).ConfigureAwait(false);
|
||||
|
||||
if (_storageManager != null)
|
||||
{
|
||||
await _storageManager.RemoveAsync(message).ConfigureAwait(false);
|
||||
}
|
||||
_mqttClient.PublishAsync(message.ApplicationMessage).GetAwaiter().GetResult();
|
||||
_storageManager?.RemoveAsync(message).GetAwaiter().GetResult();
|
||||
}
|
||||
catch (MqttCommunicationException exception)
|
||||
{
|
||||
transmitException = exception;
|
||||
|
||||
_logger.Warning<ManagedMqttClient>(exception, "Publishing application message failed.");
|
||||
_logger.Warning(exception, $"Publishing application ({message.Id}) message failed.");
|
||||
|
||||
if (message.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce)
|
||||
if (message.ApplicationMessage.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce)
|
||||
{
|
||||
_messageQueue.Add(message);
|
||||
}
|
||||
@@ -274,7 +274,7 @@ namespace MQTTnet.ManagedClient
|
||||
catch (Exception exception)
|
||||
{
|
||||
transmitException = exception;
|
||||
_logger.Error<ManagedMqttClient>(exception, "Unhandled exception while publishing queued application message.");
|
||||
_logger.Error(exception, $"Unhandled exception while publishing application message ({message.Id}).");
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -284,26 +284,21 @@ namespace MQTTnet.ManagedClient
|
||||
|
||||
private async Task SynchronizeSubscriptionsAsync()
|
||||
{
|
||||
_logger.Info<ManagedMqttClient>(nameof(ManagedMqttClient), "Synchronizing subscriptions");
|
||||
_logger.Info(nameof(ManagedMqttClient), "Synchronizing subscriptions");
|
||||
|
||||
List<TopicFilter> subscriptions;
|
||||
List<string> unsubscriptions;
|
||||
HashSet<string> unsubscriptions;
|
||||
|
||||
await _subscriptionsSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
lock (_subscriptions)
|
||||
{
|
||||
subscriptions = _subscriptions.Select(i => new TopicFilter(i.Key, i.Value)).ToList();
|
||||
|
||||
unsubscriptions = new List<string>(_unsubscriptions);
|
||||
unsubscriptions = new HashSet<string>(_unsubscriptions);
|
||||
_unsubscriptions.Clear();
|
||||
|
||||
_subscriptionsNotPushed = false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_subscriptionsSemaphore.Release();
|
||||
}
|
||||
|
||||
|
||||
if (!subscriptions.Any() && !unsubscriptions.Any())
|
||||
{
|
||||
return;
|
||||
@@ -311,22 +306,22 @@ namespace MQTTnet.ManagedClient
|
||||
|
||||
try
|
||||
{
|
||||
if (subscriptions.Any())
|
||||
{
|
||||
await _mqttClient.SubscribeAsync(subscriptions).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (unsubscriptions.Any())
|
||||
{
|
||||
await _mqttClient.UnsubscribeAsync(unsubscriptions).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (subscriptions.Any())
|
||||
{
|
||||
await _mqttClient.SubscribeAsync(subscriptions).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.Warning<ManagedMqttClient>(exception, "Synchronizing subscriptions failed.");
|
||||
_logger.Warning(exception, "Synchronizing subscriptions failed.");
|
||||
_subscriptionsNotPushed = true;
|
||||
|
||||
SynchronizingSubscriptionsFailed?.Invoke(this, EventArgs.Empty);
|
||||
SynchronizingSubscriptionsFailed?.Invoke(this, new MqttManagedProcessFailedEventArgs(exception));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -342,8 +337,9 @@ namespace MQTTnet.ManagedClient
|
||||
await _mqttClient.ConnectAsync(_options.ClientOptions).ConfigureAwait(false);
|
||||
return ReconnectionResult.Reconnected;
|
||||
}
|
||||
catch (Exception)
|
||||
catch (Exception exception)
|
||||
{
|
||||
ConnectingFailed?.Invoke(this, new MqttManagedProcessFailedEventArgs(exception));
|
||||
return ReconnectionResult.NotConnected;
|
||||
}
|
||||
}
|
||||
@@ -371,12 +367,9 @@ namespace MQTTnet.ManagedClient
|
||||
}
|
||||
|
||||
var cts = new CancellationTokenSource();
|
||||
|
||||
_publishingCancellationToken = cts;
|
||||
|
||||
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
|
||||
Task.Run(async () => await PublishQueuedMessagesAsync(cts.Token).ConfigureAwait(false), cts.Token).ConfigureAwait(false);
|
||||
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
|
||||
Task.Factory.StartNew(() => PublishQueuedMessages(cts.Token), cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
|
||||
}
|
||||
|
||||
private void StopPublishing()
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
namespace MQTTnet.ManagedClient
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public static class ManagedMqttClientExtensions
|
||||
{
|
||||
+3
-1
@@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using MQTTnet.Client;
|
||||
|
||||
namespace MQTTnet.ManagedClient
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public class ManagedMqttClientOptions : IManagedMqttClientOptions
|
||||
{
|
||||
@@ -9,6 +9,8 @@ namespace MQTTnet.ManagedClient
|
||||
|
||||
public TimeSpan AutoReconnectDelay { get; set; } = TimeSpan.FromSeconds(5);
|
||||
|
||||
public TimeSpan ConnectionCheckInterval { get; set; } = TimeSpan.FromSeconds(1);
|
||||
|
||||
public IManagedMqttClientStorage Storage { get; set; }
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using MQTTnet.Client;
|
||||
|
||||
namespace MQTTnet.ManagedClient
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public class ManagedMqttClientOptionsBuilder
|
||||
{
|
||||
@@ -0,0 +1,58 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Internal;
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public class ManagedMqttClientStorageManager
|
||||
{
|
||||
private readonly List<ManagedMqttApplicationMessage> _messages = new List<ManagedMqttApplicationMessage>();
|
||||
private readonly AsyncLock _messagesLock = new AsyncLock();
|
||||
|
||||
private readonly IManagedMqttClientStorage _storage;
|
||||
|
||||
public ManagedMqttClientStorageManager(IManagedMqttClientStorage storage)
|
||||
{
|
||||
_storage = storage ?? throw new ArgumentNullException(nameof(storage));
|
||||
}
|
||||
|
||||
public async Task<List<ManagedMqttApplicationMessage>> LoadQueuedMessagesAsync()
|
||||
{
|
||||
var loadedMessages = await _storage.LoadQueuedMessagesAsync().ConfigureAwait(false);
|
||||
_messages.AddRange(loadedMessages);
|
||||
|
||||
return _messages;
|
||||
}
|
||||
|
||||
public async Task AddAsync(ManagedMqttApplicationMessage applicationMessage)
|
||||
{
|
||||
using (await _messagesLock.LockAsync(CancellationToken.None).ConfigureAwait(false))
|
||||
{
|
||||
_messages.Add(applicationMessage);
|
||||
await SaveAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task RemoveAsync(ManagedMqttApplicationMessage applicationMessage)
|
||||
{
|
||||
using (await _messagesLock.LockAsync(CancellationToken.None).ConfigureAwait(false))
|
||||
{
|
||||
var index = _messages.IndexOf(applicationMessage);
|
||||
if (index == -1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_messages.RemoveAt(index);
|
||||
await SaveAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private Task SaveAsync()
|
||||
{
|
||||
return _storage.SaveQueuedMessagesAsync(_messages);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Diagnostics;
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public static class MqttFactoryExtensions
|
||||
{
|
||||
public static IManagedMqttClient CreateManagedMqttClient(this IMqttClientFactory factory)
|
||||
{
|
||||
if (factory == null) throw new ArgumentNullException(nameof(factory));
|
||||
|
||||
return new ManagedMqttClient(factory.CreateMqttClient(), new MqttNetLogger().CreateChildLogger());
|
||||
}
|
||||
|
||||
public static IManagedMqttClient CreateManagedMqttClient(this IMqttClientFactory factory, IMqttNetLogger logger)
|
||||
{
|
||||
if (factory == null) throw new ArgumentNullException(nameof(factory));
|
||||
if (logger == null) throw new ArgumentNullException(nameof(logger));
|
||||
|
||||
return new ManagedMqttClient(factory.CreateMqttClient(), logger.CreateChildLogger());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public class MqttManagedProcessFailedEventArgs : EventArgs
|
||||
{
|
||||
public MqttManagedProcessFailedEventArgs(Exception exception)
|
||||
{
|
||||
Exception = exception;
|
||||
}
|
||||
|
||||
public Exception Exception { get; }
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
namespace MQTTnet.ManagedClient
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public enum ReconnectionResult
|
||||
{
|
||||
+5
-7
@@ -1,21 +1,19 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard1.3;netstandard2.0;net452;net461;uap10.0</TargetFrameworks>
|
||||
<AssemblyVersion>0.0.0.0</AssemblyVersion>
|
||||
<FileVersion>0.0.0.0</FileVersion>
|
||||
<TargetFrameworks>netstandard1.3;netstandard2.0;net452;net461</TargetFrameworks>
|
||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core'">$(TargetFrameworks);uap10.0</TargetFrameworks>
|
||||
<Product />
|
||||
<Company />
|
||||
<Authors />
|
||||
<PackageId />
|
||||
<Version>0.0.0.0</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0'">
|
||||
<CopyLocalLockFileAssemblies>false</CopyLocalLockFileAssemblies>
|
||||
<NugetTargetMoniker>UAP,Version=v10.0</NugetTargetMoniker>
|
||||
<TargetPlatformIdentifier>UAP</TargetPlatformIdentifier>
|
||||
<TargetPlatformVersion>10.0.16299.0</TargetPlatformVersion>
|
||||
<TargetPlatformVersion>10.0.17134.0</TargetPlatformVersion>
|
||||
<TargetPlatformMinVersion>10.0.10240.0</TargetPlatformMinVersion>
|
||||
<TargetFrameworkIdentifier>.NETCore</TargetFrameworkIdentifier>
|
||||
<TargetFrameworkVersion>v5.0</TargetFrameworkVersion>
|
||||
@@ -25,7 +23,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Frameworks\MQTTnet.Netstandard\MQTTnet.NetStandard.csproj" />
|
||||
<ProjectReference Include="..\MQTTnet\MQTTnet.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,130 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Exceptions;
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
namespace MQTTnet.Extensions.Rpc
|
||||
{
|
||||
public class MqttRpcClient : IDisposable
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, TaskCompletionSource<byte[]>> _waitingCalls = new ConcurrentDictionary<string, TaskCompletionSource<byte[]>>();
|
||||
private readonly IMqttClient _mqttClient;
|
||||
|
||||
public MqttRpcClient(IMqttClient mqttClient)
|
||||
{
|
||||
_mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
|
||||
|
||||
_mqttClient.ApplicationMessageReceived += OnApplicationMessageReceived;
|
||||
}
|
||||
|
||||
public Task<byte[]> ExecuteAsync(TimeSpan timeout, string methodName, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel)
|
||||
{
|
||||
return ExecuteAsync(timeout, methodName, Encoding.UTF8.GetBytes(payload), qualityOfServiceLevel, CancellationToken.None);
|
||||
}
|
||||
|
||||
public Task<byte[]> ExecuteAsync(TimeSpan timeout, string methodName, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel, CancellationToken cancellationToken)
|
||||
{
|
||||
return ExecuteAsync(timeout, methodName, Encoding.UTF8.GetBytes(payload), qualityOfServiceLevel, cancellationToken);
|
||||
}
|
||||
|
||||
public Task<byte[]> ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel)
|
||||
{
|
||||
return ExecuteAsync(timeout, methodName, payload, qualityOfServiceLevel, CancellationToken.None);
|
||||
}
|
||||
|
||||
public async Task<byte[]> ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, CancellationToken cancellationToken)
|
||||
{
|
||||
if (methodName == null) throw new ArgumentNullException(nameof(methodName));
|
||||
|
||||
if (methodName.Contains("/") || methodName.Contains("+") || methodName.Contains("#"))
|
||||
{
|
||||
throw new ArgumentException("The method name cannot contain /, + or #.");
|
||||
}
|
||||
|
||||
var requestTopic = $"MQTTnet.RPC/{Guid.NewGuid():N}/{methodName}";
|
||||
var responseTopic = requestTopic + "/response";
|
||||
|
||||
var requestMessage = new MqttApplicationMessageBuilder()
|
||||
.WithTopic(requestTopic)
|
||||
.WithPayload(payload)
|
||||
.WithQualityOfServiceLevel(qualityOfServiceLevel)
|
||||
.Build();
|
||||
|
||||
try
|
||||
{
|
||||
var tcs = new TaskCompletionSource<byte[]>();
|
||||
if (!_waitingCalls.TryAdd(responseTopic, tcs))
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
await _mqttClient.SubscribeAsync(responseTopic, qualityOfServiceLevel).ConfigureAwait(false);
|
||||
await _mqttClient.PublishAsync(requestMessage).ConfigureAwait(false);
|
||||
|
||||
using (var timeoutCts = new CancellationTokenSource(timeout))
|
||||
using (var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCts.Token))
|
||||
{
|
||||
linkedCts.Token.Register(() =>
|
||||
{
|
||||
if (!tcs.Task.IsCompleted && !tcs.Task.IsFaulted && !tcs.Task.IsCanceled)
|
||||
{
|
||||
tcs.TrySetCanceled();
|
||||
}
|
||||
});
|
||||
|
||||
try
|
||||
{
|
||||
var result = await tcs.Task.ConfigureAwait(false);
|
||||
timeoutCts.Cancel(false);
|
||||
return result;
|
||||
}
|
||||
catch (OperationCanceledException exception)
|
||||
{
|
||||
if (timeoutCts.IsCancellationRequested && !cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
throw new MqttCommunicationTimedOutException(exception);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_waitingCalls.TryRemove(responseTopic, out _);
|
||||
await _mqttClient.UnsubscribeAsync(responseTopic).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnApplicationMessageReceived(object sender, MqttApplicationMessageReceivedEventArgs eventArgs)
|
||||
{
|
||||
if (!_waitingCalls.TryRemove(eventArgs.ApplicationMessage.Topic, out var tcs))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (tcs.Task.IsCompleted || tcs.Task.IsCanceled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
tcs.TrySetResult(eventArgs.ApplicationMessage.Payload);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var tcs in _waitingCalls)
|
||||
{
|
||||
tcs.Value.SetCanceled();
|
||||
}
|
||||
|
||||
_waitingCalls.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// If using the MQTT client PubSubClient it must be ensured that the request topic for each method is subscribed like the following.
|
||||
_mqttClient.subscribe("MQTTnet.RPC/+/ping");
|
||||
_mqttClient.subscribe("MQTTnet.RPC/+/do_something");
|
||||
|
||||
// It is not allowed to change the structure of the topic. Otherwise RPC will not work. So method names can be separated using
|
||||
// an _ or . but no +, # or /. If it is required to distinguish between devices own rules can be defined like the following.
|
||||
_mqttClient.subscribe("MQTTnet.RPC/+/deviceA.ping");
|
||||
_mqttClient.subscribe("MQTTnet.RPC/+/deviceB.ping");
|
||||
_mqttClient.subscribe("MQTTnet.RPC/+/deviceC.getTemperature");
|
||||
|
||||
// Within the callback of the MQTT client the topic must be checked if it belongs to MQTTnet RPC. The following code shows one
|
||||
// possible way of doing this.
|
||||
void mqtt_Callback(char *topic, byte *payload, unsigned int payloadLength)
|
||||
{
|
||||
String topicString = String(topic);
|
||||
|
||||
if (topicString.startsWith("MQTTnet.RPC/")) {
|
||||
String responseTopic = topicString + String("/response");
|
||||
|
||||
if (topicString.endsWith("/deviceA.ping")) {
|
||||
mqtt_publish(responseTopic, "pong", false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Important notes:
|
||||
// ! Do not send response message with the _retain_ flag set to true.
|
||||
// ! All required data for a RPC call and the result must be placed into the payload.
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
using MQTTnet.Client;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
using System.Security.Authentication;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.Channel;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Serializer;
|
||||
using WebSocket4Net;
|
||||
|
||||
namespace MQTTnet.TestApp.NetCore
|
||||
{
|
||||
public class WebSocket4NetMqttClientAdapterFactory : IMqttClientAdapterFactory
|
||||
{
|
||||
public IMqttChannelAdapter CreateClientAdapter(IMqttClientOptions options, IMqttNetChildLogger logger)
|
||||
{
|
||||
if (options == null) throw new ArgumentNullException(nameof(options));
|
||||
if (logger == null) throw new ArgumentNullException(nameof(logger));
|
||||
|
||||
if (!(options.ChannelOptions is MqttClientWebSocketOptions))
|
||||
{
|
||||
throw new NotSupportedException("Only WebSocket connections are supported.");
|
||||
}
|
||||
|
||||
return new MqttChannelAdapter(new WebSocket4NetMqttChannel(options), new MqttPacketSerializer(), logger);
|
||||
}
|
||||
|
||||
private class WebSocket4NetMqttChannel : IMqttChannel
|
||||
{
|
||||
private readonly BlockingCollection<byte> _receiveBuffer = new BlockingCollection<byte>();
|
||||
|
||||
private readonly IMqttClientOptions _clientOptions;
|
||||
private WebSocket4Net.WebSocket _webSocket;
|
||||
|
||||
public WebSocket4NetMqttChannel(IMqttClientOptions clientOptions)
|
||||
{
|
||||
_clientOptions = clientOptions ?? throw new ArgumentNullException(nameof(clientOptions));
|
||||
}
|
||||
|
||||
public string Endpoint { get; } = "";
|
||||
|
||||
public Task ConnectAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var channelOptions = (MqttClientWebSocketOptions)_clientOptions.ChannelOptions;
|
||||
|
||||
var uri = "ws://" + channelOptions.Uri;
|
||||
var sslProtocols = SslProtocols.None;
|
||||
|
||||
if (channelOptions.TlsOptions.UseTls)
|
||||
{
|
||||
uri = "wss://" + channelOptions.Uri;
|
||||
sslProtocols = SslProtocols.Tls12;
|
||||
}
|
||||
|
||||
var subProtocol = channelOptions.SubProtocols.FirstOrDefault() ?? string.Empty;
|
||||
|
||||
_webSocket = new WebSocket4Net.WebSocket(uri, subProtocol, sslProtocols: sslProtocols);
|
||||
_webSocket.DataReceived += OnDataReceived;
|
||||
_webSocket.Open();
|
||||
SpinWait.SpinUntil(() => _webSocket.State == WebSocketState.Open, _clientOptions.CommunicationTimeout);
|
||||
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task DisconnectAsync()
|
||||
{
|
||||
if (_webSocket != null)
|
||||
{
|
||||
_webSocket.DataReceived -= OnDataReceived;
|
||||
_webSocket.Close();
|
||||
SpinWait.SpinUntil(() => _webSocket.State == WebSocketState.Closed, _clientOptions.CommunicationTimeout);
|
||||
}
|
||||
|
||||
_webSocket = null;
|
||||
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
var readBytes = 0;
|
||||
while (count > 0 && !cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
byte @byte;
|
||||
if (readBytes == 0)
|
||||
{
|
||||
// Block until at lease one byte was received.
|
||||
@byte = _receiveBuffer.Take(cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!_receiveBuffer.TryTake(out @byte))
|
||||
{
|
||||
return Task.FromResult(readBytes);
|
||||
}
|
||||
}
|
||||
|
||||
buffer[offset] = @byte;
|
||||
offset++;
|
||||
count--;
|
||||
readBytes++;
|
||||
}
|
||||
|
||||
return Task.FromResult(readBytes);
|
||||
}
|
||||
|
||||
public Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
_webSocket.Send(buffer, offset, count);
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_webSocket != null)
|
||||
{
|
||||
_webSocket.DataReceived -= OnDataReceived;
|
||||
_webSocket.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDataReceived(object sender, WebSocket4Net.DataReceivedEventArgs e)
|
||||
{
|
||||
foreach (var @byte in e.Data)
|
||||
{
|
||||
_receiveBuffer.Add(@byte);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Packets;
|
||||
using MQTTnet.Serializer;
|
||||
|
||||
namespace MQTTnet.Adapter
|
||||
{
|
||||
public interface IMqttChannelAdapter : IDisposable
|
||||
{
|
||||
string Endpoint { get; }
|
||||
|
||||
IMqttPacketSerializer PacketSerializer { get; }
|
||||
|
||||
event EventHandler ReadingPacketStarted;
|
||||
|
||||
event EventHandler ReadingPacketCompleted;
|
||||
|
||||
Task ConnectAsync(TimeSpan timeout, CancellationToken cancellationToken);
|
||||
|
||||
Task DisconnectAsync(TimeSpan timeout, CancellationToken cancellationToken);
|
||||
|
||||
Task SendPacketAsync(MqttBasePacket packet, CancellationToken cancellationToken);
|
||||
|
||||
Task<MqttBasePacket> ReceivePacketAsync(TimeSpan timeout, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -4,7 +4,7 @@ using MQTTnet.Server;
|
||||
|
||||
namespace MQTTnet.Adapter
|
||||
{
|
||||
public interface IMqttServerAdapter
|
||||
public interface IMqttServerAdapter : IDisposable
|
||||
{
|
||||
event EventHandler<MqttServerAdapterClientAcceptedEventArgs> ClientAccepted;
|
||||
|
||||
@@ -0,0 +1,261 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net.Sockets;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Channel;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Exceptions;
|
||||
using MQTTnet.Internal;
|
||||
using MQTTnet.Packets;
|
||||
using MQTTnet.Serializer;
|
||||
|
||||
namespace MQTTnet.Adapter
|
||||
{
|
||||
public class MqttChannelAdapter : IMqttChannelAdapter
|
||||
{
|
||||
private const uint ErrorOperationAborted = 0x800703E3;
|
||||
private const int ReadBufferSize = 4096; // TODO: Move buffer size to config
|
||||
|
||||
private readonly SemaphoreSlim _writerSemaphore = new SemaphoreSlim(1, 1);
|
||||
|
||||
private readonly IMqttNetChildLogger _logger;
|
||||
private readonly IMqttChannel _channel;
|
||||
|
||||
private readonly byte[] _fixedHeaderBuffer = new byte[2];
|
||||
|
||||
private readonly byte[] _singleByteBuffer = new byte[1];
|
||||
|
||||
private bool _isDisposed;
|
||||
|
||||
public MqttChannelAdapter(IMqttChannel channel, IMqttPacketSerializer serializer, IMqttNetChildLogger logger)
|
||||
{
|
||||
if (logger == null) throw new ArgumentNullException(nameof(logger));
|
||||
|
||||
_channel = channel ?? throw new ArgumentNullException(nameof(channel));
|
||||
PacketSerializer = serializer ?? throw new ArgumentNullException(nameof(serializer));
|
||||
|
||||
_logger = logger.CreateChildLogger(nameof(MqttChannelAdapter));
|
||||
}
|
||||
|
||||
public string Endpoint => _channel.Endpoint;
|
||||
|
||||
public IMqttPacketSerializer PacketSerializer { get; }
|
||||
|
||||
public event EventHandler ReadingPacketStarted;
|
||||
public event EventHandler ReadingPacketCompleted;
|
||||
|
||||
public async Task ConnectAsync(TimeSpan timeout, CancellationToken cancellationToken)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
try
|
||||
{
|
||||
_logger.Verbose("Connecting [Timeout={0}]", timeout);
|
||||
|
||||
await Internal.TaskExtensions
|
||||
.TimeoutAfterAsync(ct => _channel.ConnectAsync(ct), timeout, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
if (IsWrappedException(exception))
|
||||
{
|
||||
throw;
|
||||
}
|
||||
|
||||
WrapException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DisconnectAsync(TimeSpan timeout, CancellationToken cancellationToken)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
try
|
||||
{
|
||||
_logger.Verbose("Disconnecting [Timeout={0}]", timeout);
|
||||
|
||||
await Internal.TaskExtensions
|
||||
.TimeoutAfterAsync(ct => _channel.DisconnectAsync(), timeout, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
if (IsWrappedException(exception))
|
||||
{
|
||||
throw;
|
||||
}
|
||||
|
||||
WrapException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SendPacketAsync(MqttBasePacket packet, CancellationToken cancellationToken)
|
||||
{
|
||||
await _writerSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
var packetData = PacketSerializer.Serialize(packet);
|
||||
await _channel.WriteAsync(packetData.Array, packetData.Offset, packetData.Count, cancellationToken).ConfigureAwait(false);
|
||||
PacketSerializer.FreeBuffer();
|
||||
|
||||
_logger.Verbose("TX >>> {0}", packet);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
if (IsWrappedException(exception))
|
||||
{
|
||||
throw;
|
||||
}
|
||||
|
||||
WrapException(exception);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_writerSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<MqttBasePacket> ReceivePacketAsync(TimeSpan timeout, CancellationToken cancellationToken)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
try
|
||||
{
|
||||
ReceivedMqttPacket receivedMqttPacket;
|
||||
|
||||
if (timeout > TimeSpan.Zero)
|
||||
{
|
||||
receivedMqttPacket = await Internal.TaskExtensions.TimeoutAfterAsync(ReceiveAsync, timeout, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
receivedMqttPacket = await ReceiveAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (receivedMqttPacket == null || cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var packet = PacketSerializer.Deserialize(receivedMqttPacket);
|
||||
if (packet == null)
|
||||
{
|
||||
throw new MqttProtocolViolationException("Received malformed packet.");
|
||||
}
|
||||
|
||||
_logger.Verbose("RX <<< {0}", packet);
|
||||
|
||||
return packet;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
if (IsWrappedException(exception))
|
||||
{
|
||||
throw;
|
||||
}
|
||||
|
||||
WrapException(exception);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private async Task<ReceivedMqttPacket> ReceiveAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var fixedHeader = await MqttPacketReader.ReadFixedHeaderAsync(_channel, _fixedHeaderBuffer, _singleByteBuffer, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
try
|
||||
{
|
||||
ReadingPacketStarted?.Invoke(this, EventArgs.Empty);
|
||||
|
||||
if (fixedHeader.RemainingLength == 0)
|
||||
{
|
||||
return new ReceivedMqttPacket(fixedHeader.Flags, null);
|
||||
}
|
||||
|
||||
var body = new byte[fixedHeader.RemainingLength];
|
||||
var bodyOffset = 0;
|
||||
var chunkSize = Math.Min(ReadBufferSize, fixedHeader.RemainingLength);
|
||||
|
||||
do
|
||||
{
|
||||
var bytesLeft = body.Length - bodyOffset;
|
||||
if (chunkSize > bytesLeft)
|
||||
{
|
||||
chunkSize = bytesLeft;
|
||||
}
|
||||
|
||||
#if WINDOWS_UWP
|
||||
var readBytes = await _channel.ReadAsync(body, bodyOffset, chunkSize, cancellationToken).ConfigureAwait(false);
|
||||
#else
|
||||
// async/await is not used to avoid the overhead of context switches. We assume that the reamining data
|
||||
// has been sent from the sender directly after the initial bytes.
|
||||
var readBytes = _channel.ReadAsync(body, bodyOffset, chunkSize, cancellationToken).ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
#endif
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
ExceptionHelper.ThrowIfGracefulSocketClose(readBytes);
|
||||
|
||||
bodyOffset += readBytes;
|
||||
} while (bodyOffset < body.Length);
|
||||
|
||||
return new ReceivedMqttPacket(fixedHeader.Flags, new MqttPacketBodyReader(body, 0, body.Length));
|
||||
}
|
||||
finally
|
||||
{
|
||||
ReadingPacketCompleted?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_isDisposed = true;
|
||||
|
||||
_channel?.Dispose();
|
||||
}
|
||||
|
||||
private void ThrowIfDisposed()
|
||||
{
|
||||
if (_isDisposed)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(MqttChannelAdapter));
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsWrappedException(Exception exception)
|
||||
{
|
||||
return exception is TaskCanceledException ||
|
||||
exception is OperationCanceledException ||
|
||||
exception is MqttCommunicationTimedOutException ||
|
||||
exception is MqttCommunicationException;
|
||||
}
|
||||
|
||||
private static void WrapException(Exception exception)
|
||||
{
|
||||
if (exception is IOException && exception.InnerException is SocketException socketException)
|
||||
{
|
||||
if (socketException.SocketErrorCode == SocketError.ConnectionAborted ||
|
||||
socketException.SocketErrorCode == SocketError.OperationAborted)
|
||||
{
|
||||
throw new OperationCanceledException();
|
||||
}
|
||||
}
|
||||
|
||||
if (exception is COMException comException)
|
||||
{
|
||||
if ((uint)comException.HResult == ErrorOperationAborted)
|
||||
{
|
||||
throw new OperationCanceledException();
|
||||
}
|
||||
}
|
||||
|
||||
throw new MqttCommunicationException(exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using MQTTnet.Serializer;
|
||||
|
||||
namespace MQTTnet.Adapter
|
||||
{
|
||||
public class ReceivedMqttPacket
|
||||
{
|
||||
public ReceivedMqttPacket(byte fixedHeader, MqttPacketBodyReader body)
|
||||
{
|
||||
FixedHeader = fixedHeader;
|
||||
Body = body;
|
||||
}
|
||||
|
||||
public byte FixedHeader { get; }
|
||||
|
||||
public MqttPacketBodyReader Body { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
namespace MQTTnet
|
||||
{
|
||||
public static class ApplicationMessagePublisherExtensions
|
||||
{
|
||||
public static async Task PublishAsync(this IApplicationMessagePublisher publisher, IEnumerable<MqttApplicationMessage> applicationMessages)
|
||||
{
|
||||
if (publisher == null) throw new ArgumentNullException(nameof(publisher));
|
||||
if (applicationMessages == null) throw new ArgumentNullException(nameof(applicationMessages));
|
||||
|
||||
foreach (var applicationMessage in applicationMessages)
|
||||
{
|
||||
await publisher.PublishAsync(applicationMessage).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task PublishAsync(this IApplicationMessagePublisher publisher, params MqttApplicationMessage[] applicationMessages)
|
||||
{
|
||||
if (publisher == null) throw new ArgumentNullException(nameof(publisher));
|
||||
if (applicationMessages == null) throw new ArgumentNullException(nameof(applicationMessages));
|
||||
|
||||
foreach (var applicationMessage in applicationMessages)
|
||||
{
|
||||
await publisher.PublishAsync(applicationMessage).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
public static Task PublishAsync(this IApplicationMessagePublisher publisher, string topic)
|
||||
{
|
||||
if (publisher == null) throw new ArgumentNullException(nameof(publisher));
|
||||
if (topic == null) throw new ArgumentNullException(nameof(topic));
|
||||
|
||||
return publisher.PublishAsync(builder => builder
|
||||
.WithTopic(topic));
|
||||
}
|
||||
|
||||
public static Task PublishAsync(this IApplicationMessagePublisher publisher, string topic, string payload)
|
||||
{
|
||||
if (publisher == null) throw new ArgumentNullException(nameof(publisher));
|
||||
if (topic == null) throw new ArgumentNullException(nameof(topic));
|
||||
|
||||
return publisher.PublishAsync(builder => builder
|
||||
.WithTopic(topic)
|
||||
.WithPayload(payload));
|
||||
}
|
||||
|
||||
public static Task PublishAsync(this IApplicationMessagePublisher publisher, string topic, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel)
|
||||
{
|
||||
if (publisher == null) throw new ArgumentNullException(nameof(publisher));
|
||||
if (topic == null) throw new ArgumentNullException(nameof(topic));
|
||||
|
||||
return publisher.PublishAsync(builder => builder
|
||||
.WithTopic(topic)
|
||||
.WithPayload(payload)
|
||||
.WithQualityOfServiceLevel(qualityOfServiceLevel));
|
||||
}
|
||||
|
||||
public static Task PublishAsync(this IApplicationMessagePublisher publisher, string topic, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel, bool retain)
|
||||
{
|
||||
if (publisher == null) throw new ArgumentNullException(nameof(publisher));
|
||||
if (topic == null) throw new ArgumentNullException(nameof(topic));
|
||||
|
||||
return publisher.PublishAsync(builder => builder
|
||||
.WithTopic(topic)
|
||||
.WithPayload(payload)
|
||||
.WithQualityOfServiceLevel(qualityOfServiceLevel)
|
||||
.WithRetainFlag(retain));
|
||||
}
|
||||
|
||||
public static Task PublishAsync(this IApplicationMessagePublisher publisher, Func<MqttApplicationMessageBuilder, MqttApplicationMessageBuilder> builder)
|
||||
{
|
||||
var message = builder(new MqttApplicationMessageBuilder()).Build();
|
||||
return publisher.PublishAsync(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MQTTnet.Channel
|
||||
{
|
||||
public interface IMqttChannel : IDisposable
|
||||
{
|
||||
string Endpoint { get; }
|
||||
|
||||
Task ConnectAsync(CancellationToken cancellationToken);
|
||||
Task DisconnectAsync();
|
||||
|
||||
Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken);
|
||||
Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user