mirror of
https://github.com/dotnet/MQTTnet.git
synced 2026-10-31 03:02:39 +08:00
@@ -13,11 +13,9 @@
|
|||||||
<releaseNotes>* Updated to MQTTnet 2.8.0.
|
<releaseNotes>* Updated to MQTTnet 2.8.0.
|
||||||
</releaseNotes>
|
</releaseNotes>
|
||||||
<copyright>Copyright Christian Kratky 2016-2018</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>
|
<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>
|
<dependencies>
|
||||||
<group targetFramework="netstandard2.0">
|
<dependency id="MQTTnet" version="2.8.0-alpha5" />
|
||||||
<dependency id="MQTTnet" version="2.8.0-alpha5" />
|
|
||||||
</group>
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</metadata>
|
</metadata>
|
||||||
|
|
||||||
|
|||||||
@@ -15,24 +15,7 @@
|
|||||||
<copyright>Copyright Christian Kratky 2016-2018</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>
|
<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>
|
<dependencies>
|
||||||
<group targetFramework="netstandard2.0">
|
<dependency id="MQTTnet" version="2.8.0-beta1" />
|
||||||
<dependency id="MQTTnet" version="2.8.0-alpha5" />
|
|
||||||
</group>
|
|
||||||
<group targetFramework="netstandard1.3">
|
|
||||||
<dependency id="MQTTnet" version="2.8.0-alpha5" />
|
|
||||||
</group>
|
|
||||||
<group targetFramework="netstandard2.0">
|
|
||||||
<dependency id="MQTTnet" version="2.8.0-alpha5" />
|
|
||||||
</group>
|
|
||||||
<group targetFramework="uap10.0">
|
|
||||||
<dependency id="MQTTnet" version="2.8.0-alpha5" />
|
|
||||||
</group>
|
|
||||||
<group targetFramework="net452">
|
|
||||||
<dependency id="MQTTnet" version="2.8.0-alpha5" />
|
|
||||||
</group>
|
|
||||||
<group targetFramework="net461">
|
|
||||||
<dependency id="MQTTnet" version="2.8.0-alpha5" />
|
|
||||||
</group>
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</metadata>
|
</metadata>
|
||||||
|
|
||||||
@@ -48,6 +31,8 @@
|
|||||||
|
|
||||||
<!-- .NET Framework -->
|
<!-- .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\net452\MQTTnet.Extensions.ManagedClient.*" target="lib\net452\"/>
|
||||||
<file src="..\Source\MQTTnet.Extensions.ManagedClient\bin\Release\net461\MQTTnet.Extensions.ManagedClient.*" target="lib\net461\"/>
|
|
||||||
|
<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\net472\"/>
|
||||||
</files>
|
</files>
|
||||||
</package>
|
</package>
|
||||||
@@ -15,24 +15,7 @@
|
|||||||
<copyright>Copyright Christian Kratky 2016-2018</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>
|
<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>
|
<dependencies>
|
||||||
<group targetFramework="netstandard2.0">
|
<dependency id="MQTTnet" version="2.8.0-beta1" />
|
||||||
<dependency id="MQTTnet" version="2.8.0-alpha5" />
|
|
||||||
</group>
|
|
||||||
<group targetFramework="netstandard1.3">
|
|
||||||
<dependency id="MQTTnet" version="2.8.0-alpha5" />
|
|
||||||
</group>
|
|
||||||
<group targetFramework="netstandard2.0">
|
|
||||||
<dependency id="MQTTnet" version="2.8.0-alpha5" />
|
|
||||||
</group>
|
|
||||||
<group targetFramework="uap10.0">
|
|
||||||
<dependency id="MQTTnet" version="2.8.0-alpha5" />
|
|
||||||
</group>
|
|
||||||
<group targetFramework="net452">
|
|
||||||
<dependency id="MQTTnet" version="2.8.0-alpha5" />
|
|
||||||
</group>
|
|
||||||
<group targetFramework="net461">
|
|
||||||
<dependency id="MQTTnet" version="2.8.0-alpha5" />
|
|
||||||
</group>
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</metadata>
|
</metadata>
|
||||||
|
|
||||||
@@ -48,6 +31,8 @@
|
|||||||
|
|
||||||
<!-- .NET Framework -->
|
<!-- .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\net452\MQTTnet.Extensions.Rpc.*" target="lib\net452\"/>
|
||||||
<file src="..\Source\MQTTnet.Extensions.Rpc\bin\Release\net461\MQTTnet.Extensions.Rpc.*" target="lib\net461\"/>
|
|
||||||
|
<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\"/>
|
||||||
</files>
|
</files>
|
||||||
</package>
|
</package>
|
||||||
@@ -48,7 +48,7 @@
|
|||||||
<dependency id="System.Net.WebSockets.Client" version="4.3.2" />
|
<dependency id="System.Net.WebSockets.Client" version="4.3.2" />
|
||||||
</group>
|
</group>
|
||||||
<group targetFramework="uap10.0">
|
<group targetFramework="uap10.0">
|
||||||
<dependency id="Microsoft.NETCore.UniversalWindowsPlatform" version="5.4.1" />
|
<dependency id="Microsoft.NETCore.UniversalWindowsPlatform" version="6.1.4" />
|
||||||
</group>
|
</group>
|
||||||
<group targetFramework="net452">
|
<group targetFramework="net452">
|
||||||
</group>
|
</group>
|
||||||
@@ -69,6 +69,8 @@
|
|||||||
|
|
||||||
<!-- .NET Framework -->
|
<!-- .NET Framework -->
|
||||||
<file src="..\Source\MQTTnet\bin\Release\net452\MQTTnet.*" target="lib\net452\"/>
|
<file src="..\Source\MQTTnet\bin\Release\net452\MQTTnet.*" target="lib\net452\"/>
|
||||||
<file src="..\Source\MQTTnet\bin\Release\net461\MQTTnet.*" target="lib\net461\"/>
|
|
||||||
|
<file src="..\Source\MQTTnet\bin\Release\netstandard2.0\MQTTnet.*" target="lib\net461\"/>
|
||||||
|
<file src="..\Source\MQTTnet\bin\Release\netstandard2.0\MQTTnet.*" target="lib\net472\"/>
|
||||||
</files>
|
</files>
|
||||||
</package>
|
</package>
|
||||||
+4
-4
@@ -36,8 +36,8 @@ if ($path) {
|
|||||||
Remove-Item .\NuGet -Force -Recurse -ErrorAction SilentlyContinue
|
Remove-Item .\NuGet -Force -Recurse -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
New-Item -ItemType Directory -Force -Path .\NuGet
|
New-Item -ItemType Directory -Force -Path .\NuGet
|
||||||
.\NuGet.exe pack MQTTnet.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.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.Rpc.nuspec -Verbosity detailed -Symbols -OutputDir "NuGet" -Version $nugetVersion
|
||||||
.\NuGet.exe pack MQTTnet.Extensions.ManagedClient.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
|
||||||
|
}
|
||||||
@@ -20,6 +20,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{67C28AC1
|
|||||||
Build\MQTTnet.Extensions.ManagedClient.nuspec = Build\MQTTnet.Extensions.ManagedClient.nuspec
|
Build\MQTTnet.Extensions.ManagedClient.nuspec = Build\MQTTnet.Extensions.ManagedClient.nuspec
|
||||||
Build\MQTTnet.Extensions.Rpc.nuspec = Build\MQTTnet.Extensions.Rpc.nuspec
|
Build\MQTTnet.Extensions.Rpc.nuspec = Build\MQTTnet.Extensions.Rpc.nuspec
|
||||||
Build\MQTTnet.nuspec = Build\MQTTnet.nuspec
|
Build\MQTTnet.nuspec = Build\MQTTnet.nuspec
|
||||||
|
Build\upload.ps1 = Build\upload.ps1
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{B3F60ECB-45BA-4C66-8903-8BB89CA67998}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{B3F60ECB-45BA-4C66-8903-8BB89CA67998}"
|
||||||
@@ -74,6 +75,7 @@ Global
|
|||||||
{A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Release|x86.Build.0 = Release|Any CPU
|
{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.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.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.ActiveCfg = Debug|ARM
|
||||||
{FF1F72D6-9524-4422-9497-3CC0002216ED}.Debug|ARM.Build.0 = Debug|ARM
|
{FF1F72D6-9524-4422-9497-3CC0002216ED}.Debug|ARM.Build.0 = Debug|ARM
|
||||||
{FF1F72D6-9524-4422-9497-3CC0002216ED}.Debug|ARM.Deploy.0 = Debug|ARM
|
{FF1F72D6-9524-4422-9497-3CC0002216ED}.Debug|ARM.Deploy.0 = Debug|ARM
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ 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)
|
* TLS 1.2 support for client and server (but not UWP servers)
|
||||||
* Extensible communication channels (i.e. In-Memory, TCP, TCP+TLS, WS)
|
* Extensible communication channels (i.e. In-Memory, TCP, TCP+TLS, WS)
|
||||||
* Lightweight (only the low level implementation of MQTT, no overhead)
|
* Lightweight (only the low level implementation of MQTT, no overhead)
|
||||||
* Performance optimized (processing ~60.000 messages / second)*
|
* Performance optimized (processing ~70.000 messages / second)*
|
||||||
* Interfaces included for mocking and testing
|
* Interfaces included for mocking and testing
|
||||||
* Access to internal trace messages
|
* Access to internal trace messages
|
||||||
* Unit tested (~90 tests)
|
* Unit tested (~90 tests)
|
||||||
@@ -50,14 +50,15 @@ MQTTnet is a high performance .NET library for MQTT based communication. It prov
|
|||||||
* .NET Standard 1.3+
|
* .NET Standard 1.3+
|
||||||
* .NET Core 1.1+
|
* .NET Core 1.1+
|
||||||
* .NET Core App 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)
|
* .NET Framework 4.5.2+ (x86, x64, AnyCPU)
|
||||||
* Mono 5.2+
|
* Mono 5.2+
|
||||||
|
* Universal Windows Platform (UWP) 10.0.10240+ (x86, x64, ARM, AnyCPU, Windows 10 IoT Core)
|
||||||
* Xamarin.Android 7.5+
|
* Xamarin.Android 7.5+
|
||||||
* Xamarin.iOS 10.14+
|
* Xamarin.iOS 10.14+
|
||||||
|
|
||||||
## Supported MQTT versions
|
## Supported MQTT versions
|
||||||
|
|
||||||
|
* 5.0.0 (planned)
|
||||||
* 3.1.1
|
* 3.1.1
|
||||||
* 3.1.0
|
* 3.1.0
|
||||||
|
|
||||||
@@ -79,8 +80,7 @@ This library is used in the following projects:
|
|||||||
|
|
||||||
* MQTT Client Rx (Wrapper for Reactive Extensions, <https://github.com/1iveowl/MQTTClient.rx>)
|
* 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))
|
* 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>)
|
* HA4IoT (Open Source Home Automation system for .NET, <https://github.com/chkr1011/HA4IoT>)
|
||||||
|
|
||||||
|
|
||||||
If you use this library and want to see your project here please let me know.
|
If you use this library and want to see your project here please let me know.
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace MQTTnet.Benchmarks.Tcp
|
namespace MQTTnet.AspNetCore.Client.Tcp
|
||||||
{
|
{
|
||||||
public static class BufferExtensions
|
public static class BufferExtensions
|
||||||
{
|
{
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
using System.IO.Pipelines;
|
using System.IO.Pipelines;
|
||||||
|
|
||||||
namespace MQTTnet.Benchmarks.Tcp
|
namespace MQTTnet.AspNetCore.Client.Tcp
|
||||||
{
|
{
|
||||||
public class DuplexPipe : IDuplexPipe
|
public class DuplexPipe : IDuplexPipe
|
||||||
{
|
{
|
||||||
+3
-2
@@ -6,7 +6,7 @@ using System.Runtime.CompilerServices;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace MQTTnet.Benchmarks.Tcp
|
namespace MQTTnet.AspNetCore.Client.Tcp
|
||||||
{
|
{
|
||||||
public class SocketAwaitable : ICriticalNotifyCompletion
|
public class SocketAwaitable : ICriticalNotifyCompletion
|
||||||
{
|
{
|
||||||
@@ -23,9 +23,10 @@ namespace MQTTnet.Benchmarks.Tcp
|
|||||||
_ioScheduler = ioScheduler;
|
_ioScheduler = ioScheduler;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SocketAwaitable GetAwaiter() => this;
|
|
||||||
public bool IsCompleted => ReferenceEquals(_callback, _callbackCompleted);
|
public bool IsCompleted => ReferenceEquals(_callback, _callbackCompleted);
|
||||||
|
|
||||||
|
public SocketAwaitable GetAwaiter() => this;
|
||||||
|
|
||||||
public int GetResult()
|
public int GetResult()
|
||||||
{
|
{
|
||||||
Debug.Assert(ReferenceEquals(_callback, _callbackCompleted));
|
Debug.Assert(ReferenceEquals(_callback, _callbackCompleted));
|
||||||
+1
-2
@@ -2,7 +2,7 @@
|
|||||||
using System.IO.Pipelines;
|
using System.IO.Pipelines;
|
||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
|
|
||||||
namespace MQTTnet.Benchmarks.Tcp
|
namespace MQTTnet.AspNetCore.Client.Tcp
|
||||||
{
|
{
|
||||||
public class SocketReceiver
|
public class SocketReceiver
|
||||||
{
|
{
|
||||||
@@ -24,7 +24,6 @@ namespace MQTTnet.Benchmarks.Tcp
|
|||||||
_eventArgs.SetBuffer(buffer);
|
_eventArgs.SetBuffer(buffer);
|
||||||
#else
|
#else
|
||||||
var segment = buffer.GetArray();
|
var segment = buffer.GetArray();
|
||||||
|
|
||||||
_eventArgs.SetBuffer(segment.Array, segment.Offset, segment.Count);
|
_eventArgs.SetBuffer(segment.Array, segment.Offset, segment.Count);
|
||||||
#endif
|
#endif
|
||||||
if (!_socket.ReceiveAsync(_eventArgs))
|
if (!_socket.ReceiveAsync(_eventArgs))
|
||||||
+1
-2
@@ -5,7 +5,7 @@ using System.Diagnostics;
|
|||||||
using System.IO.Pipelines;
|
using System.IO.Pipelines;
|
||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
|
|
||||||
namespace MQTTnet.Benchmarks.Tcp
|
namespace MQTTnet.AspNetCore.Client.Tcp
|
||||||
{
|
{
|
||||||
public class SocketSender
|
public class SocketSender
|
||||||
{
|
{
|
||||||
@@ -61,7 +61,6 @@ namespace MQTTnet.Benchmarks.Tcp
|
|||||||
_eventArgs.SetBuffer(MemoryMarshal.AsMemory(memory));
|
_eventArgs.SetBuffer(MemoryMarshal.AsMemory(memory));
|
||||||
#else
|
#else
|
||||||
var segment = memory.GetArray();
|
var segment = memory.GetArray();
|
||||||
|
|
||||||
_eventArgs.SetBuffer(segment.Array, segment.Offset, segment.Count);
|
_eventArgs.SetBuffer(segment.Array, segment.Offset, segment.Count);
|
||||||
#endif
|
#endif
|
||||||
if (!_socket.SendAsync(_eventArgs))
|
if (!_socket.SendAsync(_eventArgs))
|
||||||
+34
-21
@@ -1,30 +1,35 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.IO.Pipelines;
|
using System.IO.Pipelines;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Connections;
|
||||||
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
using MQTTnet.Exceptions;
|
using MQTTnet.Exceptions;
|
||||||
|
|
||||||
namespace MQTTnet.Benchmarks.Tcp
|
namespace MQTTnet.AspNetCore.Client.Tcp
|
||||||
{
|
{
|
||||||
public class TcpConnection
|
public class TcpConnection : ConnectionContext
|
||||||
{
|
{
|
||||||
private readonly Socket _socket;
|
|
||||||
private volatile bool _aborted;
|
private volatile bool _aborted;
|
||||||
private readonly EndPoint _endPoint;
|
private readonly EndPoint _endPoint;
|
||||||
|
private SocketSender _sender;
|
||||||
|
private SocketReceiver _receiver;
|
||||||
|
|
||||||
|
private Socket _socket;
|
||||||
private IDuplexPipe _application;
|
private IDuplexPipe _application;
|
||||||
private IDuplexPipe _transport;
|
|
||||||
private readonly SocketSender _sender;
|
public bool IsConnected { get; private set; }
|
||||||
private readonly SocketReceiver _receiver;
|
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)
|
public TcpConnection(EndPoint endPoint)
|
||||||
{
|
{
|
||||||
_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
|
|
||||||
_endPoint = endPoint;
|
_endPoint = endPoint;
|
||||||
|
|
||||||
_sender = new SocketSender(_socket, PipeScheduler.ThreadPool);
|
|
||||||
_receiver = new SocketReceiver(_socket, PipeScheduler.ThreadPool);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public TcpConnection(Socket socket)
|
public TcpConnection(Socket socket)
|
||||||
@@ -38,29 +43,34 @@ namespace MQTTnet.Benchmarks.Tcp
|
|||||||
|
|
||||||
public Task DisposeAsync()
|
public Task DisposeAsync()
|
||||||
{
|
{
|
||||||
_transport?.Output.Complete();
|
IsConnected = false;
|
||||||
_transport?.Input.Complete();
|
|
||||||
|
Transport?.Output.Complete();
|
||||||
|
Transport?.Input.Complete();
|
||||||
|
|
||||||
_socket?.Dispose();
|
_socket?.Dispose();
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IDuplexPipe> StartAsync()
|
public async Task StartAsync()
|
||||||
{
|
{
|
||||||
if (!_socket.Connected)
|
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);
|
await _socket.ConnectAsync(_endPoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
var pair = DuplexPipe.CreateConnectionPair(PipeOptions.Default, PipeOptions.Default);
|
var pair = DuplexPipe.CreateConnectionPair(PipeOptions.Default, PipeOptions.Default);
|
||||||
|
|
||||||
_transport = pair.Transport;
|
Transport = pair.Transport;
|
||||||
_application = pair.Application;
|
_application = pair.Application;
|
||||||
|
|
||||||
_ = ExecuteAsync();
|
_ = ExecuteAsync();
|
||||||
|
|
||||||
return pair.Transport;
|
IsConnected = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ExecuteAsync()
|
private async Task ExecuteAsync()
|
||||||
@@ -118,14 +128,14 @@ namespace MQTTnet.Benchmarks.Tcp
|
|||||||
if (!_aborted)
|
if (!_aborted)
|
||||||
{
|
{
|
||||||
// Calling Dispose after ReceiveAsync can cause an "InvalidArgument" error on *nix.
|
// Calling Dispose after ReceiveAsync can cause an "InvalidArgument" error on *nix.
|
||||||
//error = new MqttCommunicationException();
|
error = ConnectionAborted();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (ObjectDisposedException)
|
catch (ObjectDisposedException)
|
||||||
{
|
{
|
||||||
if (!_aborted)
|
if (!_aborted)
|
||||||
{
|
{
|
||||||
//error = new MqttCommunicationException();
|
error = ConnectionAborted();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (IOException ex)
|
catch (IOException ex)
|
||||||
@@ -140,7 +150,7 @@ namespace MQTTnet.Benchmarks.Tcp
|
|||||||
{
|
{
|
||||||
if (_aborted)
|
if (_aborted)
|
||||||
{
|
{
|
||||||
//error = error ?? new MqttCommunicationException();
|
error = error ?? ConnectionAborted();
|
||||||
}
|
}
|
||||||
|
|
||||||
_application.Output.Complete(error);
|
_application.Output.Complete(error);
|
||||||
@@ -180,6 +190,11 @@ namespace MQTTnet.Benchmarks.Tcp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Exception ConnectionAborted()
|
||||||
|
{
|
||||||
|
return new MqttCommunicationException("Connection Aborted");
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<Exception> DoSend()
|
private async Task<Exception> DoSend()
|
||||||
{
|
{
|
||||||
Exception error = null;
|
Exception error = null;
|
||||||
@@ -190,11 +205,9 @@ namespace MQTTnet.Benchmarks.Tcp
|
|||||||
}
|
}
|
||||||
catch (SocketException ex) when (ex.SocketErrorCode == SocketError.OperationAborted)
|
catch (SocketException ex) when (ex.SocketErrorCode == SocketError.OperationAborted)
|
||||||
{
|
{
|
||||||
error = null;
|
|
||||||
}
|
}
|
||||||
catch (ObjectDisposedException)
|
catch (ObjectDisposedException)
|
||||||
{
|
{
|
||||||
error = null;
|
|
||||||
}
|
}
|
||||||
catch (IOException ex)
|
catch (IOException ex)
|
||||||
{
|
{
|
||||||
@@ -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>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@
|
|||||||
<Company />
|
<Company />
|
||||||
<Authors />
|
<Authors />
|
||||||
<PackageId />
|
<PackageId />
|
||||||
|
<LangVersion>7.2</LangVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||||
@@ -16,7 +17,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<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.AspNetCore.WebSockets" Version="2.0.1" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="2.0.1" />
|
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="2.0.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -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,40 @@
|
|||||||
|
using Microsoft.AspNetCore.Connections;
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
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()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,7 +13,8 @@ namespace MQTTnet.AspNetCore
|
|||||||
{
|
{
|
||||||
private readonly IMqttServerOptions _options;
|
private readonly IMqttServerOptions _options;
|
||||||
|
|
||||||
public MqttHostedServer(IMqttServerOptions options, IEnumerable<IMqttServerAdapter> adapters, IMqttNetLogger logger) : base(adapters, logger.CreateChildLogger(nameof(MqttHostedServer)))
|
public MqttHostedServer(IMqttServerOptions options, IEnumerable<IMqttServerAdapter> adapters, IMqttNetLogger logger)
|
||||||
|
: base(adapters, logger.CreateChildLogger(nameof(MqttHostedServer)))
|
||||||
{
|
{
|
||||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
var bodySlice = copy.Slice(0, bodyLength);
|
||||||
|
packet = serializer.Deserialize(new ReceivedMqttPacket(fixedheader, new MqttPacketBodyReader(bodySlice.GetArray(), 0)));
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,10 +30,19 @@ namespace MQTTnet.AspNetCore
|
|||||||
|
|
||||||
if (options.DefaultEndpointOptions.IsEnabled)
|
if (options.DefaultEndpointOptions.IsEnabled)
|
||||||
{
|
{
|
||||||
|
services.AddSingleton<MqttTcpServerAdapter>();
|
||||||
services.AddSingleton<IMqttServerAdapter>(s => s.GetService<MqttTcpServerAdapter>());
|
services.AddSingleton<IMqttServerAdapter>(s => s.GetService<MqttTcpServerAdapter>());
|
||||||
}
|
}
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static IServiceCollection AddMqttConnectionHandler(this IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddSingleton<MqttConnectionHandler>();
|
||||||
|
services.AddSingleton<IMqttServerAdapter>(s => s.GetService<MqttConnectionHandler>());
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ using System.Threading.Tasks;
|
|||||||
using MQTTnet.Client;
|
using MQTTnet.Client;
|
||||||
using MQTTnet.Diagnostics;
|
using MQTTnet.Diagnostics;
|
||||||
using MQTTnet.Exceptions;
|
using MQTTnet.Exceptions;
|
||||||
using MQTTnet.Internal;
|
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
|
|
||||||
namespace MQTTnet.Extensions.ManagedClient
|
namespace MQTTnet.Extensions.ManagedClient
|
||||||
@@ -15,8 +14,7 @@ namespace MQTTnet.Extensions.ManagedClient
|
|||||||
public class ManagedMqttClient : IManagedMqttClient
|
public class ManagedMqttClient : IManagedMqttClient
|
||||||
{
|
{
|
||||||
private readonly BlockingCollection<ManagedMqttApplicationMessage> _messageQueue = new BlockingCollection<ManagedMqttApplicationMessage>();
|
private readonly BlockingCollection<ManagedMqttApplicationMessage> _messageQueue = new BlockingCollection<ManagedMqttApplicationMessage>();
|
||||||
private readonly Dictionary<string, MqttQualityOfServiceLevel> _subscriptions = new Dictionary<string, MqttQualityOfServiceLevel>();
|
private readonly ConcurrentDictionary<string, MqttQualityOfServiceLevel> _subscriptions = new ConcurrentDictionary<string, MqttQualityOfServiceLevel>();
|
||||||
private readonly AsyncLock _subscriptionsLock = new AsyncLock();
|
|
||||||
private readonly List<string> _unsubscriptions = new List<string>();
|
private readonly List<string> _unsubscriptions = new List<string>();
|
||||||
|
|
||||||
private readonly IMqttClient _mqttClient;
|
private readonly IMqttClient _mqttClient;
|
||||||
@@ -118,39 +116,36 @@ namespace MQTTnet.Extensions.ManagedClient
|
|||||||
_messageQueue.Add(applicationMessage);
|
_messageQueue.Add(applicationMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SubscribeAsync(IEnumerable<TopicFilter> topicFilters)
|
public Task SubscribeAsync(IEnumerable<TopicFilter> topicFilters)
|
||||||
{
|
{
|
||||||
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
|
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
|
||||||
|
|
||||||
using (await _subscriptionsLock.LockAsync(CancellationToken.None).ConfigureAwait(false))
|
foreach (var topicFilter in topicFilters)
|
||||||
{
|
{
|
||||||
foreach (var topicFilter in topicFilters)
|
_subscriptions[topicFilter.Topic] = topicFilter.QualityOfServiceLevel;
|
||||||
|
_subscriptionsNotPushed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.FromResult(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task UnsubscribeAsync(IEnumerable<string> topics)
|
||||||
|
{
|
||||||
|
foreach (var topic in topics)
|
||||||
|
{
|
||||||
|
if (_subscriptions.TryRemove(topic, out _))
|
||||||
{
|
{
|
||||||
_subscriptions[topicFilter.Topic] = topicFilter.QualityOfServiceLevel;
|
_unsubscriptions.Add(topic);
|
||||||
_subscriptionsNotPushed = true;
|
_subscriptionsNotPushed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public async Task UnsubscribeAsync(IEnumerable<string> topics)
|
return Task.FromResult(0);
|
||||||
{
|
|
||||||
using (await _subscriptionsLock.LockAsync(CancellationToken.None).ConfigureAwait(false))
|
|
||||||
{
|
|
||||||
foreach (var topic in topics)
|
|
||||||
{
|
|
||||||
if (_subscriptions.Remove(topic))
|
|
||||||
{
|
|
||||||
_unsubscriptions.Add(topic);
|
|
||||||
_subscriptionsNotPushed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_messageQueue?.Dispose();
|
_messageQueue?.Dispose();
|
||||||
_subscriptionsLock?.Dispose();
|
|
||||||
_connectionCancellationToken?.Dispose();
|
_connectionCancellationToken?.Dispose();
|
||||||
_publishingCancellationToken?.Dispose();
|
_publishingCancellationToken?.Dispose();
|
||||||
}
|
}
|
||||||
@@ -289,7 +284,7 @@ namespace MQTTnet.Extensions.ManagedClient
|
|||||||
List<TopicFilter> subscriptions;
|
List<TopicFilter> subscriptions;
|
||||||
List<string> unsubscriptions;
|
List<string> unsubscriptions;
|
||||||
|
|
||||||
using (await _subscriptionsLock.LockAsync(CancellationToken.None).ConfigureAwait(false))
|
lock (_subscriptions)
|
||||||
{
|
{
|
||||||
subscriptions = _subscriptions.Select(i => new TopicFilter(i.Key, i.Value)).ToList();
|
subscriptions = _subscriptions.Select(i => new TopicFilter(i.Key, i.Value)).ToList();
|
||||||
|
|
||||||
|
|||||||
@@ -82,11 +82,11 @@ namespace MQTTnet.Extensions.Rpc
|
|||||||
timeoutCts.Cancel(false);
|
timeoutCts.Cancel(false);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
catch (TaskCanceledException taskCanceledException)
|
catch (OperationCanceledException exception)
|
||||||
{
|
{
|
||||||
if (timeoutCts.IsCancellationRequested && !cancellationToken.IsCancellationRequested)
|
if (timeoutCts.IsCancellationRequested && !cancellationToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
throw new MqttCommunicationTimedOutException(taskCanceledException);
|
throw new MqttCommunicationTimedOutException(exception);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ namespace MQTTnet.Adapter
|
|||||||
|
|
||||||
Task DisconnectAsync(TimeSpan timeout, CancellationToken cancellationToken);
|
Task DisconnectAsync(TimeSpan timeout, CancellationToken cancellationToken);
|
||||||
|
|
||||||
Task SendPacketAsync(TimeSpan timeout, MqttBasePacket packet, CancellationToken cancellationToken);
|
Task SendPacketAsync(MqttBasePacket packet, CancellationToken cancellationToken);
|
||||||
|
|
||||||
Task<MqttBasePacket> ReceivePacketAsync(TimeSpan timeout, CancellationToken cancellationToken);
|
Task<MqttBasePacket> ReceivePacketAsync(TimeSpan timeout, CancellationToken cancellationToken);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ namespace MQTTnet.Adapter
|
|||||||
private const uint ErrorOperationAborted = 0x800703E3;
|
private const uint ErrorOperationAborted = 0x800703E3;
|
||||||
private const int ReadBufferSize = 4096; // TODO: Move buffer size to config
|
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 IMqttNetChildLogger _logger;
|
||||||
private readonly IMqttChannel _channel;
|
private readonly IMqttChannel _channel;
|
||||||
|
|
||||||
@@ -40,52 +42,91 @@ namespace MQTTnet.Adapter
|
|||||||
public event EventHandler ReadingPacketStarted;
|
public event EventHandler ReadingPacketStarted;
|
||||||
public event EventHandler ReadingPacketCompleted;
|
public event EventHandler ReadingPacketCompleted;
|
||||||
|
|
||||||
public Task ConnectAsync(TimeSpan timeout, CancellationToken cancellationToken)
|
public async Task ConnectAsync(TimeSpan timeout, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
ThrowIfDisposed();
|
ThrowIfDisposed();
|
||||||
_logger.Verbose("Connecting [Timeout={0}]", timeout);
|
|
||||||
|
|
||||||
return ExecuteAndWrapExceptionAsync(() =>
|
try
|
||||||
Internal.TaskExtensions.TimeoutAfter(ct => _channel.ConnectAsync(ct), timeout, cancellationToken));
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task DisconnectAsync(TimeSpan timeout, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
ThrowIfDisposed();
|
|
||||||
_logger.Verbose("Disconnecting [Timeout={0}]", timeout);
|
|
||||||
|
|
||||||
return ExecuteAndWrapExceptionAsync(() =>
|
|
||||||
Internal.TaskExtensions.TimeoutAfter(ct => _channel.DisconnectAsync(), timeout, cancellationToken));
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task SendPacketAsync(TimeSpan timeout, MqttBasePacket packet, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
return ExecuteAndWrapExceptionAsync(() =>
|
|
||||||
{
|
{
|
||||||
_logger.Verbose("TX >>> {0} [Timeout={1}]", packet, timeout);
|
_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
|
||||||
|
{
|
||||||
|
_logger.Verbose("TX >>> {0}", packet);
|
||||||
|
|
||||||
var packetData = PacketSerializer.Serialize(packet);
|
var packetData = PacketSerializer.Serialize(packet);
|
||||||
|
|
||||||
return Internal.TaskExtensions.TimeoutAfter(ct => _channel.WriteAsync(
|
await _channel.WriteAsync(packetData.Array, packetData.Offset, packetData.Count, cancellationToken).ConfigureAwait(false);
|
||||||
packetData.Array,
|
|
||||||
packetData.Offset,
|
PacketSerializer.FreeBuffer();
|
||||||
packetData.Count,
|
}
|
||||||
ct), timeout, cancellationToken);
|
catch (Exception exception)
|
||||||
});
|
{
|
||||||
|
if (IsWrappedException(exception))
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
WrapException(exception);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_writerSemaphore.Release();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<MqttBasePacket> ReceivePacketAsync(TimeSpan timeout, CancellationToken cancellationToken)
|
public async Task<MqttBasePacket> ReceivePacketAsync(TimeSpan timeout, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
ThrowIfDisposed();
|
ThrowIfDisposed();
|
||||||
|
|
||||||
MqttBasePacket packet = null;
|
try
|
||||||
await ExecuteAndWrapExceptionAsync(async () =>
|
|
||||||
{
|
{
|
||||||
ReceivedMqttPacket receivedMqttPacket;
|
ReceivedMqttPacket receivedMqttPacket;
|
||||||
|
|
||||||
if (timeout > TimeSpan.Zero)
|
if (timeout > TimeSpan.Zero)
|
||||||
{
|
{
|
||||||
receivedMqttPacket = await Internal.TaskExtensions.TimeoutAfter(ct => ReceiveAsync(_channel, ct), timeout, cancellationToken).ConfigureAwait(false);
|
receivedMqttPacket = await Internal.TaskExtensions.TimeoutAfterAsync(ct => ReceiveAsync(_channel, ct), timeout, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -94,28 +135,35 @@ namespace MQTTnet.Adapter
|
|||||||
|
|
||||||
if (receivedMqttPacket == null || cancellationToken.IsCancellationRequested)
|
if (receivedMqttPacket == null || cancellationToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
packet = PacketSerializer.Deserialize(receivedMqttPacket);
|
var packet = PacketSerializer.Deserialize(receivedMqttPacket);
|
||||||
if (packet == null)
|
if (packet == null)
|
||||||
{
|
{
|
||||||
throw new MqttProtocolViolationException("Received malformed packet.");
|
throw new MqttProtocolViolationException("Received malformed packet.");
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.Verbose("RX <<< {0}", packet);
|
_logger.Verbose("RX <<< {0}", packet);
|
||||||
}).ConfigureAwait(false);
|
|
||||||
|
return packet;
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
if (IsWrappedException(exception))
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
return packet;
|
WrapException(exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<ReceivedMqttPacket> ReceiveAsync(IMqttChannel channel, CancellationToken cancellationToken)
|
private async Task<ReceivedMqttPacket> ReceiveAsync(IMqttChannel channel, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var fixedHeader = await MqttPacketReader.ReadFixedHeaderAsync(channel, cancellationToken).ConfigureAwait(false);
|
var fixedHeader = await MqttPacketReader.ReadFixedHeaderAsync(channel, cancellationToken).ConfigureAwait(false);
|
||||||
if (fixedHeader == null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -138,7 +186,9 @@ namespace MQTTnet.Adapter
|
|||||||
chunkSize = bytesLeft;
|
chunkSize = bytesLeft;
|
||||||
}
|
}
|
||||||
|
|
||||||
var readBytes = await channel.ReadAsync(body, bodyOffset, chunkSize, cancellationToken) .ConfigureAwait(false);
|
// 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).GetAwaiter().GetResult();
|
||||||
if (readBytes <= 0)
|
if (readBytes <= 0)
|
||||||
{
|
{
|
||||||
ExceptionHelper.ThrowGracefulSocketClose();
|
ExceptionHelper.ThrowGracefulSocketClose();
|
||||||
@@ -147,7 +197,7 @@ namespace MQTTnet.Adapter
|
|||||||
bodyOffset += readBytes;
|
bodyOffset += readBytes;
|
||||||
} while (bodyOffset < body.Length);
|
} while (bodyOffset < body.Length);
|
||||||
|
|
||||||
return new ReceivedMqttPacket(fixedHeader.Flags, new MqttPacketBodyReader(body));
|
return new ReceivedMqttPacket(fixedHeader.Flags, new MqttPacketBodyReader(body, 0));
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -155,42 +205,6 @@ namespace MQTTnet.Adapter
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task ExecuteAndWrapExceptionAsync(Func<Task> action)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await action().ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
catch (Exception exception)
|
|
||||||
{
|
|
||||||
if (exception is TaskCanceledException ||
|
|
||||||
exception is OperationCanceledException ||
|
|
||||||
exception is MqttCommunicationTimedOutException ||
|
|
||||||
exception is MqttCommunicationException)
|
|
||||||
{
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (exception is IOException && exception.InnerException is SocketException socketException)
|
|
||||||
{
|
|
||||||
if (socketException.SocketErrorCode == SocketError.ConnectionAborted)
|
|
||||||
{
|
|
||||||
throw new OperationCanceledException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (exception is COMException comException)
|
|
||||||
{
|
|
||||||
if ((uint)comException.HResult == ErrorOperationAborted)
|
|
||||||
{
|
|
||||||
throw new OperationCanceledException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new MqttCommunicationException(exception);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_isDisposed = true;
|
_isDisposed = true;
|
||||||
@@ -205,5 +219,34 @@ namespace MQTTnet.Adapter
|
|||||||
throw new ObjectDisposedException(nameof(MqttChannelAdapter));
|
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)
|
||||||
|
{
|
||||||
|
throw new OperationCanceledException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exception is COMException comException)
|
||||||
|
{
|
||||||
|
if ((uint)comException.HResult == ErrorOperationAborted)
|
||||||
|
{
|
||||||
|
throw new OperationCanceledException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new MqttCommunicationException(exception);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ namespace MQTTnet.Client
|
|||||||
{
|
{
|
||||||
private readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider();
|
private readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider();
|
||||||
private readonly Stopwatch _sendTracker = new Stopwatch();
|
private readonly Stopwatch _sendTracker = new Stopwatch();
|
||||||
private readonly SemaphoreSlim _disconnectLock = new SemaphoreSlim(1, 1);
|
private readonly object _disconnectLock = new object();
|
||||||
private readonly MqttPacketDispatcher _packetDispatcher = new MqttPacketDispatcher();
|
private readonly MqttPacketDispatcher _packetDispatcher = new MqttPacketDispatcher();
|
||||||
|
|
||||||
private readonly IMqttClientAdapterFactory _adapterFactory;
|
private readonly IMqttClientAdapterFactory _adapterFactory;
|
||||||
@@ -215,7 +215,7 @@ namespace MQTTnet.Client
|
|||||||
|
|
||||||
private async Task DisconnectInternalAsync(Task sender, Exception exception)
|
private async Task DisconnectInternalAsync(Task sender, Exception exception)
|
||||||
{
|
{
|
||||||
await InitiateDisconnectAsync().ConfigureAwait(false);
|
InitiateDisconnect();
|
||||||
|
|
||||||
var clientWasConnected = IsConnected;
|
var clientWasConnected = IsConnected;
|
||||||
IsConnected = false;
|
IsConnected = false;
|
||||||
@@ -249,45 +249,38 @@ namespace MQTTnet.Client
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task InitiateDisconnectAsync()
|
private void InitiateDisconnect()
|
||||||
{
|
{
|
||||||
await _disconnectLock.WaitAsync().ConfigureAwait(false);
|
lock (_disconnectLock)
|
||||||
try
|
|
||||||
{
|
{
|
||||||
if (_cancellationTokenSource == null || _cancellationTokenSource.IsCancellationRequested)
|
try
|
||||||
{
|
{
|
||||||
return;
|
if (_cancellationTokenSource == null || _cancellationTokenSource.IsCancellationRequested)
|
||||||
}
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
_cancellationTokenSource.Cancel(false);
|
_cancellationTokenSource.Cancel(false);
|
||||||
}
|
}
|
||||||
catch (Exception adapterException)
|
catch (Exception adapterException)
|
||||||
{
|
{
|
||||||
_logger.Warning(adapterException, "Error while initiating disconnect.");
|
_logger.Warning(adapterException, "Error while initiating disconnect.");
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
{
|
|
||||||
_disconnectLock.Release();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task SendAsync(MqttBasePacket packet, CancellationToken cancellationToken)
|
private Task SendAsync(MqttBasePacket packet, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (cancellationToken.IsCancellationRequested)
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
{
|
|
||||||
throw new TaskCanceledException();
|
|
||||||
}
|
|
||||||
|
|
||||||
_sendTracker.Restart();
|
_sendTracker.Restart();
|
||||||
return _adapter.SendPacketAsync(_options.CommunicationTimeout, packet, cancellationToken);
|
|
||||||
|
return _adapter.SendPacketAsync(packet, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<TResponsePacket> SendAndReceiveAsync<TResponsePacket>(MqttBasePacket requestPacket, CancellationToken cancellationToken) where TResponsePacket : MqttBasePacket
|
private async Task<TResponsePacket> SendAndReceiveAsync<TResponsePacket>(MqttBasePacket requestPacket, CancellationToken cancellationToken) where TResponsePacket : MqttBasePacket
|
||||||
{
|
{
|
||||||
if (cancellationToken.IsCancellationRequested)
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
{
|
|
||||||
throw new TaskCanceledException();
|
|
||||||
}
|
|
||||||
|
|
||||||
_sendTracker.Restart();
|
_sendTracker.Restart();
|
||||||
|
|
||||||
@@ -300,8 +293,8 @@ namespace MQTTnet.Client
|
|||||||
var packetAwaiter = _packetDispatcher.AddPacketAwaiter<TResponsePacket>(identifier);
|
var packetAwaiter = _packetDispatcher.AddPacketAwaiter<TResponsePacket>(identifier);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await _adapter.SendPacketAsync(_options.CommunicationTimeout, requestPacket, cancellationToken).ConfigureAwait(false);
|
await _adapter.SendPacketAsync(requestPacket, cancellationToken).ConfigureAwait(false);
|
||||||
var respone = await Internal.TaskExtensions.TimeoutAfter(ct => packetAwaiter.Task, _options.CommunicationTimeout, cancellationToken).ConfigureAwait(false);
|
var respone = await Internal.TaskExtensions.TimeoutAfterAsync(ct => packetAwaiter.Task, _options.CommunicationTimeout, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
return (TResponsePacket)respone;
|
return (TResponsePacket)respone;
|
||||||
}
|
}
|
||||||
@@ -526,7 +519,7 @@ namespace MQTTnet.Client
|
|||||||
{
|
{
|
||||||
await task.ConfigureAwait(false);
|
await task.ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (TaskCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ namespace MQTTnet.Diagnostics
|
|||||||
|
|
||||||
public MqttNetChildLogger(IMqttNetLogger logger, string source)
|
public MqttNetChildLogger(IMqttNetLogger logger, string source)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||||
_source = source;
|
_source = source;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,8 @@
|
|||||||
return "net452";
|
return "net452";
|
||||||
#elif NET461
|
#elif NET461
|
||||||
return "net461";
|
return "net461";
|
||||||
|
#elif NET472
|
||||||
|
return "net472";
|
||||||
#elif NETSTANDARD1_3
|
#elif NETSTANDARD1_3
|
||||||
return "netstandard1.3";
|
return "netstandard1.3";
|
||||||
#elif NETSTANDARD2_0
|
#elif NETSTANDARD2_0
|
||||||
|
|||||||
@@ -40,7 +40,18 @@ namespace MQTTnet.Implementations
|
|||||||
|
|
||||||
public static Func<MqttClientTcpOptions, IEnumerable<ChainValidationResult>> CustomIgnorableServerCertificateErrorsResolver { get; set; }
|
public static Func<MqttClientTcpOptions, IEnumerable<ChainValidationResult>> CustomIgnorableServerCertificateErrorsResolver { get; set; }
|
||||||
|
|
||||||
public string Endpoint => _socket?.Information?.RemoteAddress?.ToString(); // TODO: Check if contains also the port.
|
public string Endpoint
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_socket?.Information != null)
|
||||||
|
{
|
||||||
|
return _socket.Information.RemoteAddress + ":" + _socket.Information.RemotePort;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async Task ConnectAsync(CancellationToken cancellationToken)
|
public async Task ConnectAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
@@ -81,10 +92,13 @@ namespace MQTTnet.Implementations
|
|||||||
return _readStream.ReadAsync(buffer, offset, count, cancellationToken);
|
return _readStream.ReadAsync(buffer, offset, count, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
public Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
await _writeStream.WriteAsync(buffer, offset, count, cancellationToken);
|
// In the write method only the internal buffer will be filled. So here is no
|
||||||
await _writeStream.FlushAsync(cancellationToken);
|
// async/await required. The real network transmit is done when calling the
|
||||||
|
// Flush method.
|
||||||
|
_writeStream.Write(buffer, offset, count);
|
||||||
|
return _writeStream.FlushAsync(cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#if NET452 || NET461 || NETSTANDARD1_3 || NETSTANDARD2_0
|
#if !WINDOWS_UWP
|
||||||
using System;
|
using System;
|
||||||
using System.Net.Security;
|
using System.Net.Security;
|
||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#if NET452 || NET461 || NETSTANDARD1_3 || NETSTANDARD2_0
|
#if !WINDOWS_UWP
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#if NET452 || NET461 || NETSTANDARD1_3 || NETSTANDARD2_0
|
#if !WINDOWS_UWP
|
||||||
using System;
|
using System;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Security;
|
using System.Net.Security;
|
||||||
@@ -78,7 +78,8 @@ namespace MQTTnet.Implementations
|
|||||||
await sslStream.AuthenticateAsServerAsync(_tlsCertificate, false, SslProtocols.Tls12, false).ConfigureAwait(false);
|
await sslStream.AuthenticateAsServerAsync(_tlsCertificate, false, SslProtocols.Tls12, false).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.Verbose($"Client '{clientSocket.RemoteEndPoint}' accepted by TCP listener '{_socket.LocalEndPoint}, {_addressFamily}'.");
|
var protocol = _addressFamily == AddressFamily.InterNetwork ? "ipv4" : "ipv6";
|
||||||
|
_logger.Verbose($"Client '{clientSocket.RemoteEndPoint}' accepted by TCP listener '{_socket.LocalEndPoint}, {protocol}'.");
|
||||||
|
|
||||||
var clientAdapter = new MqttChannelAdapter(new MqttTcpChannel(clientSocket, sslStream), new MqttPacketSerializer(), _logger);
|
var clientAdapter = new MqttChannelAdapter(new MqttTcpChannel(clientSocket, sslStream), new MqttPacketSerializer(), _logger);
|
||||||
ClientAccepted?.Invoke(this, new MqttServerAdapterClientAcceptedEventArgs(clientAdapter));
|
ClientAccepted?.Invoke(this, new MqttServerAdapterClientAcceptedEventArgs(clientAdapter));
|
||||||
@@ -104,7 +105,7 @@ namespace MQTTnet.Implementations
|
|||||||
{
|
{
|
||||||
_socket?.Dispose();
|
_socket?.Dispose();
|
||||||
|
|
||||||
#if NETSTANDARD1_3 || NETSTANDARD2_0 || NET461
|
#if NETSTANDARD1_3 || NETSTANDARD2_0 || NET461 || NET472
|
||||||
_tlsCertificate?.Dispose();
|
_tlsCertificate?.Dispose();
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,8 +11,10 @@ namespace MQTTnet.Internal
|
|||||||
private readonly LinkedList<TaskCompletionSource<bool>> _waiters = new LinkedList<TaskCompletionSource<bool>>();
|
private readonly LinkedList<TaskCompletionSource<bool>> _waiters = new LinkedList<TaskCompletionSource<bool>>();
|
||||||
private bool _isSignaled;
|
private bool _isSignaled;
|
||||||
|
|
||||||
public AsyncAutoResetEvent() : this(false)
|
public AsyncAutoResetEvent()
|
||||||
{ }
|
: this(false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public AsyncAutoResetEvent(bool signaled)
|
public AsyncAutoResetEvent(bool signaled)
|
||||||
{
|
{
|
||||||
@@ -58,27 +60,24 @@ namespace MQTTnet.Internal
|
|||||||
}
|
}
|
||||||
|
|
||||||
var winner = await Task.WhenAny(tcs.Task, Task.Delay(timeout, cancellationToken)).ConfigureAwait(false);
|
var winner = await Task.WhenAny(tcs.Task, Task.Delay(timeout, cancellationToken)).ConfigureAwait(false);
|
||||||
if (winner == tcs.Task)
|
var taskWasSignaled = winner == tcs.Task;
|
||||||
|
if (taskWasSignaled)
|
||||||
{
|
{
|
||||||
// The task was signaled.
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
// We timed-out; remove our reference to the task.
|
||||||
|
// This is an O(n) operation since waiters is a LinkedList<T>.
|
||||||
|
lock (_waiters)
|
||||||
{
|
{
|
||||||
// We timed-out; remove our reference to the task.
|
_waiters.Remove(tcs);
|
||||||
// This is an O(n) operation since waiters is a LinkedList<T>.
|
|
||||||
lock (_waiters)
|
if (winner.Status == TaskStatus.Canceled)
|
||||||
{
|
{
|
||||||
_waiters.Remove(tcs);
|
throw new OperationCanceledException(cancellationToken);
|
||||||
if (winner.Status == TaskStatus.Canceled)
|
|
||||||
{
|
|
||||||
throw new OperationCanceledException(cancellationToken);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new TimeoutException();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
throw new TimeoutException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ namespace MQTTnet.Internal
|
|||||||
|
|
||||||
public Task<IDisposable> LockAsync(CancellationToken cancellationToken)
|
public Task<IDisposable> LockAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
Task wait = _semaphore.WaitAsync(cancellationToken);
|
var wait = _semaphore.WaitAsync(cancellationToken);
|
||||||
return wait.IsCompleted ?
|
return wait.IsCompleted ?
|
||||||
_releaser :
|
_releaser :
|
||||||
wait.ContinueWith((_, state) => (IDisposable)state,
|
wait.ContinueWith((_, state) => (IDisposable)state,
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ namespace MQTTnet.Internal
|
|||||||
{
|
{
|
||||||
public static class TaskExtensions
|
public static class TaskExtensions
|
||||||
{
|
{
|
||||||
public static async Task TimeoutAfter(Func<CancellationToken, Task> action, TimeSpan timeout, CancellationToken cancellationToken)
|
public static async Task TimeoutAfterAsync(Func<CancellationToken, Task> action, TimeSpan timeout, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (action == null) throw new ArgumentNullException(nameof(action));
|
if (action == null) throw new ArgumentNullException(nameof(action));
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@ namespace MQTTnet.Internal
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<TResult> TimeoutAfter<TResult>(Func<CancellationToken, Task<TResult>> action, TimeSpan timeout, CancellationToken cancellationToken)
|
public static async Task<TResult> TimeoutAfterAsync<TResult>(Func<CancellationToken, Task<TResult>> action, TimeSpan timeout, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (action == null) throw new ArgumentNullException(nameof(action));
|
if (action == null) throw new ArgumentNullException(nameof(action));
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,14 @@ namespace MQTTnet
|
|||||||
return new MqttClient(new MqttClientAdapterFactory(), logger);
|
return new MqttClient(new MqttClientAdapterFactory(), logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IMqttClient CreateMqttClient(IMqttNetLogger logger, IMqttClientAdapterFactory mqttClientAdapterFactory)
|
||||||
|
{
|
||||||
|
if (logger == null) throw new ArgumentNullException(nameof(logger));
|
||||||
|
if (mqttClientAdapterFactory == null) throw new ArgumentNullException(nameof(mqttClientAdapterFactory));
|
||||||
|
|
||||||
|
return new MqttClient(mqttClientAdapterFactory, logger);
|
||||||
|
}
|
||||||
|
|
||||||
public IMqttServer CreateMqttServer()
|
public IMqttServer CreateMqttServer()
|
||||||
{
|
{
|
||||||
var logger = new MqttNetLogger();
|
var logger = new MqttNetLogger();
|
||||||
|
|||||||
@@ -1,48 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace MQTTnet.Serializer
|
|
||||||
{
|
|
||||||
public 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 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++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -12,7 +12,10 @@ namespace MQTTnet.Serializer
|
|||||||
}
|
}
|
||||||
|
|
||||||
var buffer = new byte[source.Count];
|
var buffer = new byte[source.Count];
|
||||||
Buffer.BlockCopy(source.Array, source.Offset, buffer, 0, buffer.Length);
|
if (buffer.Length > 0)
|
||||||
|
{
|
||||||
|
Array.Copy(source.Array, source.Offset, buffer, 0, buffer.Length);
|
||||||
|
}
|
||||||
|
|
||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,5 +11,7 @@ namespace MQTTnet.Serializer
|
|||||||
ArraySegment<byte> Serialize(MqttBasePacket mqttPacket);
|
ArraySegment<byte> Serialize(MqttBasePacket mqttPacket);
|
||||||
|
|
||||||
MqttBasePacket Deserialize(ReceivedMqttPacket receivedMqttPacket);
|
MqttBasePacket Deserialize(ReceivedMqttPacket receivedMqttPacket);
|
||||||
|
|
||||||
|
void FreeBuffer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
namespace MQTTnet.Serializer
|
namespace MQTTnet.Serializer
|
||||||
{
|
{
|
||||||
public class MqttFixedHeader
|
public struct MqttFixedHeader
|
||||||
{
|
{
|
||||||
public MqttFixedHeader(byte flags, int remainingLength)
|
public MqttFixedHeader(byte flags, int remainingLength)
|
||||||
{
|
{
|
||||||
@@ -10,6 +10,6 @@
|
|||||||
|
|
||||||
public byte Flags { get; }
|
public byte Flags { get; }
|
||||||
|
|
||||||
public int RemainingLength { get; set; }
|
public int RemainingLength { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,9 +8,10 @@ namespace MQTTnet.Serializer
|
|||||||
private readonly byte[] _buffer;
|
private readonly byte[] _buffer;
|
||||||
private int _offset;
|
private int _offset;
|
||||||
|
|
||||||
public MqttPacketBodyReader(byte[] buffer)
|
public MqttPacketBodyReader(byte[] buffer, int offset)
|
||||||
{
|
{
|
||||||
_buffer = buffer;
|
_buffer = buffer;
|
||||||
|
_offset = offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int Length => _buffer.Length - _offset;
|
public int Length => _buffer.Length - _offset;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Threading;
|
using System;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MQTTnet.Channel;
|
using MQTTnet.Channel;
|
||||||
using MQTTnet.Exceptions;
|
using MQTTnet.Exceptions;
|
||||||
@@ -8,23 +9,27 @@ namespace MQTTnet.Serializer
|
|||||||
{
|
{
|
||||||
public static class MqttPacketReader
|
public static class MqttPacketReader
|
||||||
{
|
{
|
||||||
|
[ThreadStatic]
|
||||||
|
private static byte[] _fixedHeaderBuffer;
|
||||||
|
|
||||||
|
[ThreadStatic]
|
||||||
|
private static byte[] _singleByteBuffer;
|
||||||
|
|
||||||
public static async Task<MqttFixedHeader> ReadFixedHeaderAsync(IMqttChannel channel, CancellationToken cancellationToken)
|
public static async Task<MqttFixedHeader> ReadFixedHeaderAsync(IMqttChannel channel, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// The MQTT fixed header contains 1 byte of flags and at least 1 byte for the remaining data length.
|
// The MQTT fixed header contains 1 byte of flags and at least 1 byte for the remaining data length.
|
||||||
// So in all cases at least 2 bytes must be read for a complete MQTT packet.
|
// So in all cases at least 2 bytes must be read for a complete MQTT packet.
|
||||||
var buffer = new byte[2];
|
// async/await is used here because the next packet is received in a couple of minutes so the performance
|
||||||
|
// impact is acceptable according to a useless waiting thread.
|
||||||
|
var buffer = InitializeFixedHeaderBuffer();
|
||||||
var totalBytesRead = 0;
|
var totalBytesRead = 0;
|
||||||
|
|
||||||
while (totalBytesRead < buffer.Length)
|
while (totalBytesRead < buffer.Length)
|
||||||
{
|
{
|
||||||
var bytesRead = await channel.ReadAsync(buffer, 0, buffer.Length - totalBytesRead, cancellationToken).ConfigureAwait(false);
|
var bytesRead = await channel.ReadAsync(buffer, totalBytesRead, buffer.Length - totalBytesRead, cancellationToken).ConfigureAwait(false);
|
||||||
if (bytesRead <= 0)
|
if (bytesRead <= 0)
|
||||||
{
|
{
|
||||||
if (cancellationToken.IsCancellationRequested)
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
ExceptionHelper.ThrowGracefulSocketClose();
|
ExceptionHelper.ThrowGracefulSocketClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,11 +42,11 @@ namespace MQTTnet.Serializer
|
|||||||
return new MqttFixedHeader(buffer[0], 0);
|
return new MqttFixedHeader(buffer[0], 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
var bodyLength = await ReadBodyLengthAsync(channel, buffer[1], cancellationToken).ConfigureAwait(false);
|
var bodyLength = ReadBodyLength(channel, buffer[1], cancellationToken);
|
||||||
return new MqttFixedHeader(buffer[0], bodyLength);
|
return new MqttFixedHeader(buffer[0], bodyLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<int> ReadBodyLengthAsync(IMqttChannel channel, byte initialEncodedByte, CancellationToken cancellationToken)
|
private static int ReadBodyLength(IMqttChannel channel, byte initialEncodedByte, 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.
|
// 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 = 128;
|
var multiplier = 128;
|
||||||
@@ -50,7 +55,13 @@ namespace MQTTnet.Serializer
|
|||||||
|
|
||||||
while ((encodedByte & 128) != 0)
|
while ((encodedByte & 128) != 0)
|
||||||
{
|
{
|
||||||
encodedByte = await ReadByteAsync(channel, cancellationToken).ConfigureAwait(false);
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
// Here the async/await pattern is not used becuase the overhead of context switches
|
||||||
|
// is too big for reading 1 byte in a row. We expect that the remaining data was sent
|
||||||
|
// directly after the initial bytes. If the client disconnects just in this moment we
|
||||||
|
// will get an exception anyway.
|
||||||
|
encodedByte = ReadByte(channel, cancellationToken);
|
||||||
|
|
||||||
value += (byte)(encodedByte & 127) * multiplier;
|
value += (byte)(encodedByte & 127) * multiplier;
|
||||||
if (multiplier > 128 * 128 * 128)
|
if (multiplier > 128 * 128 * 128)
|
||||||
@@ -64,16 +75,27 @@ namespace MQTTnet.Serializer
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<byte> ReadByteAsync(IMqttChannel channel, CancellationToken cancellationToken)
|
private static byte ReadByte(IMqttChannel channel, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var buffer = new byte[1];
|
var buffer = InitializeSingleByteBuffer();
|
||||||
var readCount = await channel.ReadAsync(buffer, 0, 1, cancellationToken).ConfigureAwait(false);
|
var readCount = channel.ReadAsync(buffer, 0, 1, cancellationToken).GetAwaiter().GetResult();
|
||||||
if (readCount <= 0)
|
if (readCount <= 0)
|
||||||
{
|
{
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
ExceptionHelper.ThrowGracefulSocketClose();
|
ExceptionHelper.ThrowGracefulSocketClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
return buffer[0];
|
return buffer[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static byte[] InitializeFixedHeaderBuffer()
|
||||||
|
{
|
||||||
|
return _fixedHeaderBuffer ?? (_fixedHeaderBuffer = new byte[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] InitializeSingleByteBuffer()
|
||||||
|
{
|
||||||
|
return _singleByteBuffer ?? (_singleByteBuffer = new byte[1]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
using MQTTnet.Packets;
|
using MQTTnet.Packets;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using MQTTnet.Adapter;
|
using MQTTnet.Adapter;
|
||||||
|
|
||||||
@@ -12,65 +11,33 @@ namespace MQTTnet.Serializer
|
|||||||
{
|
{
|
||||||
private const int FixedHeaderSize = 1;
|
private const int FixedHeaderSize = 1;
|
||||||
|
|
||||||
|
private readonly MqttPacketWriter _packetWriter = new MqttPacketWriter();
|
||||||
|
|
||||||
public MqttProtocolVersion ProtocolVersion { get; set; } = MqttProtocolVersion.V311;
|
public MqttProtocolVersion ProtocolVersion { get; set; } = MqttProtocolVersion.V311;
|
||||||
|
|
||||||
public ArraySegment<byte> Serialize(MqttBasePacket packet)
|
public ArraySegment<byte> Serialize(MqttBasePacket packet)
|
||||||
{
|
{
|
||||||
if (packet == null) throw new ArgumentNullException(nameof(packet));
|
if (packet == null) throw new ArgumentNullException(nameof(packet));
|
||||||
|
|
||||||
using (var stream = new MemoryStream(128))
|
// Leave enough head space for max header size (fixed + 4 variable remaining length = 5 bytes)
|
||||||
{
|
_packetWriter.Reset();
|
||||||
// Leave enough head space for max header size (fixed + 4 variable remaining length = 5 bytes)
|
_packetWriter.Seek(5);
|
||||||
stream.Seek(5, SeekOrigin.Begin);
|
|
||||||
|
|
||||||
var fixedHeader = SerializePacket(packet, stream);
|
var fixedHeader = SerializePacket(packet, _packetWriter);
|
||||||
var remainingLength = (int)stream.Length - 5;
|
var remainingLength = _packetWriter.Length - 5;
|
||||||
|
|
||||||
var remainingLengthBuffer = MqttPacketWriter.EncodeRemainingLength(remainingLength);
|
var remainingLengthBuffer = MqttPacketWriter.EncodeRemainingLength(remainingLength);
|
||||||
|
|
||||||
var headerSize = FixedHeaderSize + remainingLengthBuffer.Count;
|
var headerSize = FixedHeaderSize + remainingLengthBuffer.Count;
|
||||||
var headerOffset = 5 - headerSize;
|
var headerOffset = 5 - headerSize;
|
||||||
|
|
||||||
// Position cursor on correct offset on beginining of array (has leading 0x0)
|
// Position cursor on correct offset on beginining of array (has leading 0x0)
|
||||||
stream.Seek(headerOffset, SeekOrigin.Begin);
|
_packetWriter.Seek(headerOffset);
|
||||||
stream.WriteByte(fixedHeader);
|
_packetWriter.Write(fixedHeader);
|
||||||
stream.Write(remainingLengthBuffer.Array, remainingLengthBuffer.Offset, remainingLengthBuffer.Count);
|
_packetWriter.Write(remainingLengthBuffer.Array, remainingLengthBuffer.Offset, remainingLengthBuffer.Count);
|
||||||
|
|
||||||
#if NET461 || NET452 || NETSTANDARD2_0
|
var buffer = _packetWriter.GetBuffer();
|
||||||
var buffer = stream.GetBuffer();
|
return new ArraySegment<byte>(buffer, headerOffset, _packetWriter.Length - headerOffset);
|
||||||
return new ArraySegment<byte>(buffer, headerOffset, (int)stream.Length - headerOffset);
|
|
||||||
#else
|
|
||||||
if (stream.TryGetBuffer(out var segment))
|
|
||||||
{
|
|
||||||
return new ArraySegment<byte>(segment.Array, headerOffset, segment.Count - headerOffset);
|
|
||||||
}
|
|
||||||
|
|
||||||
var buffer = stream.ToArray();
|
|
||||||
return new ArraySegment<byte>(buffer, headerOffset, buffer.Length - headerOffset);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte SerializePacket(MqttBasePacket packet, Stream stream)
|
|
||||||
{
|
|
||||||
switch (packet)
|
|
||||||
{
|
|
||||||
case MqttConnectPacket connectPacket: return Serialize(connectPacket, stream);
|
|
||||||
case MqttConnAckPacket connAckPacket: return Serialize(connAckPacket, stream);
|
|
||||||
case MqttDisconnectPacket _: return SerializeEmptyPacket(MqttControlPacketType.Disconnect);
|
|
||||||
case MqttPingReqPacket _: return SerializeEmptyPacket(MqttControlPacketType.PingReq);
|
|
||||||
case MqttPingRespPacket _: return SerializeEmptyPacket(MqttControlPacketType.PingResp);
|
|
||||||
case MqttPublishPacket publishPacket: return Serialize(publishPacket, stream);
|
|
||||||
case MqttPubAckPacket pubAckPacket: return Serialize(pubAckPacket, stream);
|
|
||||||
case MqttPubRecPacket pubRecPacket: return Serialize(pubRecPacket, stream);
|
|
||||||
case MqttPubRelPacket pubRelPacket: return Serialize(pubRelPacket, stream);
|
|
||||||
case MqttPubCompPacket pubCompPacket: return Serialize(pubCompPacket, stream);
|
|
||||||
case MqttSubscribePacket subscribePacket: return Serialize(subscribePacket, stream);
|
|
||||||
case MqttSubAckPacket subAckPacket: return Serialize(subAckPacket, stream);
|
|
||||||
case MqttUnsubscribePacket unsubscribePacket: return Serialize(unsubscribePacket, stream);
|
|
||||||
case MqttUnsubAckPacket unsubAckPacket: return Serialize(unsubAckPacket, stream);
|
|
||||||
default: throw new MqttProtocolViolationException("Packet type invalid.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public MqttBasePacket Deserialize(ReceivedMqttPacket receivedMqttPacket)
|
public MqttBasePacket Deserialize(ReceivedMqttPacket receivedMqttPacket)
|
||||||
@@ -104,6 +71,33 @@ namespace MQTTnet.Serializer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void FreeBuffer()
|
||||||
|
{
|
||||||
|
_packetWriter.FreeBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte SerializePacket(MqttBasePacket packet, MqttPacketWriter packetWriter)
|
||||||
|
{
|
||||||
|
switch (packet)
|
||||||
|
{
|
||||||
|
case MqttConnectPacket connectPacket: return Serialize(connectPacket, packetWriter);
|
||||||
|
case MqttConnAckPacket connAckPacket: return Serialize(connAckPacket, packetWriter);
|
||||||
|
case MqttDisconnectPacket _: return SerializeEmptyPacket(MqttControlPacketType.Disconnect);
|
||||||
|
case MqttPingReqPacket _: return SerializeEmptyPacket(MqttControlPacketType.PingReq);
|
||||||
|
case MqttPingRespPacket _: return SerializeEmptyPacket(MqttControlPacketType.PingResp);
|
||||||
|
case MqttPublishPacket publishPacket: return Serialize(publishPacket, packetWriter);
|
||||||
|
case MqttPubAckPacket pubAckPacket: return Serialize(pubAckPacket, packetWriter);
|
||||||
|
case MqttPubRecPacket pubRecPacket: return Serialize(pubRecPacket, packetWriter);
|
||||||
|
case MqttPubRelPacket pubRelPacket: return Serialize(pubRelPacket, packetWriter);
|
||||||
|
case MqttPubCompPacket pubCompPacket: return Serialize(pubCompPacket, packetWriter);
|
||||||
|
case MqttSubscribePacket subscribePacket: return Serialize(subscribePacket, packetWriter);
|
||||||
|
case MqttSubAckPacket subAckPacket: return Serialize(subAckPacket, packetWriter);
|
||||||
|
case MqttUnsubscribePacket unsubscribePacket: return Serialize(unsubscribePacket, packetWriter);
|
||||||
|
case MqttUnsubAckPacket unsubAckPacket: return Serialize(unsubAckPacket, packetWriter);
|
||||||
|
default: throw new MqttProtocolViolationException("Packet type invalid.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static MqttBasePacket DeserializeUnsubAck(MqttPacketBodyReader body)
|
private static MqttBasePacket DeserializeUnsubAck(MqttPacketBodyReader body)
|
||||||
{
|
{
|
||||||
ThrowIfBodyIsEmpty(body);
|
ThrowIfBodyIsEmpty(body);
|
||||||
@@ -192,20 +186,18 @@ namespace MQTTnet.Serializer
|
|||||||
|
|
||||||
private static MqttBasePacket DeserializePublish(ReceivedMqttPacket receivedMqttPacket)
|
private static MqttBasePacket DeserializePublish(ReceivedMqttPacket receivedMqttPacket)
|
||||||
{
|
{
|
||||||
var body = receivedMqttPacket.Body;
|
ThrowIfBodyIsEmpty(receivedMqttPacket.Body);
|
||||||
ThrowIfBodyIsEmpty(body);
|
|
||||||
|
|
||||||
var fixedHeader = new ByteReader(receivedMqttPacket.FixedHeader);
|
var retain = (receivedMqttPacket.FixedHeader & 0x1) > 0;
|
||||||
var retain = fixedHeader.Read();
|
var qualityOfServiceLevel = (MqttQualityOfServiceLevel)(receivedMqttPacket.FixedHeader >> 1 & 0x3);
|
||||||
var qualityOfServiceLevel = (MqttQualityOfServiceLevel)fixedHeader.Read(2);
|
var dup = (receivedMqttPacket.FixedHeader & 0x3) > 0;
|
||||||
var dup = fixedHeader.Read();
|
|
||||||
|
|
||||||
var topic = body.ReadStringWithLengthPrefix();
|
var topic = receivedMqttPacket.Body.ReadStringWithLengthPrefix();
|
||||||
|
|
||||||
ushort? packetIdentifier = null;
|
ushort? packetIdentifier = null;
|
||||||
if (qualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce)
|
if (qualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce)
|
||||||
{
|
{
|
||||||
packetIdentifier = body.ReadUInt16();
|
packetIdentifier = receivedMqttPacket.Body.ReadUInt16();
|
||||||
}
|
}
|
||||||
|
|
||||||
var packet = new MqttPublishPacket
|
var packet = new MqttPublishPacket
|
||||||
@@ -213,7 +205,7 @@ namespace MQTTnet.Serializer
|
|||||||
PacketIdentifier = packetIdentifier,
|
PacketIdentifier = packetIdentifier,
|
||||||
Retain = retain,
|
Retain = retain,
|
||||||
Topic = topic,
|
Topic = topic,
|
||||||
Payload = body.ReadRemainingData().ToArray(),
|
Payload = receivedMqttPacket.Body.ReadRemainingData().ToArray(),
|
||||||
QualityOfServiceLevel = qualityOfServiceLevel,
|
QualityOfServiceLevel = qualityOfServiceLevel,
|
||||||
Dup = dup
|
Dup = dup
|
||||||
};
|
};
|
||||||
@@ -253,8 +245,8 @@ namespace MQTTnet.Serializer
|
|||||||
throw new MqttProtocolViolationException($"Protocol name ({protocolName}) is not supported.");
|
throw new MqttProtocolViolationException($"Protocol name ({protocolName}) is not supported.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var connectFlags = new ByteReader(body.ReadByte());
|
var connectFlags = body.ReadByte();
|
||||||
if (connectFlags.Read())
|
if ((connectFlags & 0x1) > 0)
|
||||||
{
|
{
|
||||||
throw new MqttProtocolViolationException("The first bit of the Connect Flags must be set to 0.");
|
throw new MqttProtocolViolationException("The first bit of the Connect Flags must be set to 0.");
|
||||||
}
|
}
|
||||||
@@ -262,14 +254,14 @@ namespace MQTTnet.Serializer
|
|||||||
var packet = new MqttConnectPacket
|
var packet = new MqttConnectPacket
|
||||||
{
|
{
|
||||||
ProtocolVersion = protocolVersion,
|
ProtocolVersion = protocolVersion,
|
||||||
CleanSession = connectFlags.Read()
|
CleanSession = (connectFlags & 0x2) > 0
|
||||||
};
|
};
|
||||||
|
|
||||||
var willFlag = connectFlags.Read();
|
var willFlag = (connectFlags & 0x4) > 0;
|
||||||
var willQoS = connectFlags.Read(2);
|
var willQoS = (connectFlags & 0x18) >> 3;
|
||||||
var willRetain = connectFlags.Read();
|
var willRetain = (connectFlags & 0x20) > 0;
|
||||||
var passwordFlag = connectFlags.Read();
|
var passwordFlag = (connectFlags & 0x40) > 0;
|
||||||
var usernameFlag = connectFlags.Read();
|
var usernameFlag = (connectFlags & 0x80) > 0;
|
||||||
|
|
||||||
packet.KeepAlivePeriod = body.ReadUInt16();
|
packet.KeepAlivePeriod = body.ReadUInt16();
|
||||||
packet.ClientId = body.ReadStringWithLengthPrefix();
|
packet.ClientId = body.ReadStringWithLengthPrefix();
|
||||||
@@ -322,11 +314,11 @@ namespace MQTTnet.Serializer
|
|||||||
|
|
||||||
var packet = new MqttConnAckPacket();
|
var packet = new MqttConnAckPacket();
|
||||||
|
|
||||||
var firstByteReader = new ByteReader(body.ReadByte());
|
var acknowledgeFlags = body.ReadByte();
|
||||||
|
|
||||||
if (ProtocolVersion == MqttProtocolVersion.V311)
|
if (ProtocolVersion == MqttProtocolVersion.V311)
|
||||||
{
|
{
|
||||||
packet.IsSessionPresent = firstByteReader.Read();
|
packet.IsSessionPresent = (acknowledgeFlags & 0x1) > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
packet.ConnectReturnCode = (MqttConnectReturnCode)body.ReadByte();
|
packet.ConnectReturnCode = (MqttConnectReturnCode)body.ReadByte();
|
||||||
@@ -344,46 +336,46 @@ namespace MQTTnet.Serializer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local
|
||||||
private static void ValidatePublishPacket(MqttPublishPacket packet)
|
private static void ValidatePublishPacket(MqttPublishPacket packet)
|
||||||
{
|
{
|
||||||
if (packet == null) throw new ArgumentNullException(nameof(packet));
|
|
||||||
|
|
||||||
if (packet.QualityOfServiceLevel == 0 && packet.Dup)
|
if (packet.QualityOfServiceLevel == 0 && packet.Dup)
|
||||||
{
|
{
|
||||||
throw new MqttProtocolViolationException("Dup flag must be false for QoS 0 packets [MQTT-3.3.1-2].");
|
throw new MqttProtocolViolationException("Dup flag must be false for QoS 0 packets [MQTT-3.3.1-2].");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte Serialize(MqttConnectPacket packet, Stream stream)
|
private byte Serialize(MqttConnectPacket packet, MqttPacketWriter packetWriter)
|
||||||
{
|
{
|
||||||
ValidateConnectPacket(packet);
|
ValidateConnectPacket(packet);
|
||||||
|
|
||||||
// Write variable header
|
// Write variable header
|
||||||
if (ProtocolVersion == MqttProtocolVersion.V311)
|
if (ProtocolVersion == MqttProtocolVersion.V311)
|
||||||
{
|
{
|
||||||
stream.WriteWithLengthPrefix("MQTT");
|
packetWriter.WriteWithLengthPrefix("MQTT");
|
||||||
stream.WriteByte(4); // 3.1.2.2 Protocol Level 4
|
packetWriter.Write(4); // 3.1.2.2 Protocol Level 4
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
stream.WriteWithLengthPrefix("MQIsdp");
|
packetWriter.WriteWithLengthPrefix("MQIsdp");
|
||||||
stream.WriteByte(3); // Protocol Level 3
|
packetWriter.Write(3); // Protocol Level 3
|
||||||
}
|
}
|
||||||
|
|
||||||
var connectFlags = new ByteWriter(); // 3.1.2.3 Connect Flags
|
byte connectFlags = 0x0;
|
||||||
connectFlags.Write(false); // Reserved
|
if (packet.CleanSession)
|
||||||
connectFlags.Write(packet.CleanSession);
|
{
|
||||||
connectFlags.Write(packet.WillMessage != null);
|
connectFlags |= 0x2;
|
||||||
|
}
|
||||||
|
|
||||||
if (packet.WillMessage != null)
|
if (packet.WillMessage != null)
|
||||||
{
|
{
|
||||||
connectFlags.Write((int)packet.WillMessage.QualityOfServiceLevel, 2);
|
connectFlags |= 0x4;
|
||||||
connectFlags.Write(packet.WillMessage.Retain);
|
connectFlags |= (byte)((byte)packet.WillMessage.QualityOfServiceLevel << 3);
|
||||||
}
|
|
||||||
else
|
if (packet.WillMessage.Retain)
|
||||||
{
|
{
|
||||||
connectFlags.Write(0, 2);
|
connectFlags |= 0x20;
|
||||||
connectFlags.Write(false);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (packet.Password != null && packet.Username == null)
|
if (packet.Password != null && packet.Username == null)
|
||||||
@@ -391,72 +383,82 @@ namespace MQTTnet.Serializer
|
|||||||
throw new MqttProtocolViolationException("If the User Name Flag is set to 0, the Password Flag MUST be set to 0 [MQTT-3.1.2-22].");
|
throw new MqttProtocolViolationException("If the User Name Flag is set to 0, the Password Flag MUST be set to 0 [MQTT-3.1.2-22].");
|
||||||
}
|
}
|
||||||
|
|
||||||
connectFlags.Write(packet.Password != null);
|
if (packet.Password != null)
|
||||||
connectFlags.Write(packet.Username != null);
|
|
||||||
|
|
||||||
stream.Write(connectFlags);
|
|
||||||
stream.Write(packet.KeepAlivePeriod);
|
|
||||||
stream.WriteWithLengthPrefix(packet.ClientId);
|
|
||||||
|
|
||||||
if (packet.WillMessage != null)
|
|
||||||
{
|
{
|
||||||
stream.WriteWithLengthPrefix(packet.WillMessage.Topic);
|
connectFlags |= 0x40;
|
||||||
stream.WriteWithLengthPrefix(packet.WillMessage.Payload);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (packet.Username != null)
|
if (packet.Username != null)
|
||||||
{
|
{
|
||||||
stream.WriteWithLengthPrefix(packet.Username);
|
connectFlags |= 0x80;
|
||||||
|
}
|
||||||
|
|
||||||
|
packetWriter.Write(connectFlags);
|
||||||
|
packetWriter.Write(packet.KeepAlivePeriod);
|
||||||
|
packetWriter.WriteWithLengthPrefix(packet.ClientId);
|
||||||
|
|
||||||
|
if (packet.WillMessage != null)
|
||||||
|
{
|
||||||
|
packetWriter.WriteWithLengthPrefix(packet.WillMessage.Topic);
|
||||||
|
packetWriter.WriteWithLengthPrefix(packet.WillMessage.Payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (packet.Username != null)
|
||||||
|
{
|
||||||
|
packetWriter.WriteWithLengthPrefix(packet.Username);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (packet.Password != null)
|
if (packet.Password != null)
|
||||||
{
|
{
|
||||||
stream.WriteWithLengthPrefix(packet.Password);
|
packetWriter.WriteWithLengthPrefix(packet.Password);
|
||||||
}
|
}
|
||||||
|
|
||||||
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.Connect);
|
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.Connect);
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte Serialize(MqttConnAckPacket packet, Stream stream)
|
private byte Serialize(MqttConnAckPacket packet, MqttPacketWriter packetWriter)
|
||||||
{
|
{
|
||||||
if (ProtocolVersion == MqttProtocolVersion.V310)
|
if (ProtocolVersion == MqttProtocolVersion.V310)
|
||||||
{
|
{
|
||||||
stream.WriteByte(0);
|
packetWriter.Write(0);
|
||||||
}
|
}
|
||||||
else if (ProtocolVersion == MqttProtocolVersion.V311)
|
else if (ProtocolVersion == MqttProtocolVersion.V311)
|
||||||
{
|
{
|
||||||
var connectAcknowledgeFlags = new ByteWriter();
|
byte connectAcknowledgeFlags = 0x0;
|
||||||
connectAcknowledgeFlags.Write(packet.IsSessionPresent);
|
if (packet.IsSessionPresent)
|
||||||
|
{
|
||||||
|
connectAcknowledgeFlags |= 0x1;
|
||||||
|
}
|
||||||
|
|
||||||
stream.Write(connectAcknowledgeFlags);
|
packetWriter.Write(connectAcknowledgeFlags);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw new MqttProtocolViolationException("Protocol version not supported.");
|
throw new MqttProtocolViolationException("Protocol version not supported.");
|
||||||
}
|
}
|
||||||
|
|
||||||
stream.WriteByte((byte)packet.ConnectReturnCode);
|
packetWriter.Write((byte)packet.ConnectReturnCode);
|
||||||
|
|
||||||
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.ConnAck);
|
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.ConnAck);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte Serialize(MqttPubRelPacket packet, Stream stream)
|
private static byte Serialize(MqttPubRelPacket packet, MqttPacketWriter packetWriter)
|
||||||
{
|
{
|
||||||
if (!packet.PacketIdentifier.HasValue)
|
if (!packet.PacketIdentifier.HasValue)
|
||||||
{
|
{
|
||||||
throw new MqttProtocolViolationException("PubRel packet has no packet identifier.");
|
throw new MqttProtocolViolationException("PubRel packet has no packet identifier.");
|
||||||
}
|
}
|
||||||
|
|
||||||
stream.Write(packet.PacketIdentifier.Value);
|
packetWriter.Write(packet.PacketIdentifier.Value);
|
||||||
|
|
||||||
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.PubRel, 0x02);
|
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.PubRel, 0x02);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte Serialize(MqttPublishPacket packet, Stream stream)
|
private static byte Serialize(MqttPublishPacket packet, MqttPacketWriter packetWriter)
|
||||||
{
|
{
|
||||||
ValidatePublishPacket(packet);
|
ValidatePublishPacket(packet);
|
||||||
|
|
||||||
stream.WriteWithLengthPrefix(packet.Topic);
|
packetWriter.WriteWithLengthPrefix(packet.Topic);
|
||||||
|
|
||||||
if (packet.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce)
|
if (packet.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce)
|
||||||
{
|
{
|
||||||
@@ -465,7 +467,7 @@ namespace MQTTnet.Serializer
|
|||||||
throw new MqttProtocolViolationException("Publish packet has no packet identifier.");
|
throw new MqttProtocolViolationException("Publish packet has no packet identifier.");
|
||||||
}
|
}
|
||||||
|
|
||||||
stream.Write(packet.PacketIdentifier.Value);
|
packetWriter.Write(packet.PacketIdentifier.Value);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -477,7 +479,7 @@ namespace MQTTnet.Serializer
|
|||||||
|
|
||||||
if (packet.Payload?.Length > 0)
|
if (packet.Payload?.Length > 0)
|
||||||
{
|
{
|
||||||
stream.Write(packet.Payload, 0, packet.Payload.Length);
|
packetWriter.Write(packet.Payload, 0, packet.Payload.Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
byte fixedHeader = 0;
|
byte fixedHeader = 0;
|
||||||
@@ -497,43 +499,43 @@ namespace MQTTnet.Serializer
|
|||||||
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.Publish, fixedHeader);
|
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.Publish, fixedHeader);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte Serialize(MqttPubAckPacket packet, Stream stream)
|
private static byte Serialize(MqttPubAckPacket packet, MqttPacketWriter packetWriter)
|
||||||
{
|
{
|
||||||
if (!packet.PacketIdentifier.HasValue)
|
if (!packet.PacketIdentifier.HasValue)
|
||||||
{
|
{
|
||||||
throw new MqttProtocolViolationException("PubAck packet has no packet identifier.");
|
throw new MqttProtocolViolationException("PubAck packet has no packet identifier.");
|
||||||
}
|
}
|
||||||
|
|
||||||
stream.Write(packet.PacketIdentifier.Value);
|
packetWriter.Write(packet.PacketIdentifier.Value);
|
||||||
|
|
||||||
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.PubAck);
|
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.PubAck);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte Serialize(MqttPubRecPacket packet, Stream stream)
|
private static byte Serialize(MqttPubRecPacket packet, MqttPacketWriter packetWriter)
|
||||||
{
|
{
|
||||||
if (!packet.PacketIdentifier.HasValue)
|
if (!packet.PacketIdentifier.HasValue)
|
||||||
{
|
{
|
||||||
throw new MqttProtocolViolationException("PubRec packet has no packet identifier.");
|
throw new MqttProtocolViolationException("PubRec packet has no packet identifier.");
|
||||||
}
|
}
|
||||||
|
|
||||||
stream.Write(packet.PacketIdentifier.Value);
|
packetWriter.Write(packet.PacketIdentifier.Value);
|
||||||
|
|
||||||
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.PubRec);
|
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.PubRec);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte Serialize(MqttPubCompPacket packet, Stream stream)
|
private static byte Serialize(MqttPubCompPacket packet, MqttPacketWriter packetWriter)
|
||||||
{
|
{
|
||||||
if (!packet.PacketIdentifier.HasValue)
|
if (!packet.PacketIdentifier.HasValue)
|
||||||
{
|
{
|
||||||
throw new MqttProtocolViolationException("PubComp packet has no packet identifier.");
|
throw new MqttProtocolViolationException("PubComp packet has no packet identifier.");
|
||||||
}
|
}
|
||||||
|
|
||||||
stream.Write(packet.PacketIdentifier.Value);
|
packetWriter.Write(packet.PacketIdentifier.Value);
|
||||||
|
|
||||||
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.PubComp);
|
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.PubComp);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte Serialize(MqttSubscribePacket packet, Stream stream)
|
private static byte Serialize(MqttSubscribePacket packet, MqttPacketWriter packetWriter)
|
||||||
{
|
{
|
||||||
if (!packet.TopicFilters.Any()) throw new MqttProtocolViolationException("At least one topic filter must be set [MQTT-3.8.3-3].");
|
if (!packet.TopicFilters.Any()) throw new MqttProtocolViolationException("At least one topic filter must be set [MQTT-3.8.3-3].");
|
||||||
|
|
||||||
@@ -542,41 +544,41 @@ namespace MQTTnet.Serializer
|
|||||||
throw new MqttProtocolViolationException("Subscribe packet has no packet identifier.");
|
throw new MqttProtocolViolationException("Subscribe packet has no packet identifier.");
|
||||||
}
|
}
|
||||||
|
|
||||||
stream.Write(packet.PacketIdentifier.Value);
|
packetWriter.Write(packet.PacketIdentifier.Value);
|
||||||
|
|
||||||
if (packet.TopicFilters?.Count > 0)
|
if (packet.TopicFilters?.Count > 0)
|
||||||
{
|
{
|
||||||
foreach (var topicFilter in packet.TopicFilters)
|
foreach (var topicFilter in packet.TopicFilters)
|
||||||
{
|
{
|
||||||
stream.WriteWithLengthPrefix(topicFilter.Topic);
|
packetWriter.WriteWithLengthPrefix(topicFilter.Topic);
|
||||||
stream.WriteByte((byte)topicFilter.QualityOfServiceLevel);
|
packetWriter.Write((byte)topicFilter.QualityOfServiceLevel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.Subscribe, 0x02);
|
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.Subscribe, 0x02);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte Serialize(MqttSubAckPacket packet, Stream stream)
|
private static byte Serialize(MqttSubAckPacket packet, MqttPacketWriter packetWriter)
|
||||||
{
|
{
|
||||||
if (!packet.PacketIdentifier.HasValue)
|
if (!packet.PacketIdentifier.HasValue)
|
||||||
{
|
{
|
||||||
throw new MqttProtocolViolationException("SubAck packet has no packet identifier.");
|
throw new MqttProtocolViolationException("SubAck packet has no packet identifier.");
|
||||||
}
|
}
|
||||||
|
|
||||||
stream.Write(packet.PacketIdentifier.Value);
|
packetWriter.Write(packet.PacketIdentifier.Value);
|
||||||
|
|
||||||
if (packet.SubscribeReturnCodes?.Any() == true)
|
if (packet.SubscribeReturnCodes?.Any() == true)
|
||||||
{
|
{
|
||||||
foreach (var packetSubscribeReturnCode in packet.SubscribeReturnCodes)
|
foreach (var packetSubscribeReturnCode in packet.SubscribeReturnCodes)
|
||||||
{
|
{
|
||||||
stream.WriteByte((byte)packetSubscribeReturnCode);
|
packetWriter.Write((byte)packetSubscribeReturnCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.SubAck);
|
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.SubAck);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte Serialize(MqttUnsubscribePacket packet, Stream stream)
|
private static byte Serialize(MqttUnsubscribePacket packet, MqttPacketWriter packetWriter)
|
||||||
{
|
{
|
||||||
if (!packet.TopicFilters.Any()) throw new MqttProtocolViolationException("At least one topic filter must be set [MQTT-3.10.3-2].");
|
if (!packet.TopicFilters.Any()) throw new MqttProtocolViolationException("At least one topic filter must be set [MQTT-3.10.3-2].");
|
||||||
|
|
||||||
@@ -585,27 +587,27 @@ namespace MQTTnet.Serializer
|
|||||||
throw new MqttProtocolViolationException("Unsubscribe packet has no packet identifier.");
|
throw new MqttProtocolViolationException("Unsubscribe packet has no packet identifier.");
|
||||||
}
|
}
|
||||||
|
|
||||||
stream.Write(packet.PacketIdentifier.Value);
|
packetWriter.Write(packet.PacketIdentifier.Value);
|
||||||
|
|
||||||
if (packet.TopicFilters?.Any() == true)
|
if (packet.TopicFilters?.Any() == true)
|
||||||
{
|
{
|
||||||
foreach (var topicFilter in packet.TopicFilters)
|
foreach (var topicFilter in packet.TopicFilters)
|
||||||
{
|
{
|
||||||
stream.WriteWithLengthPrefix(topicFilter);
|
packetWriter.WriteWithLengthPrefix(topicFilter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.Unsubscibe, 0x02);
|
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.Unsubscibe, 0x02);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte Serialize(MqttUnsubAckPacket packet, Stream stream)
|
private static byte Serialize(MqttUnsubAckPacket packet, MqttPacketWriter packetWriter)
|
||||||
{
|
{
|
||||||
if (!packet.PacketIdentifier.HasValue)
|
if (!packet.PacketIdentifier.HasValue)
|
||||||
{
|
{
|
||||||
throw new MqttProtocolViolationException("UnsubAck packet has no packet identifier.");
|
throw new MqttProtocolViolationException("UnsubAck packet has no packet identifier.");
|
||||||
}
|
}
|
||||||
|
|
||||||
stream.Write(packet.PacketIdentifier.Value);
|
packetWriter.Write(packet.PacketIdentifier.Value);
|
||||||
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.UnsubAck);
|
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.UnsubAck);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -614,6 +616,7 @@ namespace MQTTnet.Serializer
|
|||||||
return MqttPacketWriter.BuildFixedHeader(type);
|
return MqttPacketWriter.BuildFixedHeader(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local
|
||||||
private static void ThrowIfBodyIsEmpty(MqttPacketBodyReader body)
|
private static void ThrowIfBodyIsEmpty(MqttPacketBodyReader body)
|
||||||
{
|
{
|
||||||
if (body == null || body.Length == 0)
|
if (body == null || body.Length == 0)
|
||||||
|
|||||||
@@ -1,12 +1,25 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
|
|
||||||
namespace MQTTnet.Serializer
|
namespace MQTTnet.Serializer
|
||||||
{
|
{
|
||||||
public static class MqttPacketWriter
|
/// <summary>
|
||||||
|
/// This is a custom implementation of a memory stream which provides only MQTTnet relevant features.
|
||||||
|
/// The goal is to avoid lots of argument checks like in the original stream. The growth rule is the
|
||||||
|
/// same as for the original MemoryStream in .net. Also this implementation allows accessing the internal
|
||||||
|
/// buffer for all platforms and .net framework versions (which is not available at the regular MemoryStream).
|
||||||
|
/// </summary>
|
||||||
|
public class MqttPacketWriter
|
||||||
{
|
{
|
||||||
|
public static int MaxBufferSize = 4096;
|
||||||
|
|
||||||
|
private byte[] _buffer = new byte[128];
|
||||||
|
|
||||||
|
private int _position;
|
||||||
|
|
||||||
|
public int Length { get; private set; }
|
||||||
|
|
||||||
public static byte BuildFixedHeader(MqttControlPacketType packetType, byte flags = 0)
|
public static byte BuildFixedHeader(MqttControlPacketType packetType, byte flags = 0)
|
||||||
{
|
{
|
||||||
var fixedHeader = (int)packetType << 4;
|
var fixedHeader = (int)packetType << 4;
|
||||||
@@ -14,33 +27,6 @@ namespace MQTTnet.Serializer
|
|||||||
return (byte)fixedHeader;
|
return (byte)fixedHeader;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Write(this Stream stream, ushort value)
|
|
||||||
{
|
|
||||||
var buffer = BitConverter.GetBytes(value);
|
|
||||||
stream.WriteByte(buffer[1]);
|
|
||||||
stream.WriteByte(buffer[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Write(this Stream stream, ByteWriter value)
|
|
||||||
{
|
|
||||||
if (value == null) throw new ArgumentNullException(nameof(value));
|
|
||||||
|
|
||||||
stream.WriteByte(value.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void WriteWithLengthPrefix(this Stream stream, string value)
|
|
||||||
{
|
|
||||||
stream.WriteWithLengthPrefix(Encoding.UTF8.GetBytes(value ?? string.Empty));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void WriteWithLengthPrefix(this Stream stream, byte[] value)
|
|
||||||
{
|
|
||||||
var length = (ushort)value.Length;
|
|
||||||
|
|
||||||
stream.Write(length);
|
|
||||||
stream.Write(value, 0, length);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ArraySegment<byte> EncodeRemainingLength(int length)
|
public static ArraySegment<byte> EncodeRemainingLength(int length)
|
||||||
{
|
{
|
||||||
// write the encoded remaining length right aligned on the 4 byte buffer
|
// write the encoded remaining length right aligned on the 4 byte buffer
|
||||||
@@ -69,5 +55,112 @@ namespace MQTTnet.Serializer
|
|||||||
|
|
||||||
return new ArraySegment<byte>(buffer, 0, bufferOffset);
|
return new ArraySegment<byte>(buffer, 0, bufferOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void WriteWithLengthPrefix(string value)
|
||||||
|
{
|
||||||
|
WriteWithLengthPrefix(Encoding.UTF8.GetBytes(value ?? string.Empty));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WriteWithLengthPrefix(byte[] value)
|
||||||
|
{
|
||||||
|
EnsureAdditionalCapacity(value.Length + 2);
|
||||||
|
|
||||||
|
Write((ushort)value.Length);
|
||||||
|
Write(value, 0, value.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Write(byte @byte)
|
||||||
|
{
|
||||||
|
EnsureAdditionalCapacity(1);
|
||||||
|
|
||||||
|
_buffer[_position] = @byte;
|
||||||
|
IncreasePostition(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Write(ushort value)
|
||||||
|
{
|
||||||
|
EnsureAdditionalCapacity(2);
|
||||||
|
|
||||||
|
_buffer[_position] = (byte)(value >> 8);
|
||||||
|
IncreasePostition(1);
|
||||||
|
_buffer[_position] = (byte)value;
|
||||||
|
IncreasePostition(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Write(byte[] array, int offset, int count)
|
||||||
|
{
|
||||||
|
EnsureAdditionalCapacity(count);
|
||||||
|
|
||||||
|
Array.Copy(array, offset, _buffer, _position, count);
|
||||||
|
IncreasePostition(count);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
Length = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Seek(int offset)
|
||||||
|
{
|
||||||
|
EnsureCapacity(offset);
|
||||||
|
_position = offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] GetBuffer()
|
||||||
|
{
|
||||||
|
return _buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void FreeBuffer()
|
||||||
|
{
|
||||||
|
// This method frees the used memory by shrinking the buffer. This is required because the buffer
|
||||||
|
// is used across several messages. In general this is not a big issue because subsequent Ping packages
|
||||||
|
// have the same size but a very big publish package with 100 MB of payload will increase the buffer
|
||||||
|
// a lot and the size will never reduced. So this method tries to find a size which can be held in
|
||||||
|
// memory for a long time without causing troubles.
|
||||||
|
|
||||||
|
if (_buffer.Length < MaxBufferSize)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Array.Resize(ref _buffer, MaxBufferSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsureAdditionalCapacity(int additionalCapacity)
|
||||||
|
{
|
||||||
|
var freeSpace = _buffer.Length - _position;
|
||||||
|
if (freeSpace >= additionalCapacity)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
EnsureCapacity(additionalCapacity - freeSpace);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsureCapacity(int capacity)
|
||||||
|
{
|
||||||
|
if (_buffer.Length >= capacity)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var newBufferLength = _buffer.Length;
|
||||||
|
while (newBufferLength < capacity)
|
||||||
|
{
|
||||||
|
newBufferLength *= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
Array.Resize(ref _buffer, newBufferLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void IncreasePostition(int length)
|
||||||
|
{
|
||||||
|
_position += length;
|
||||||
|
if (_position > Length)
|
||||||
|
{
|
||||||
|
Length = _position;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using MQTTnet.Adapter;
|
||||||
|
using MQTTnet.Packets;
|
||||||
|
|
||||||
|
namespace MQTTnet.Server
|
||||||
|
{
|
||||||
|
public interface IMqttClientSession : IDisposable
|
||||||
|
{
|
||||||
|
string ClientId { get; }
|
||||||
|
void FillStatus(MqttClientSessionStatus status);
|
||||||
|
|
||||||
|
void EnqueueApplicationMessage(MqttClientSession senderClientSession, MqttPublishPacket publishPacket);
|
||||||
|
void ClearPendingApplicationMessages();
|
||||||
|
|
||||||
|
Task<bool> RunAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter adapter);
|
||||||
|
void Stop(MqttClientDisconnectType disconnectType);
|
||||||
|
|
||||||
|
Task SubscribeAsync(IList<TopicFilter> topicFilters);
|
||||||
|
Task UnsubscribeAsync(IList<string> topicFilters);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,19 +12,17 @@ namespace MQTTnet.Server
|
|||||||
private readonly Stopwatch _lastPacketReceivedTracker = new Stopwatch();
|
private readonly Stopwatch _lastPacketReceivedTracker = new Stopwatch();
|
||||||
private readonly Stopwatch _lastNonKeepAlivePacketReceivedTracker = new Stopwatch();
|
private readonly Stopwatch _lastNonKeepAlivePacketReceivedTracker = new Stopwatch();
|
||||||
|
|
||||||
|
private readonly IMqttClientSession _clientSession;
|
||||||
private readonly IMqttNetChildLogger _logger;
|
private readonly IMqttNetChildLogger _logger;
|
||||||
private readonly string _clientId;
|
|
||||||
private readonly Action _callback;
|
|
||||||
|
|
||||||
private bool _isPaused;
|
private bool _isPaused;
|
||||||
private Task _workerTask;
|
|
||||||
|
public MqttClientKeepAliveMonitor(IMqttClientSession clientSession, IMqttNetChildLogger logger)
|
||||||
public MqttClientKeepAliveMonitor(string clientId, Action callback, IMqttNetChildLogger logger)
|
|
||||||
{
|
{
|
||||||
if (logger == null) throw new ArgumentNullException(nameof(logger));
|
if (logger == null) throw new ArgumentNullException(nameof(logger));
|
||||||
|
|
||||||
_clientId = clientId;
|
_clientSession = clientSession ?? throw new ArgumentNullException(nameof(clientSession));
|
||||||
_callback = callback;
|
|
||||||
_logger = logger.CreateChildLogger(nameof(MqttClientKeepAliveMonitor));
|
_logger = logger.CreateChildLogger(nameof(MqttClientKeepAliveMonitor));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,7 +37,7 @@ namespace MQTTnet.Server
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_workerTask = Task.Run(() => RunAsync(keepAlivePeriod, cancellationToken), cancellationToken);
|
Task.Run(() => RunAsync(keepAlivePeriod, cancellationToken), cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Pause()
|
public void Pause()
|
||||||
@@ -74,9 +72,9 @@ namespace MQTTnet.Server
|
|||||||
// Values described here: [MQTT-3.1.2-24].
|
// Values described here: [MQTT-3.1.2-24].
|
||||||
if (!_isPaused && _lastPacketReceivedTracker.Elapsed.TotalSeconds > keepAlivePeriod * 1.5D)
|
if (!_isPaused && _lastPacketReceivedTracker.Elapsed.TotalSeconds > keepAlivePeriod * 1.5D)
|
||||||
{
|
{
|
||||||
_logger.Warning(null, "Client '{0}': Did not receive any packet or keep alive signal.", _clientId);
|
_logger.Warning(null, "Client '{0}': Did not receive any packet or keep alive signal.", _clientSession.ClientId);
|
||||||
_callback();
|
_clientSession.Stop(MqttClientDisconnectType.NotClean);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,11 +86,11 @@ namespace MQTTnet.Server
|
|||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
_logger.Error(exception, "Client '{0}': Unhandled exception while checking keep alive timeouts.", _clientId);
|
_logger.Error(exception, "Client '{0}': Unhandled exception while checking keep alive timeouts.", _clientSession.ClientId);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
_logger.Verbose("Client {0}: Stopped checking keep alive timeout.", _clientId);
|
_logger.Verbose("Client {0}: Stopped checking keep alive timeout.", _clientSession.ClientId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+41
-23
@@ -1,5 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Generic;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MQTTnet.Adapter;
|
using MQTTnet.Adapter;
|
||||||
@@ -11,25 +11,34 @@ using MQTTnet.Protocol;
|
|||||||
|
|
||||||
namespace MQTTnet.Server
|
namespace MQTTnet.Server
|
||||||
{
|
{
|
||||||
public class MqttClientPendingMessagesQueue : IDisposable
|
public class MqttClientPendingPacketsQueue : IDisposable
|
||||||
{
|
{
|
||||||
|
private readonly Queue<MqttBasePacket> _queue = new Queue<MqttBasePacket>();
|
||||||
private readonly AsyncAutoResetEvent _queueAutoResetEvent = new AsyncAutoResetEvent();
|
private readonly AsyncAutoResetEvent _queueAutoResetEvent = new AsyncAutoResetEvent();
|
||||||
|
|
||||||
private readonly IMqttServerOptions _options;
|
private readonly IMqttServerOptions _options;
|
||||||
private readonly MqttClientSession _clientSession;
|
private readonly MqttClientSession _clientSession;
|
||||||
private readonly IMqttNetChildLogger _logger;
|
private readonly IMqttNetChildLogger _logger;
|
||||||
|
|
||||||
private ConcurrentQueue<MqttBasePacket> _queue = new ConcurrentQueue<MqttBasePacket>();
|
public MqttClientPendingPacketsQueue(IMqttServerOptions options, MqttClientSession clientSession, IMqttNetChildLogger logger)
|
||||||
|
|
||||||
public MqttClientPendingMessagesQueue(IMqttServerOptions options, MqttClientSession clientSession, IMqttNetChildLogger logger)
|
|
||||||
{
|
{
|
||||||
if (logger == null) throw new ArgumentNullException(nameof(logger));
|
if (logger == null) throw new ArgumentNullException(nameof(logger));
|
||||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||||
_clientSession = clientSession ?? throw new ArgumentNullException(nameof(clientSession));
|
_clientSession = clientSession ?? throw new ArgumentNullException(nameof(clientSession));
|
||||||
|
|
||||||
_logger = logger.CreateChildLogger(nameof(MqttClientPendingMessagesQueue));
|
_logger = logger.CreateChildLogger(nameof(MqttClientPendingPacketsQueue));
|
||||||
}
|
}
|
||||||
|
|
||||||
public int Count => _queue.Count;
|
public int Count
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
lock (_queue)
|
||||||
|
{
|
||||||
|
return _queue.Count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void Start(IMqttChannelAdapter adapter, CancellationToken cancellationToken)
|
public void Start(IMqttChannelAdapter adapter, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
@@ -42,25 +51,29 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
Task.Run(() => SendQueuedPacketsAsync(adapter, cancellationToken), cancellationToken);
|
Task.Run(() => SendQueuedPacketsAsync(adapter, cancellationToken), cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Enqueue(MqttBasePacket packet)
|
public void Enqueue(MqttBasePacket packet)
|
||||||
{
|
{
|
||||||
if (packet == null) throw new ArgumentNullException(nameof(packet));
|
if (packet == null) throw new ArgumentNullException(nameof(packet));
|
||||||
|
|
||||||
if (_queue.Count >= _options.MaxPendingMessagesPerClient)
|
lock (_queue)
|
||||||
{
|
{
|
||||||
if (_options.PendingMessagesOverflowStrategy == MqttPendingMessagesOverflowStrategy.DropNewMessage)
|
if (_queue.Count >= _options.MaxPendingMessagesPerClient)
|
||||||
{
|
{
|
||||||
return;
|
if (_options.PendingMessagesOverflowStrategy == MqttPendingMessagesOverflowStrategy.DropNewMessage)
|
||||||
}
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (_options.PendingMessagesOverflowStrategy == MqttPendingMessagesOverflowStrategy.DropOldestQueuedMessage)
|
if (_options.PendingMessagesOverflowStrategy == MqttPendingMessagesOverflowStrategy.DropOldestQueuedMessage)
|
||||||
{
|
{
|
||||||
_queue.TryDequeue(out _);
|
_queue.Dequeue();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_queue.Enqueue(packet);
|
||||||
}
|
}
|
||||||
|
|
||||||
_queue.Enqueue(packet);
|
|
||||||
_queueAutoResetEvent.Set();
|
_queueAutoResetEvent.Set();
|
||||||
|
|
||||||
_logger.Verbose("Enqueued packet (ClientId: {0}).", _clientSession.ClientId);
|
_logger.Verbose("Enqueued packet (ClientId: {0}).", _clientSession.ClientId);
|
||||||
@@ -68,13 +81,14 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
public void Clear()
|
public void Clear()
|
||||||
{
|
{
|
||||||
var newQueue = new ConcurrentQueue<MqttBasePacket>();
|
lock (_queue)
|
||||||
Interlocked.Exchange(ref _queue, newQueue);
|
{
|
||||||
|
_queue.Clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SendQueuedPacketsAsync(IMqttChannelAdapter adapter, CancellationToken cancellationToken)
|
private async Task SendQueuedPacketsAsync(IMqttChannelAdapter adapter, CancellationToken cancellationToken)
|
||||||
@@ -100,13 +114,17 @@ namespace MQTTnet.Server
|
|||||||
MqttBasePacket packet = null;
|
MqttBasePacket packet = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (_queue.IsEmpty)
|
lock (_queue)
|
||||||
{
|
{
|
||||||
await _queueAutoResetEvent.WaitOneAsync(cancellationToken).ConfigureAwait(false);
|
if (_queue.Count > 0)
|
||||||
|
{
|
||||||
|
packet = _queue.Dequeue();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_queue.TryDequeue(out packet))
|
if (packet == null)
|
||||||
{
|
{
|
||||||
|
await _queueAutoResetEvent.WaitOneAsync(cancellationToken).ConfigureAwait(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,7 +133,7 @@ namespace MQTTnet.Server
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await adapter.SendPacketAsync(_options.DefaultCommunicationTimeout, packet, cancellationToken).ConfigureAwait(false);
|
adapter.SendPacketAsync(packet, cancellationToken).GetAwaiter().GetResult();
|
||||||
|
|
||||||
_logger.Verbose("Enqueued packet sent (ClientId: {0}).", _clientSession.ClientId);
|
_logger.Verbose("Enqueued packet sent (ClientId: {0}).", _clientSession.ClientId);
|
||||||
}
|
}
|
||||||
@@ -12,13 +12,13 @@ using MQTTnet.Protocol;
|
|||||||
|
|
||||||
namespace MQTTnet.Server
|
namespace MQTTnet.Server
|
||||||
{
|
{
|
||||||
public class MqttClientSession : IDisposable
|
public class MqttClientSession : IMqttClientSession
|
||||||
{
|
{
|
||||||
private readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider();
|
private readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider();
|
||||||
|
|
||||||
private readonly MqttRetainedMessagesManager _retainedMessagesManager;
|
private readonly MqttRetainedMessagesManager _retainedMessagesManager;
|
||||||
private readonly MqttClientKeepAliveMonitor _keepAliveMonitor;
|
private readonly MqttClientKeepAliveMonitor _keepAliveMonitor;
|
||||||
private readonly MqttClientPendingMessagesQueue _pendingMessagesQueue;
|
private readonly MqttClientPendingPacketsQueue _pendingPacketsQueue;
|
||||||
private readonly MqttClientSubscriptionsManager _subscriptionsManager;
|
private readonly MqttClientSubscriptionsManager _subscriptionsManager;
|
||||||
private readonly MqttClientSessionsManager _sessionsManager;
|
private readonly MqttClientSessionsManager _sessionsManager;
|
||||||
|
|
||||||
@@ -47,9 +47,9 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
_logger = logger.CreateChildLogger(nameof(MqttClientSession));
|
_logger = logger.CreateChildLogger(nameof(MqttClientSession));
|
||||||
|
|
||||||
_keepAliveMonitor = new MqttClientKeepAliveMonitor(clientId, () => Stop(MqttClientDisconnectType.NotClean), _logger);
|
_keepAliveMonitor = new MqttClientKeepAliveMonitor(this, _logger);
|
||||||
_subscriptionsManager = new MqttClientSubscriptionsManager(clientId, _options, sessionsManager.Server);
|
_subscriptionsManager = new MqttClientSubscriptionsManager(clientId, _options, sessionsManager.Server);
|
||||||
_pendingMessagesQueue = new MqttClientPendingMessagesQueue(_options, this, _logger);
|
_pendingPacketsQueue = new MqttClientPendingPacketsQueue(_options, this, _logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string ClientId { get; }
|
public string ClientId { get; }
|
||||||
@@ -60,7 +60,7 @@ namespace MQTTnet.Server
|
|||||||
status.IsConnected = _adapter != null;
|
status.IsConnected = _adapter != null;
|
||||||
status.Endpoint = _adapter?.Endpoint;
|
status.Endpoint = _adapter?.Endpoint;
|
||||||
status.ProtocolVersion = _adapter?.PacketSerializer?.ProtocolVersion;
|
status.ProtocolVersion = _adapter?.PacketSerializer?.ProtocolVersion;
|
||||||
status.PendingApplicationMessagesCount = _pendingMessagesQueue.Count;
|
status.PendingApplicationMessagesCount = _pendingPacketsQueue.Count;
|
||||||
status.LastPacketReceived = _keepAliveMonitor.LastPacketReceived;
|
status.LastPacketReceived = _keepAliveMonitor.LastPacketReceived;
|
||||||
status.LastNonKeepAlivePacketReceived = _keepAliveMonitor.LastNonKeepAlivePacketReceived;
|
status.LastNonKeepAlivePacketReceived = _keepAliveMonitor.LastNonKeepAlivePacketReceived;
|
||||||
}
|
}
|
||||||
@@ -80,7 +80,7 @@ namespace MQTTnet.Server
|
|||||||
_wasCleanDisconnect = false;
|
_wasCleanDisconnect = false;
|
||||||
_willMessage = connectPacket.WillMessage;
|
_willMessage = connectPacket.WillMessage;
|
||||||
|
|
||||||
_pendingMessagesQueue.Start(adapter, _cancellationTokenSource.Token);
|
_pendingPacketsQueue.Start(adapter, _cancellationTokenSource.Token);
|
||||||
_keepAliveMonitor.Start(connectPacket.KeepAlivePeriod, _cancellationTokenSource.Token);
|
_keepAliveMonitor.Start(connectPacket.KeepAlivePeriod, _cancellationTokenSource.Token);
|
||||||
|
|
||||||
while (!_cancellationTokenSource.IsCancellationRequested)
|
while (!_cancellationTokenSource.IsCancellationRequested)
|
||||||
@@ -89,7 +89,7 @@ namespace MQTTnet.Server
|
|||||||
if (packet != null)
|
if (packet != null)
|
||||||
{
|
{
|
||||||
_keepAliveMonitor.PacketReceived(packet);
|
_keepAliveMonitor.PacketReceived(packet);
|
||||||
await ProcessReceivedPacketAsync(adapter, packet, _cancellationTokenSource.Token).ConfigureAwait(false);
|
ProcessReceivedPacket(adapter, packet, _cancellationTokenSource.Token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -102,7 +102,7 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
if (exception is MqttCommunicationClosedGracefullyException)
|
if (exception is MqttCommunicationClosedGracefullyException)
|
||||||
{
|
{
|
||||||
_logger.Verbose("Client '{0}': Connection closed gracefully.", ClientId); ;
|
_logger.Verbose("Client '{0}': Connection closed gracefully.", ClientId);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -113,7 +113,7 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
_logger.Error(exception, "Client '{0}': Unhandled exception while receiving client packets.", ClientId);
|
_logger.Error(exception, "Client '{0}': Unhandled exception while receiving client packets.", ClientId);
|
||||||
}
|
}
|
||||||
|
|
||||||
Stop(MqttClientDisconnectType.NotClean);
|
Stop(MqttClientDisconnectType.NotClean);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
@@ -123,7 +123,7 @@ namespace MQTTnet.Server
|
|||||||
_adapter.ReadingPacketStarted -= OnAdapterReadingPacketStarted;
|
_adapter.ReadingPacketStarted -= OnAdapterReadingPacketStarted;
|
||||||
_adapter.ReadingPacketCompleted -= OnAdapterReadingPacketCompleted;
|
_adapter.ReadingPacketCompleted -= OnAdapterReadingPacketCompleted;
|
||||||
}
|
}
|
||||||
|
|
||||||
_adapter = null;
|
_adapter = null;
|
||||||
|
|
||||||
_cancellationTokenSource?.Dispose();
|
_cancellationTokenSource?.Dispose();
|
||||||
@@ -149,13 +149,10 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
if (_willMessage != null && !_wasCleanDisconnect)
|
if (_willMessage != null && !_wasCleanDisconnect)
|
||||||
{
|
{
|
||||||
_sessionsManager.StartDispatchApplicationMessage(this, _willMessage);
|
_sessionsManager.EnqueueApplicationMessage(this, _willMessage.ToPublishPacket());
|
||||||
}
|
}
|
||||||
|
|
||||||
_willMessage = null;
|
_willMessage = null;
|
||||||
|
|
||||||
////_pendingMessagesQueue.WaitForCompletion();
|
|
||||||
////_keepAliveMonitor.WaitForCompletion();
|
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -163,18 +160,24 @@ namespace MQTTnet.Server
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void EnqueueApplicationMessage(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage)
|
public void EnqueueApplicationMessage(MqttClientSession senderClientSession, MqttPublishPacket publishPacket)
|
||||||
{
|
{
|
||||||
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
|
if (publishPacket == null) throw new ArgumentNullException(nameof(publishPacket));
|
||||||
|
|
||||||
var checkSubscriptionsResult = _subscriptionsManager.CheckSubscriptions(applicationMessage);
|
var checkSubscriptionsResult = _subscriptionsManager.CheckSubscriptions(publishPacket.Topic, publishPacket.QualityOfServiceLevel);
|
||||||
if (!checkSubscriptionsResult.IsSubscribed)
|
if (!checkSubscriptionsResult.IsSubscribed)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var publishPacket = applicationMessage.ToPublishPacket();
|
publishPacket = new MqttPublishPacket
|
||||||
publishPacket.QualityOfServiceLevel = checkSubscriptionsResult.QualityOfServiceLevel;
|
{
|
||||||
|
Topic = publishPacket.Topic,
|
||||||
|
Payload = publishPacket.Payload,
|
||||||
|
QualityOfServiceLevel = checkSubscriptionsResult.QualityOfServiceLevel,
|
||||||
|
Retain = false,
|
||||||
|
Dup = false
|
||||||
|
};
|
||||||
|
|
||||||
if (publishPacket.QualityOfServiceLevel > 0)
|
if (publishPacket.QualityOfServiceLevel > 0)
|
||||||
{
|
{
|
||||||
@@ -187,16 +190,20 @@ namespace MQTTnet.Server
|
|||||||
senderClientSession?.ClientId,
|
senderClientSession?.ClientId,
|
||||||
ClientId,
|
ClientId,
|
||||||
publishPacket.ToApplicationMessage());
|
publishPacket.ToApplicationMessage());
|
||||||
|
|
||||||
_options.ClientMessageQueueInterceptor?.Invoke(context);
|
_options.ClientMessageQueueInterceptor?.Invoke(context);
|
||||||
|
|
||||||
if (!context.AcceptEnqueue || context.ApplicationMessage == null)
|
if (!context.AcceptEnqueue || context.ApplicationMessage == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
publishPacket.Topic = context.ApplicationMessage.Topic;
|
||||||
|
publishPacket.Payload = context.ApplicationMessage.Payload;
|
||||||
|
publishPacket.QualityOfServiceLevel = context.ApplicationMessage.QualityOfServiceLevel;
|
||||||
}
|
}
|
||||||
|
|
||||||
_pendingMessagesQueue.Enqueue(publishPacket);
|
_pendingPacketsQueue.Enqueue(publishPacket);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task SubscribeAsync(IList<TopicFilter> topicFilters)
|
public Task SubscribeAsync(IList<TopicFilter> topicFilters)
|
||||||
@@ -226,31 +233,39 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
public void ClearPendingApplicationMessages()
|
public void ClearPendingApplicationMessages()
|
||||||
{
|
{
|
||||||
_pendingMessagesQueue.Clear();
|
_pendingPacketsQueue.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_pendingMessagesQueue?.Dispose();
|
_pendingPacketsQueue?.Dispose();
|
||||||
|
|
||||||
_cancellationTokenSource?.Dispose();
|
_cancellationTokenSource?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task ProcessReceivedPacketAsync(IMqttChannelAdapter adapter, MqttBasePacket packet, CancellationToken cancellationToken)
|
private void ProcessReceivedPacket(IMqttChannelAdapter adapter, MqttBasePacket packet, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (packet is MqttPublishPacket publishPacket)
|
if (packet is MqttPublishPacket publishPacket)
|
||||||
{
|
{
|
||||||
return HandleIncomingPublishPacketAsync(adapter, publishPacket, cancellationToken);
|
HandleIncomingPublishPacket(adapter, publishPacket, cancellationToken);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (packet is MqttPingReqPacket)
|
if (packet is MqttPingReqPacket)
|
||||||
{
|
{
|
||||||
return adapter.SendPacketAsync(_options.DefaultCommunicationTimeout, new MqttPingRespPacket(), cancellationToken);
|
adapter.SendPacketAsync(new MqttPingRespPacket(), cancellationToken).GetAwaiter().GetResult();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (packet is MqttPubRelPacket pubRelPacket)
|
if (packet is MqttPubRelPacket pubRelPacket)
|
||||||
{
|
{
|
||||||
return HandleIncomingPubRelPacketAsync(adapter, pubRelPacket, cancellationToken);
|
var responsePacket = new MqttPubCompPacket
|
||||||
|
{
|
||||||
|
PacketIdentifier = pubRelPacket.PacketIdentifier
|
||||||
|
};
|
||||||
|
|
||||||
|
adapter.SendPacketAsync(responsePacket, cancellationToken).GetAwaiter().GetResult();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (packet is MqttPubRecPacket pubRecPacket)
|
if (packet is MqttPubRecPacket pubRecPacket)
|
||||||
@@ -260,40 +275,41 @@ namespace MQTTnet.Server
|
|||||||
PacketIdentifier = pubRecPacket.PacketIdentifier
|
PacketIdentifier = pubRecPacket.PacketIdentifier
|
||||||
};
|
};
|
||||||
|
|
||||||
return adapter.SendPacketAsync(_options.DefaultCommunicationTimeout, responsePacket, cancellationToken);
|
adapter.SendPacketAsync(responsePacket, cancellationToken).GetAwaiter().GetResult();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (packet is MqttPubAckPacket || packet is MqttPubCompPacket)
|
if (packet is MqttPubAckPacket || packet is MqttPubCompPacket)
|
||||||
{
|
{
|
||||||
// Discard message.
|
return;
|
||||||
return Task.FromResult(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (packet is MqttSubscribePacket subscribePacket)
|
if (packet is MqttSubscribePacket subscribePacket)
|
||||||
{
|
{
|
||||||
return HandleIncomingSubscribePacketAsync(adapter, subscribePacket, cancellationToken);
|
HandleIncomingSubscribePacket(adapter, subscribePacket, cancellationToken);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (packet is MqttUnsubscribePacket unsubscribePacket)
|
if (packet is MqttUnsubscribePacket unsubscribePacket)
|
||||||
{
|
{
|
||||||
return HandleIncomingUnsubscribePacketAsync(adapter, unsubscribePacket, cancellationToken);
|
HandleIncomingUnsubscribePacket(adapter, unsubscribePacket, cancellationToken);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (packet is MqttDisconnectPacket)
|
if (packet is MqttDisconnectPacket)
|
||||||
{
|
{
|
||||||
Stop(MqttClientDisconnectType.Clean);
|
Stop(MqttClientDisconnectType.Clean);
|
||||||
return Task.FromResult(0);
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (packet is MqttConnectPacket)
|
if (packet is MqttConnectPacket)
|
||||||
{
|
{
|
||||||
Stop(MqttClientDisconnectType.NotClean);
|
Stop(MqttClientDisconnectType.NotClean);
|
||||||
return Task.FromResult(0);
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.Warning(null, "Client '{0}': Received not supported packet ({1}). Closing connection.", ClientId, packet);
|
_logger.Warning(null, "Client '{0}': Received not supported packet ({1}). Closing connection.", ClientId, packet);
|
||||||
Stop(MqttClientDisconnectType.NotClean);
|
Stop(MqttClientDisconnectType.NotClean);
|
||||||
return Task.FromResult(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void EnqueueSubscribedRetainedMessages(ICollection<TopicFilter> topicFilters)
|
private void EnqueueSubscribedRetainedMessages(ICollection<TopicFilter> topicFilters)
|
||||||
@@ -301,14 +317,14 @@ namespace MQTTnet.Server
|
|||||||
var retainedMessages = _retainedMessagesManager.GetSubscribedMessages(topicFilters);
|
var retainedMessages = _retainedMessagesManager.GetSubscribedMessages(topicFilters);
|
||||||
foreach (var applicationMessage in retainedMessages)
|
foreach (var applicationMessage in retainedMessages)
|
||||||
{
|
{
|
||||||
EnqueueApplicationMessage(null, applicationMessage);
|
EnqueueApplicationMessage(null, applicationMessage.ToPublishPacket());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task HandleIncomingSubscribePacketAsync(IMqttChannelAdapter adapter, MqttSubscribePacket subscribePacket, CancellationToken cancellationToken)
|
private void HandleIncomingSubscribePacket(IMqttChannelAdapter adapter, MqttSubscribePacket subscribePacket, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var subscribeResult = _subscriptionsManager.Subscribe(subscribePacket);
|
var subscribeResult = _subscriptionsManager.Subscribe(subscribePacket);
|
||||||
await adapter.SendPacketAsync(_options.DefaultCommunicationTimeout, subscribeResult.ResponsePacket, cancellationToken).ConfigureAwait(false);
|
adapter.SendPacketAsync(subscribeResult.ResponsePacket, cancellationToken).GetAwaiter().GetResult();
|
||||||
|
|
||||||
if (subscribeResult.CloseConnection)
|
if (subscribeResult.CloseConnection)
|
||||||
{
|
{
|
||||||
@@ -319,30 +335,30 @@ namespace MQTTnet.Server
|
|||||||
EnqueueSubscribedRetainedMessages(subscribePacket.TopicFilters);
|
EnqueueSubscribedRetainedMessages(subscribePacket.TopicFilters);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task HandleIncomingUnsubscribePacketAsync(IMqttChannelAdapter adapter, MqttUnsubscribePacket unsubscribePacket, CancellationToken cancellationToken)
|
private void HandleIncomingUnsubscribePacket(IMqttChannelAdapter adapter, MqttUnsubscribePacket unsubscribePacket, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var unsubscribeResult = _subscriptionsManager.Unsubscribe(unsubscribePacket);
|
var unsubscribeResult = _subscriptionsManager.Unsubscribe(unsubscribePacket);
|
||||||
return adapter.SendPacketAsync(_options.DefaultCommunicationTimeout, unsubscribeResult, cancellationToken);
|
adapter.SendPacketAsync(unsubscribeResult, cancellationToken).GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task HandleIncomingPublishPacketAsync(IMqttChannelAdapter adapter, MqttPublishPacket publishPacket, CancellationToken cancellationToken)
|
private void HandleIncomingPublishPacket(IMqttChannelAdapter adapter, MqttPublishPacket publishPacket, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var applicationMessage = publishPacket.ToApplicationMessage();
|
switch (publishPacket.QualityOfServiceLevel)
|
||||||
|
|
||||||
switch (applicationMessage.QualityOfServiceLevel)
|
|
||||||
{
|
{
|
||||||
case MqttQualityOfServiceLevel.AtMostOnce:
|
case MqttQualityOfServiceLevel.AtMostOnce:
|
||||||
{
|
{
|
||||||
_sessionsManager.StartDispatchApplicationMessage(this, applicationMessage);
|
HandleIncomingPublishPacketWithQoS0(publishPacket);
|
||||||
return Task.FromResult(0);
|
break;
|
||||||
}
|
}
|
||||||
case MqttQualityOfServiceLevel.AtLeastOnce:
|
case MqttQualityOfServiceLevel.AtLeastOnce:
|
||||||
{
|
{
|
||||||
return HandleIncomingPublishPacketWithQoS1(adapter, applicationMessage, publishPacket, cancellationToken);
|
HandleIncomingPublishPacketWithQoS1(adapter, publishPacket, cancellationToken);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
case MqttQualityOfServiceLevel.ExactlyOnce:
|
case MqttQualityOfServiceLevel.ExactlyOnce:
|
||||||
{
|
{
|
||||||
return HandleIncomingPublishPacketWithQoS2(adapter, applicationMessage, publishPacket, cancellationToken);
|
HandleIncomingPublishPacketWithQoS2(adapter, publishPacket, cancellationToken);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
@@ -351,27 +367,40 @@ namespace MQTTnet.Server
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task HandleIncomingPublishPacketWithQoS1(IMqttChannelAdapter adapter, MqttApplicationMessage applicationMessage, MqttPublishPacket publishPacket, CancellationToken cancellationToken)
|
private void HandleIncomingPublishPacketWithQoS0(MqttPublishPacket publishPacket)
|
||||||
{
|
{
|
||||||
_sessionsManager.StartDispatchApplicationMessage(this, applicationMessage);
|
_sessionsManager.EnqueueApplicationMessage(this, publishPacket);
|
||||||
|
|
||||||
var response = new MqttPubAckPacket { PacketIdentifier = publishPacket.PacketIdentifier };
|
|
||||||
return adapter.SendPacketAsync(_options.DefaultCommunicationTimeout, response, cancellationToken);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task HandleIncomingPublishPacketWithQoS2(IMqttChannelAdapter adapter, MqttApplicationMessage applicationMessage, MqttPublishPacket publishPacket, CancellationToken cancellationToken)
|
private void HandleIncomingPublishPacketWithQoS1(
|
||||||
|
IMqttChannelAdapter adapter,
|
||||||
|
MqttPublishPacket publishPacket,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
_sessionsManager.EnqueueApplicationMessage(this, publishPacket);
|
||||||
|
|
||||||
|
var response = new MqttPubAckPacket
|
||||||
|
{
|
||||||
|
PacketIdentifier = publishPacket.PacketIdentifier
|
||||||
|
};
|
||||||
|
|
||||||
|
adapter.SendPacketAsync(response, cancellationToken).GetAwaiter().GetResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleIncomingPublishPacketWithQoS2(
|
||||||
|
IMqttChannelAdapter adapter,
|
||||||
|
MqttPublishPacket publishPacket,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// QoS 2 is implement as method "B" (4.3.3 QoS 2: Exactly once delivery)
|
// QoS 2 is implement as method "B" (4.3.3 QoS 2: Exactly once delivery)
|
||||||
_sessionsManager.StartDispatchApplicationMessage(this, applicationMessage);
|
_sessionsManager.EnqueueApplicationMessage(this, publishPacket);
|
||||||
|
|
||||||
var response = new MqttPubRecPacket { PacketIdentifier = publishPacket.PacketIdentifier };
|
var response = new MqttPubRecPacket
|
||||||
return adapter.SendPacketAsync(_options.DefaultCommunicationTimeout, response, cancellationToken);
|
{
|
||||||
}
|
PacketIdentifier = publishPacket.PacketIdentifier
|
||||||
|
};
|
||||||
|
|
||||||
private Task HandleIncomingPubRelPacketAsync(IMqttChannelAdapter adapter, MqttPubRelPacket pubRelPacket, CancellationToken cancellationToken)
|
adapter.SendPacketAsync(response, cancellationToken).GetAwaiter().GetResult();
|
||||||
{
|
|
||||||
var response = new MqttPubCompPacket { PacketIdentifier = pubRelPacket.PacketIdentifier };
|
|
||||||
return adapter.SendPacketAsync(_options.DefaultCommunicationTimeout, response, cancellationToken);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnAdapterReadingPacketCompleted(object sender, EventArgs e)
|
private void OnAdapterReadingPacketCompleted(object sender, EventArgs e)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MQTTnet.Adapter;
|
using MQTTnet.Adapter;
|
||||||
@@ -14,19 +15,26 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
public class MqttClientSessionsManager : IDisposable
|
public class MqttClientSessionsManager : IDisposable
|
||||||
{
|
{
|
||||||
private readonly ConcurrentDictionary<string, MqttClientSession> _sessions = new ConcurrentDictionary<string, MqttClientSession>();
|
private readonly BlockingCollection<MqttEnqueuedApplicationMessage> _messageQueue = new BlockingCollection<MqttEnqueuedApplicationMessage>();
|
||||||
private readonly AsyncLock _sessionPreparationLock = new AsyncLock();
|
|
||||||
|
/// <summary>
|
||||||
|
/// manual locking dictionaries is faster than using concurrent dictionary
|
||||||
|
/// </summary>
|
||||||
|
private readonly Dictionary<string, MqttClientSession> _sessions = new Dictionary<string, MqttClientSession>();
|
||||||
|
|
||||||
|
private readonly CancellationToken _cancellationToken;
|
||||||
|
|
||||||
private readonly MqttRetainedMessagesManager _retainedMessagesManager;
|
private readonly MqttRetainedMessagesManager _retainedMessagesManager;
|
||||||
private readonly IMqttServerOptions _options;
|
private readonly IMqttServerOptions _options;
|
||||||
private readonly IMqttNetChildLogger _logger;
|
private readonly IMqttNetChildLogger _logger;
|
||||||
|
|
||||||
public MqttClientSessionsManager(IMqttServerOptions options, MqttServer server, MqttRetainedMessagesManager retainedMessagesManager, IMqttNetChildLogger logger)
|
public MqttClientSessionsManager(IMqttServerOptions options, MqttServer server, MqttRetainedMessagesManager retainedMessagesManager, CancellationToken cancellationToken, IMqttNetChildLogger logger)
|
||||||
{
|
{
|
||||||
if (logger == null) throw new ArgumentNullException(nameof(logger));
|
if (logger == null) throw new ArgumentNullException(nameof(logger));
|
||||||
|
|
||||||
_logger = logger.CreateChildLogger(nameof(MqttClientSessionsManager));
|
_logger = logger.CreateChildLogger(nameof(MqttClientSessionsManager));
|
||||||
|
|
||||||
|
_cancellationToken = cancellationToken;
|
||||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||||
Server = server ?? throw new ArgumentNullException(nameof(server));
|
Server = server ?? throw new ArgumentNullException(nameof(server));
|
||||||
_retainedMessagesManager = retainedMessagesManager ?? throw new ArgumentNullException(nameof(retainedMessagesManager));
|
_retainedMessagesManager = retainedMessagesManager ?? throw new ArgumentNullException(nameof(retainedMessagesManager));
|
||||||
@@ -34,7 +42,154 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
public MqttServer Server { get; }
|
public MqttServer Server { get; }
|
||||||
|
|
||||||
public async Task RunSessionAsync(IMqttChannelAdapter clientAdapter, CancellationToken cancellationToken)
|
public void Start()
|
||||||
|
{
|
||||||
|
Task.Factory.StartNew(() => ProcessQueuedApplicationMessages(_cancellationToken), _cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Default);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Stop()
|
||||||
|
{
|
||||||
|
lock (_sessions)
|
||||||
|
{
|
||||||
|
foreach (var session in _sessions)
|
||||||
|
{
|
||||||
|
session.Value.Stop(MqttClientDisconnectType.NotClean);
|
||||||
|
}
|
||||||
|
|
||||||
|
_sessions.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task StartSession(IMqttChannelAdapter clientAdapter)
|
||||||
|
{
|
||||||
|
return Task.Run(() => RunSession(clientAdapter, _cancellationToken), _cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<IList<IMqttClientSessionStatus>> GetClientStatusAsync()
|
||||||
|
{
|
||||||
|
var result = new List<IMqttClientSessionStatus>();
|
||||||
|
|
||||||
|
foreach (var session in GetSessions())
|
||||||
|
{
|
||||||
|
var status = new MqttClientSessionStatus(this, session);
|
||||||
|
session.FillStatus(status);
|
||||||
|
|
||||||
|
result.Add(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.FromResult((IList<IMqttClientSessionStatus>)result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void EnqueueApplicationMessage(MqttClientSession senderClientSession, MqttPublishPacket publishPacket)
|
||||||
|
{
|
||||||
|
if (publishPacket == null) throw new ArgumentNullException(nameof(publishPacket));
|
||||||
|
|
||||||
|
_messageQueue.Add(new MqttEnqueuedApplicationMessage(senderClientSession, publishPacket), _cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task SubscribeAsync(string clientId, IList<TopicFilter> topicFilters)
|
||||||
|
{
|
||||||
|
if (clientId == null) throw new ArgumentNullException(nameof(clientId));
|
||||||
|
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
|
||||||
|
|
||||||
|
lock (_sessions)
|
||||||
|
{
|
||||||
|
if (!_sessions.TryGetValue(clientId, out var session))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Client session '{clientId}' is unknown.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return session.SubscribeAsync(topicFilters);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task UnsubscribeAsync(string clientId, IList<string> topicFilters)
|
||||||
|
{
|
||||||
|
if (clientId == null) throw new ArgumentNullException(nameof(clientId));
|
||||||
|
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
|
||||||
|
|
||||||
|
lock (_sessions)
|
||||||
|
{
|
||||||
|
if (!_sessions.TryGetValue(clientId, out var session))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Client session '{clientId}' is unknown.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return session.UnsubscribeAsync(topicFilters);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DeleteSession(string clientId)
|
||||||
|
{
|
||||||
|
lock (_sessions)
|
||||||
|
{
|
||||||
|
_sessions.Remove(clientId);
|
||||||
|
}
|
||||||
|
_logger.Verbose("Session for client '{0}' deleted.", clientId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_messageQueue?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ProcessQueuedApplicationMessages(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
while (!cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var enqueuedApplicationMessage = _messageQueue.Take(cancellationToken);
|
||||||
|
var sender = enqueuedApplicationMessage.Sender;
|
||||||
|
var applicationMessage = enqueuedApplicationMessage.PublishPacket.ToApplicationMessage();
|
||||||
|
|
||||||
|
var interceptorContext = InterceptApplicationMessage(sender, applicationMessage);
|
||||||
|
if (interceptorContext != null)
|
||||||
|
{
|
||||||
|
if (interceptorContext.CloseConnection)
|
||||||
|
{
|
||||||
|
enqueuedApplicationMessage.Sender.Stop(MqttClientDisconnectType.NotClean);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (interceptorContext.ApplicationMessage == null || !interceptorContext.AcceptPublish)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
applicationMessage = interceptorContext.ApplicationMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
Server.OnApplicationMessageReceived(sender?.ClientId, applicationMessage);
|
||||||
|
|
||||||
|
if (applicationMessage.Retain)
|
||||||
|
{
|
||||||
|
_retainedMessagesManager.HandleMessageAsync(sender?.ClientId, applicationMessage).GetAwaiter().GetResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var clientSession in GetSessions())
|
||||||
|
{
|
||||||
|
clientSession.EnqueueApplicationMessage(enqueuedApplicationMessage.Sender, applicationMessage.ToPublishPacket());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
_logger.Error(exception, "Unhandled exception while processing queued application message.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<MqttClientSession> GetSessions()
|
||||||
|
{
|
||||||
|
lock (_sessions)
|
||||||
|
{
|
||||||
|
return _sessions.Values.ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task RunSession(IMqttChannelAdapter clientAdapter, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var clientId = string.Empty;
|
var clientId = string.Empty;
|
||||||
var wasCleanDisconnect = false;
|
var wasCleanDisconnect = false;
|
||||||
@@ -60,7 +215,7 @@ namespace MQTTnet.Server
|
|||||||
var connectReturnCode = ValidateConnection(connectPacket);
|
var connectReturnCode = ValidateConnection(connectPacket);
|
||||||
if (connectReturnCode != MqttConnectReturnCode.ConnectionAccepted)
|
if (connectReturnCode != MqttConnectReturnCode.ConnectionAccepted)
|
||||||
{
|
{
|
||||||
await clientAdapter.SendPacketAsync(_options.DefaultCommunicationTimeout,
|
await clientAdapter.SendPacketAsync(
|
||||||
new MqttConnAckPacket
|
new MqttConnAckPacket
|
||||||
{
|
{
|
||||||
ConnectReturnCode = connectReturnCode
|
ConnectReturnCode = connectReturnCode
|
||||||
@@ -70,15 +225,15 @@ namespace MQTTnet.Server
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = await PrepareClientSessionAsync(connectPacket).ConfigureAwait(false);
|
var result = PrepareClientSession(connectPacket);
|
||||||
var clientSession = result.Session;
|
var clientSession = result.Session;
|
||||||
|
|
||||||
await clientAdapter.SendPacketAsync(_options.DefaultCommunicationTimeout,
|
await clientAdapter.SendPacketAsync(
|
||||||
new MqttConnAckPacket
|
new MqttConnAckPacket
|
||||||
{
|
{
|
||||||
ConnectReturnCode = connectReturnCode,
|
ConnectReturnCode = connectReturnCode,
|
||||||
IsSessionPresent = result.IsExistingSession
|
IsSessionPresent = result.IsExistingSession
|
||||||
},
|
},
|
||||||
cancellationToken).ConfigureAwait(false);
|
cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
Server.OnClientConnected(clientId);
|
Server.OnClientConnected(clientId);
|
||||||
@@ -113,73 +268,6 @@ namespace MQTTnet.Server
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task StopAsync()
|
|
||||||
{
|
|
||||||
foreach (var session in _sessions)
|
|
||||||
{
|
|
||||||
session.Value.Stop(MqttClientDisconnectType.NotClean);
|
|
||||||
}
|
|
||||||
|
|
||||||
_sessions.Clear();
|
|
||||||
return Task.FromResult(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<IList<IMqttClientSessionStatus>> GetClientStatusAsync()
|
|
||||||
{
|
|
||||||
var result = new List<IMqttClientSessionStatus>();
|
|
||||||
foreach (var session in _sessions)
|
|
||||||
{
|
|
||||||
var status = new MqttClientSessionStatus(this, session.Value);
|
|
||||||
session.Value.FillStatus(status);
|
|
||||||
|
|
||||||
result.Add(status);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Task.FromResult((IList<IMqttClientSessionStatus>)result);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void StartDispatchApplicationMessage(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage)
|
|
||||||
{
|
|
||||||
Task.Run(() => DispatchApplicationMessageAsync(senderClientSession, applicationMessage));
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task SubscribeAsync(string clientId, IList<TopicFilter> topicFilters)
|
|
||||||
{
|
|
||||||
if (clientId == null) throw new ArgumentNullException(nameof(clientId));
|
|
||||||
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
|
|
||||||
|
|
||||||
if (!_sessions.TryGetValue(clientId, out var session))
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException($"Client session '{clientId}' is unknown.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return session.SubscribeAsync(topicFilters);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task UnsubscribeAsync(string clientId, IList<string> topicFilters)
|
|
||||||
{
|
|
||||||
if (clientId == null) throw new ArgumentNullException(nameof(clientId));
|
|
||||||
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
|
|
||||||
|
|
||||||
if (!_sessions.TryGetValue(clientId, out var session))
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException($"Client session '{clientId}' is unknown.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return session.UnsubscribeAsync(topicFilters);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void DeleteSession(string clientId)
|
|
||||||
{
|
|
||||||
_sessions.TryRemove(clientId, out _);
|
|
||||||
_logger.Verbose("Session for client '{0}' deleted.", clientId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
_sessionPreparationLock?.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
private MqttConnectReturnCode ValidateConnection(MqttConnectPacket connectPacket)
|
private MqttConnectReturnCode ValidateConnection(MqttConnectPacket connectPacket)
|
||||||
{
|
{
|
||||||
if (_options.ConnectionValidator == null)
|
if (_options.ConnectionValidator == null)
|
||||||
@@ -197,16 +285,16 @@ namespace MQTTnet.Server
|
|||||||
return context.ReturnCode;
|
return context.ReturnCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<GetOrCreateClientSessionResult> PrepareClientSessionAsync(MqttConnectPacket connectPacket)
|
private PrepareClientSessionResult PrepareClientSession(MqttConnectPacket connectPacket)
|
||||||
{
|
{
|
||||||
using (await _sessionPreparationLock.LockAsync(CancellationToken.None).ConfigureAwait(false))
|
lock (_sessions)
|
||||||
{
|
{
|
||||||
var isSessionPresent = _sessions.TryGetValue(connectPacket.ClientId, out var clientSession);
|
var isSessionPresent = _sessions.TryGetValue(connectPacket.ClientId, out var clientSession);
|
||||||
if (isSessionPresent)
|
if (isSessionPresent)
|
||||||
{
|
{
|
||||||
if (connectPacket.CleanSession)
|
if (connectPacket.CleanSession)
|
||||||
{
|
{
|
||||||
_sessions.TryRemove(connectPacket.ClientId, out _);
|
_sessions.Remove(connectPacket.ClientId);
|
||||||
|
|
||||||
clientSession.Stop(MqttClientDisconnectType.Clean);
|
clientSession.Stop(MqttClientDisconnectType.Clean);
|
||||||
clientSession.Dispose();
|
clientSession.Dispose();
|
||||||
@@ -231,60 +319,19 @@ namespace MQTTnet.Server
|
|||||||
_logger.Verbose("Created a new session for client '{0}'.", connectPacket.ClientId);
|
_logger.Verbose("Created a new session for client '{0}'.", connectPacket.ClientId);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new GetOrCreateClientSessionResult { IsExistingSession = isExistingSession, Session = clientSession };
|
return new PrepareClientSessionResult { IsExistingSession = isExistingSession, Session = clientSession };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task DispatchApplicationMessageAsync(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage)
|
private MqttApplicationMessageInterceptorContext InterceptApplicationMessage(MqttClientSession sender, MqttApplicationMessage applicationMessage)
|
||||||
{
|
{
|
||||||
try
|
|
||||||
{
|
|
||||||
var interceptorContext = InterceptApplicationMessage(senderClientSession, applicationMessage);
|
|
||||||
if (interceptorContext != null)
|
|
||||||
{
|
|
||||||
if (interceptorContext.CloseConnection)
|
|
||||||
{
|
|
||||||
senderClientSession.Stop(MqttClientDisconnectType.NotClean);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (interceptorContext.ApplicationMessage == null || !interceptorContext.AcceptPublish)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
applicationMessage = interceptorContext.ApplicationMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
Server.OnApplicationMessageReceived(senderClientSession?.ClientId, applicationMessage);
|
|
||||||
|
|
||||||
if (applicationMessage.Retain)
|
|
||||||
{
|
|
||||||
await _retainedMessagesManager.HandleMessageAsync(senderClientSession?.ClientId, applicationMessage).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var clientSession in _sessions.Values)
|
|
||||||
{
|
|
||||||
clientSession.EnqueueApplicationMessage(senderClientSession, applicationMessage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception exception)
|
|
||||||
{
|
|
||||||
_logger.Error(exception, "Error while processing application message");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private MqttApplicationMessageInterceptorContext InterceptApplicationMessage(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage)
|
|
||||||
{
|
|
||||||
var interceptorContext = new MqttApplicationMessageInterceptorContext(
|
|
||||||
senderClientSession?.ClientId,
|
|
||||||
applicationMessage);
|
|
||||||
|
|
||||||
var interceptor = _options.ApplicationMessageInterceptor;
|
var interceptor = _options.ApplicationMessageInterceptor;
|
||||||
if (interceptor == null)
|
if (interceptor == null)
|
||||||
{
|
{
|
||||||
return interceptorContext;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var interceptorContext = new MqttApplicationMessageInterceptorContext(sender?.ClientId, applicationMessage);
|
||||||
interceptor(interceptorContext);
|
interceptor(interceptorContext);
|
||||||
return interceptorContext;
|
return interceptorContext;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using MQTTnet.Packets;
|
using MQTTnet.Packets;
|
||||||
@@ -9,7 +8,7 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
public class MqttClientSubscriptionsManager
|
public class MqttClientSubscriptionsManager
|
||||||
{
|
{
|
||||||
private readonly ConcurrentDictionary<string, MqttQualityOfServiceLevel> _subscriptions = new ConcurrentDictionary<string, MqttQualityOfServiceLevel>();
|
private readonly Dictionary<string, MqttQualityOfServiceLevel> _subscriptions = new Dictionary<string, MqttQualityOfServiceLevel>();
|
||||||
private readonly IMqttServerOptions _options;
|
private readonly IMqttServerOptions _options;
|
||||||
private readonly MqttServer _server;
|
private readonly MqttServer _server;
|
||||||
private readonly string _clientId;
|
private readonly string _clientId;
|
||||||
@@ -54,7 +53,11 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
if (interceptorContext.AcceptSubscription)
|
if (interceptorContext.AcceptSubscription)
|
||||||
{
|
{
|
||||||
_subscriptions[topicFilter.Topic] = topicFilter.QualityOfServiceLevel;
|
lock (_subscriptions)
|
||||||
|
{
|
||||||
|
_subscriptions[topicFilter.Topic] = topicFilter.QualityOfServiceLevel;
|
||||||
|
}
|
||||||
|
|
||||||
_server.OnClientSubscribedTopic(_clientId, topicFilter);
|
_server.OnClientSubscribedTopic(_clientId, topicFilter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -66,10 +69,14 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
if (unsubscribePacket == null) throw new ArgumentNullException(nameof(unsubscribePacket));
|
if (unsubscribePacket == null) throw new ArgumentNullException(nameof(unsubscribePacket));
|
||||||
|
|
||||||
foreach (var topicFilter in unsubscribePacket.TopicFilters)
|
lock (_subscriptions)
|
||||||
{
|
{
|
||||||
_subscriptions.TryRemove(topicFilter, out _);
|
foreach (var topicFilter in unsubscribePacket.TopicFilters)
|
||||||
_server.OnClientUnsubscribedTopic(_clientId, topicFilter);
|
{
|
||||||
|
_subscriptions.Remove(topicFilter);
|
||||||
|
|
||||||
|
_server.OnClientUnsubscribedTopic(_clientId, topicFilter);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new MqttUnsubAckPacket
|
return new MqttUnsubAckPacket
|
||||||
@@ -78,19 +85,21 @@ namespace MQTTnet.Server
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public CheckSubscriptionsResult CheckSubscriptions(MqttApplicationMessage applicationMessage)
|
public CheckSubscriptionsResult CheckSubscriptions(string topic, MqttQualityOfServiceLevel qosLevel)
|
||||||
{
|
{
|
||||||
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
|
|
||||||
|
|
||||||
var qosLevels = new HashSet<MqttQualityOfServiceLevel>();
|
var qosLevels = new HashSet<MqttQualityOfServiceLevel>();
|
||||||
foreach (var subscription in _subscriptions)
|
|
||||||
{
|
|
||||||
if (!MqttTopicFilterComparer.IsMatch(applicationMessage.Topic, subscription.Key))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
qosLevels.Add(subscription.Value);
|
lock (_subscriptions)
|
||||||
|
{
|
||||||
|
foreach (var subscription in _subscriptions)
|
||||||
|
{
|
||||||
|
if (!MqttTopicFilterComparer.IsMatch(topic, subscription.Key))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
qosLevels.Add(subscription.Value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (qosLevels.Count == 0)
|
if (qosLevels.Count == 0)
|
||||||
@@ -101,7 +110,7 @@ namespace MQTTnet.Server
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return CreateSubscriptionResult(applicationMessage, qosLevels);
|
return CreateSubscriptionResult(qosLevel, qosLevels);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static MqttSubscribeReturnCode ConvertToMaximumQoS(MqttQualityOfServiceLevel qualityOfServiceLevel)
|
private static MqttSubscribeReturnCode ConvertToMaximumQoS(MqttQualityOfServiceLevel qualityOfServiceLevel)
|
||||||
@@ -122,12 +131,12 @@ namespace MQTTnet.Server
|
|||||||
return interceptorContext;
|
return interceptorContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static CheckSubscriptionsResult CreateSubscriptionResult(MqttApplicationMessage applicationMessage, HashSet<MqttQualityOfServiceLevel> subscribedQoSLevels)
|
private static CheckSubscriptionsResult CreateSubscriptionResult(MqttQualityOfServiceLevel qosLevel, HashSet<MqttQualityOfServiceLevel> subscribedQoSLevels)
|
||||||
{
|
{
|
||||||
MqttQualityOfServiceLevel effectiveQoS;
|
MqttQualityOfServiceLevel effectiveQoS;
|
||||||
if (subscribedQoSLevels.Contains(applicationMessage.QualityOfServiceLevel))
|
if (subscribedQoSLevels.Contains(qosLevel))
|
||||||
{
|
{
|
||||||
effectiveQoS = applicationMessage.QualityOfServiceLevel;
|
effectiveQoS = qosLevel;
|
||||||
}
|
}
|
||||||
else if (subscribedQoSLevels.Count == 1)
|
else if (subscribedQoSLevels.Count == 1)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
using MQTTnet.Packets;
|
||||||
|
|
||||||
|
namespace MQTTnet.Server
|
||||||
|
{
|
||||||
|
public class MqttEnqueuedApplicationMessage
|
||||||
|
{
|
||||||
|
public MqttEnqueuedApplicationMessage(MqttClientSession sender, MqttPublishPacket publishPacket)
|
||||||
|
{
|
||||||
|
Sender = sender;
|
||||||
|
PublishPacket = publishPacket;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MqttClientSession Sender { get; }
|
||||||
|
|
||||||
|
public MqttPublishPacket PublishPacket { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -9,7 +8,8 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
public class MqttRetainedMessagesManager
|
public class MqttRetainedMessagesManager
|
||||||
{
|
{
|
||||||
private readonly ConcurrentDictionary<string, MqttApplicationMessage> _messages = new ConcurrentDictionary<string, MqttApplicationMessage>();
|
private readonly Dictionary<string, MqttApplicationMessage> _messages = new Dictionary<string, MqttApplicationMessage>();
|
||||||
|
|
||||||
private readonly IMqttNetChildLogger _logger;
|
private readonly IMqttNetChildLogger _logger;
|
||||||
private readonly IMqttServerOptions _options;
|
private readonly IMqttServerOptions _options;
|
||||||
|
|
||||||
@@ -31,10 +31,13 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
var retainedMessages = await _options.Storage.LoadRetainedMessagesAsync().ConfigureAwait(false);
|
var retainedMessages = await _options.Storage.LoadRetainedMessagesAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
_messages.Clear();
|
lock (_messages)
|
||||||
foreach (var retainedMessage in retainedMessages)
|
|
||||||
{
|
{
|
||||||
_messages[retainedMessage.Topic] = retainedMessage;
|
_messages.Clear();
|
||||||
|
foreach (var retainedMessage in retainedMessages)
|
||||||
|
{
|
||||||
|
_messages[retainedMessage.Topic] = retainedMessage;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
@@ -61,17 +64,20 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
var retainedMessages = new List<MqttApplicationMessage>();
|
var retainedMessages = new List<MqttApplicationMessage>();
|
||||||
|
|
||||||
foreach (var retainedMessage in _messages.Values)
|
lock (_messages)
|
||||||
{
|
{
|
||||||
foreach (var topicFilter in topicFilters)
|
foreach (var retainedMessage in _messages.Values)
|
||||||
{
|
{
|
||||||
if (!MqttTopicFilterComparer.IsMatch(retainedMessage.Topic, topicFilter.Topic))
|
foreach (var topicFilter in topicFilters)
|
||||||
{
|
{
|
||||||
continue;
|
if (!MqttTopicFilterComparer.IsMatch(retainedMessage.Topic, topicFilter.Topic))
|
||||||
}
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
retainedMessages.Add(retainedMessage);
|
retainedMessages.Add(retainedMessage);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,28 +88,31 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
var saveIsRequired = false;
|
var saveIsRequired = false;
|
||||||
|
|
||||||
if (applicationMessage.Payload?.Length == 0)
|
lock (_messages)
|
||||||
{
|
{
|
||||||
saveIsRequired = _messages.TryRemove(applicationMessage.Topic, out _);
|
if (applicationMessage.Payload?.Length == 0)
|
||||||
_logger.Info("Client '{0}' cleared retained message for topic '{1}'.", clientId, applicationMessage.Topic);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (!_messages.TryGetValue(applicationMessage.Topic, out var existingMessage))
|
|
||||||
{
|
{
|
||||||
_messages[applicationMessage.Topic] = applicationMessage;
|
saveIsRequired = _messages.Remove(applicationMessage.Topic);
|
||||||
saveIsRequired = true;
|
_logger.Info("Client '{0}' cleared retained message for topic '{1}'.", clientId, applicationMessage.Topic);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (existingMessage.QualityOfServiceLevel != applicationMessage.QualityOfServiceLevel || !existingMessage.Payload.SequenceEqual(applicationMessage.Payload ?? new byte[0]))
|
if (!_messages.TryGetValue(applicationMessage.Topic, out var existingMessage))
|
||||||
{
|
{
|
||||||
_messages[applicationMessage.Topic] = applicationMessage;
|
_messages[applicationMessage.Topic] = applicationMessage;
|
||||||
saveIsRequired = true;
|
saveIsRequired = true;
|
||||||
}
|
}
|
||||||
}
|
else
|
||||||
|
{
|
||||||
|
if (existingMessage.QualityOfServiceLevel != applicationMessage.QualityOfServiceLevel || !existingMessage.Payload.SequenceEqual(applicationMessage.Payload ?? new byte[0]))
|
||||||
|
{
|
||||||
|
_messages[applicationMessage.Topic] = applicationMessage;
|
||||||
|
saveIsRequired = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_logger.Info("Client '{0}' set retained message for topic '{1}'.", clientId, applicationMessage.Topic);
|
_logger.Info("Client '{0}' set retained message for topic '{1}'.", clientId, applicationMessage.Topic);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!saveIsRequired)
|
if (!saveIsRequired)
|
||||||
@@ -113,7 +122,13 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
if (saveIsRequired && _options.Storage != null)
|
if (saveIsRequired && _options.Storage != null)
|
||||||
{
|
{
|
||||||
await _options.Storage.SaveRetainedMessagesAsync(_messages.Values.ToList()).ConfigureAwait(false);
|
List<MqttApplicationMessage> messages;
|
||||||
|
lock (_messages)
|
||||||
|
{
|
||||||
|
messages = _messages.Values.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
await _options.Storage.SaveRetainedMessagesAsync(messages).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using System.Threading;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MQTTnet.Adapter;
|
using MQTTnet.Adapter;
|
||||||
using MQTTnet.Diagnostics;
|
using MQTTnet.Diagnostics;
|
||||||
|
using MQTTnet.Internal;
|
||||||
|
|
||||||
namespace MQTTnet.Server
|
namespace MQTTnet.Server
|
||||||
{
|
{
|
||||||
@@ -65,7 +66,7 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
if (_cancellationTokenSource == null) throw new InvalidOperationException("The server is not started.");
|
if (_cancellationTokenSource == null) throw new InvalidOperationException("The server is not started.");
|
||||||
|
|
||||||
_clientSessionsManager.StartDispatchApplicationMessage(null, applicationMessage);
|
_clientSessionsManager.EnqueueApplicationMessage(null, applicationMessage.ToPublishPacket());
|
||||||
|
|
||||||
return Task.FromResult(0);
|
return Task.FromResult(0);
|
||||||
}
|
}
|
||||||
@@ -81,7 +82,8 @@ namespace MQTTnet.Server
|
|||||||
_retainedMessagesManager = new MqttRetainedMessagesManager(Options, _logger);
|
_retainedMessagesManager = new MqttRetainedMessagesManager(Options, _logger);
|
||||||
await _retainedMessagesManager.LoadMessagesAsync().ConfigureAwait(false);
|
await _retainedMessagesManager.LoadMessagesAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
_clientSessionsManager = new MqttClientSessionsManager(Options, this, _retainedMessagesManager, _logger);
|
_clientSessionsManager = new MqttClientSessionsManager(Options, this, _retainedMessagesManager, _cancellationTokenSource.Token, _logger);
|
||||||
|
_clientSessionsManager.Start();
|
||||||
|
|
||||||
foreach (var adapter in _adapters)
|
foreach (var adapter in _adapters)
|
||||||
{
|
{
|
||||||
@@ -103,25 +105,26 @@ namespace MQTTnet.Server
|
|||||||
}
|
}
|
||||||
|
|
||||||
_cancellationTokenSource.Cancel(false);
|
_cancellationTokenSource.Cancel(false);
|
||||||
_cancellationTokenSource.Dispose();
|
|
||||||
|
|
||||||
foreach (var adapter in _adapters)
|
foreach (var adapter in _adapters)
|
||||||
{
|
{
|
||||||
adapter.ClientAccepted -= OnClientAccepted;
|
adapter.ClientAccepted -= OnClientAccepted;
|
||||||
await adapter.StopAsync().ConfigureAwait(false);
|
await adapter.StopAsync().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
await _clientSessionsManager.StopAsync().ConfigureAwait(false);
|
_clientSessionsManager.Stop();
|
||||||
|
|
||||||
_logger.Info("Stopped.");
|
_logger.Info("Stopped.");
|
||||||
Stopped?.Invoke(this, EventArgs.Empty);
|
Stopped?.Invoke(this, EventArgs.Empty);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
_clientSessionsManager?.Dispose();
|
_cancellationTokenSource?.Dispose();
|
||||||
|
|
||||||
_cancellationTokenSource = null;
|
_cancellationTokenSource = null;
|
||||||
|
|
||||||
_retainedMessagesManager = null;
|
_retainedMessagesManager = null;
|
||||||
|
|
||||||
|
_clientSessionsManager?.Dispose();
|
||||||
_clientSessionsManager = null;
|
_clientSessionsManager = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -155,9 +158,7 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
private void OnClientAccepted(object sender, MqttServerAdapterClientAcceptedEventArgs eventArgs)
|
private void OnClientAccepted(object sender, MqttServerAdapterClientAcceptedEventArgs eventArgs)
|
||||||
{
|
{
|
||||||
eventArgs.SessionTask = Task.Run(
|
eventArgs.SessionTask = _clientSessionsManager.StartSession(eventArgs.Client);
|
||||||
() => _clientSessionsManager.RunSessionAsync(eventArgs.Client, _cancellationTokenSource.Token),
|
|
||||||
_cancellationTokenSource.Token);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
namespace MQTTnet.Server
|
namespace MQTTnet.Server
|
||||||
{
|
{
|
||||||
public class GetOrCreateClientSessionResult
|
public class PrepareClientSessionResult
|
||||||
{
|
{
|
||||||
public bool IsExistingSession { get; set; }
|
public bool IsExistingSession { get; set; }
|
||||||
|
|
||||||
@@ -65,7 +65,7 @@ namespace MQTTnet.Benchmarks
|
|||||||
|
|
||||||
for (var i = 0; i < 10000; i++)
|
for (var i = 0; i < 10000; i++)
|
||||||
{
|
{
|
||||||
_channelAdapter.SendPacketAsync(TimeSpan.FromSeconds(15), _packet, CancellationToken.None).GetAwaiter().GetResult();
|
_channelAdapter.SendPacketAsync(_packet, CancellationToken.None).GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
_stream.Position = 0;
|
_stream.Position = 0;
|
||||||
|
|||||||
@@ -10,9 +10,11 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="BenchmarkDotNet" Version="0.10.14" />
|
<PackageReference Include="BenchmarkDotNet" Version="0.10.14" />
|
||||||
<PackageReference Include="System.IO.Pipelines" Version="4.5.0" />
|
<PackageReference Include="System.IO.Pipelines" Version="4.5.0" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore" Version="2.1.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj" />
|
||||||
<ProjectReference Include="..\..\Source\MQTTnet\MQTTnet.csproj" />
|
<ProjectReference Include="..\..\Source\MQTTnet\MQTTnet.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ namespace MQTTnet.Benchmarks
|
|||||||
{
|
{
|
||||||
[ClrJob]
|
[ClrJob]
|
||||||
[RPlotExporter, RankColumn]
|
[RPlotExporter, RankColumn]
|
||||||
|
[MemoryDiagnoser]
|
||||||
public class MessageProcessingBenchmark
|
public class MessageProcessingBenchmark
|
||||||
{
|
{
|
||||||
private IMqttServer _mqttServer;
|
private IMqttServer _mqttServer;
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
using BenchmarkDotNet.Attributes;
|
||||||
|
using MQTTnet.Client;
|
||||||
|
|
||||||
|
|
||||||
|
using MQTTnet.AspNetCore;
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using MQTTnet.Server;
|
||||||
|
using MQTTnet.Diagnostics;
|
||||||
|
using MQTTnet.AspNetCore.Client;
|
||||||
|
|
||||||
|
namespace MQTTnet.Benchmarks
|
||||||
|
{
|
||||||
|
[MemoryDiagnoser]
|
||||||
|
public class MessageProcessingMqttConnectionContextBenchmark
|
||||||
|
{
|
||||||
|
private IWebHost _host;
|
||||||
|
private IMqttClient _mqttClient;
|
||||||
|
private MqttApplicationMessage _message;
|
||||||
|
|
||||||
|
[GlobalSetup]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
_host = WebHost.CreateDefaultBuilder()
|
||||||
|
.UseKestrel(o => o.ListenAnyIP(1883, l => l.UseMqtt()))
|
||||||
|
.ConfigureServices(services => {
|
||||||
|
var mqttServerOptions = new MqttServerOptionsBuilder()
|
||||||
|
.WithoutDefaultEndpoint()
|
||||||
|
.Build();
|
||||||
|
services
|
||||||
|
.AddHostedMqttServer(mqttServerOptions)
|
||||||
|
.AddMqttConnectionHandler();
|
||||||
|
})
|
||||||
|
.Configure(app => {
|
||||||
|
app.UseMqttServer(s => {
|
||||||
|
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var factory = new MqttFactory();
|
||||||
|
_mqttClient = factory.CreateMqttClient(new MqttNetLogger(), new MqttClientConnectionContextFactory());
|
||||||
|
|
||||||
|
_host.StartAsync().GetAwaiter().GetResult();
|
||||||
|
|
||||||
|
var clientOptions = new MqttClientOptionsBuilder()
|
||||||
|
.WithTcpServer("localhost").Build();
|
||||||
|
|
||||||
|
_mqttClient.ConnectAsync(clientOptions).GetAwaiter().GetResult();
|
||||||
|
|
||||||
|
_message = new MqttApplicationMessageBuilder()
|
||||||
|
.WithTopic("A")
|
||||||
|
.Build();
|
||||||
|
}
|
||||||
|
|
||||||
|
[GlobalCleanup]
|
||||||
|
public void Cleanup()
|
||||||
|
{
|
||||||
|
_mqttClient.DisconnectAsync().GetAwaiter().GetResult();
|
||||||
|
_mqttClient.Dispose();
|
||||||
|
|
||||||
|
_host.StopAsync().GetAwaiter().GetResult();
|
||||||
|
_host.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public void Send_10000_Messages()
|
||||||
|
{
|
||||||
|
for (var i = 0; i < 10000; i++)
|
||||||
|
{
|
||||||
|
_mqttClient.PublishAsync(_message).GetAwaiter().GetResult();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,6 +16,7 @@ namespace MQTTnet.Benchmarks
|
|||||||
Console.WriteLine("5 = ChannelAdapterBenchmark");
|
Console.WriteLine("5 = ChannelAdapterBenchmark");
|
||||||
Console.WriteLine("6 = MqttTcpChannelBenchmark");
|
Console.WriteLine("6 = MqttTcpChannelBenchmark");
|
||||||
Console.WriteLine("7 = TcpPipesBenchmark");
|
Console.WriteLine("7 = TcpPipesBenchmark");
|
||||||
|
Console.WriteLine("8 = MessageProcessingMqttConnectionContextBenchmark");
|
||||||
|
|
||||||
var pressedKey = Console.ReadKey(true);
|
var pressedKey = Console.ReadKey(true);
|
||||||
switch (pressedKey.KeyChar)
|
switch (pressedKey.KeyChar)
|
||||||
@@ -41,6 +42,9 @@ namespace MQTTnet.Benchmarks
|
|||||||
case '7':
|
case '7':
|
||||||
BenchmarkRunner.Run<TcpPipesBenchmark>();
|
BenchmarkRunner.Run<TcpPipesBenchmark>();
|
||||||
break;
|
break;
|
||||||
|
case '8':
|
||||||
|
BenchmarkRunner.Run<MessageProcessingMqttConnectionContextBenchmark>(new AllowNonOptimized());
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
Console.ReadLine();
|
Console.ReadLine();
|
||||||
|
|||||||
@@ -6,8 +6,9 @@ using BenchmarkDotNet.Attributes.Jobs;
|
|||||||
using BenchmarkDotNet.Attributes.Exporters;
|
using BenchmarkDotNet.Attributes.Exporters;
|
||||||
using System;
|
using System;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.IO;
|
using System.Threading.Tasks;
|
||||||
using MQTTnet.Adapter;
|
using MQTTnet.Adapter;
|
||||||
|
using MQTTnet.Channel;
|
||||||
|
|
||||||
namespace MQTTnet.Benchmarks
|
namespace MQTTnet.Benchmarks
|
||||||
{
|
{
|
||||||
@@ -38,37 +39,73 @@ namespace MQTTnet.Benchmarks
|
|||||||
for (var i = 0; i < 10000; i++)
|
for (var i = 0; i < 10000; i++)
|
||||||
{
|
{
|
||||||
_serializer.Serialize(_packet);
|
_serializer.Serialize(_packet);
|
||||||
|
_serializer.FreeBuffer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Benchmark]
|
[Benchmark]
|
||||||
public void Deserialize_10000_Messages()
|
public void Deserialize_10000_Messages()
|
||||||
{
|
{
|
||||||
|
var channel = new BenchmarkMqttChannel(_serializedPacket);
|
||||||
|
|
||||||
for (var i = 0; i < 10000; i++)
|
for (var i = 0; i < 10000; i++)
|
||||||
{
|
{
|
||||||
using (var headerStream = new MemoryStream(Join(_serializedPacket)))
|
channel.Reset();
|
||||||
{
|
|
||||||
var channel = new TestMqttChannel(headerStream);
|
|
||||||
|
|
||||||
var header = MqttPacketReader.ReadFixedHeaderAsync(channel, CancellationToken.None).GetAwaiter().GetResult();
|
var header = MqttPacketReader.ReadFixedHeaderAsync(channel, CancellationToken.None).GetAwaiter().GetResult();
|
||||||
|
|
||||||
using (var bodyStream = new MemoryStream(Join(_serializedPacket), (int)headerStream.Position, header.RemainingLength))
|
var receivedPacket = new ReceivedMqttPacket(
|
||||||
{
|
header.Flags,
|
||||||
_serializer.Deserialize(new ReceivedMqttPacket(header.Flags, new MqttPacketBodyReader(bodyStream.ToArray())));
|
new MqttPacketBodyReader(_serializedPacket.Array, _serializedPacket.Count - header.RemainingLength));
|
||||||
}
|
|
||||||
}
|
_serializer.Deserialize(receivedPacket);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] Join(params ArraySegment<byte>[] chunks)
|
private class BenchmarkMqttChannel : IMqttChannel
|
||||||
{
|
{
|
||||||
var buffer = new MemoryStream();
|
private readonly ArraySegment<byte> _buffer;
|
||||||
foreach (var chunk in chunks)
|
private int _position;
|
||||||
|
|
||||||
|
public BenchmarkMqttChannel(ArraySegment<byte> buffer)
|
||||||
{
|
{
|
||||||
buffer.Write(chunk.Array, chunk.Offset, chunk.Count);
|
_buffer = buffer;
|
||||||
|
_position = _buffer.Offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
return buffer.ToArray();
|
public string Endpoint { get; }
|
||||||
|
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
_position = _buffer.Offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task ConnectAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task DisconnectAsync()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
Array.Copy(_buffer.Array, _position, buffer, offset, count);
|
||||||
|
_position += count;
|
||||||
|
|
||||||
|
return Task.FromResult(count);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ using System.Net.Sockets;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using MQTTnet.Benchmarks.Tcp;
|
using MQTTnet.AspNetCore.Client.Tcp;
|
||||||
|
|
||||||
namespace MQTTnet.Benchmarks
|
namespace MQTTnet.Benchmarks
|
||||||
{
|
{
|
||||||
@@ -25,10 +25,12 @@ namespace MQTTnet.Benchmarks
|
|||||||
|
|
||||||
var clientConnection = new TcpConnection(new IPEndPoint(IPAddress.Loopback, 1883));
|
var clientConnection = new TcpConnection(new IPEndPoint(IPAddress.Loopback, 1883));
|
||||||
|
|
||||||
_client = clientConnection.StartAsync().GetAwaiter().GetResult();
|
clientConnection.StartAsync().GetAwaiter().GetResult();
|
||||||
|
_client = clientConnection.Transport;
|
||||||
|
|
||||||
var serverConnection = new TcpConnection(task.GetAwaiter().GetResult());
|
var serverConnection = new TcpConnection(task.GetAwaiter().GetResult());
|
||||||
_server = serverConnection.StartAsync().GetAwaiter().GetResult();
|
serverConnection.StartAsync().GetAwaiter().GetResult();
|
||||||
|
_server = serverConnection.Transport;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
|
||||||
using MQTTnet.Serializer;
|
|
||||||
|
|
||||||
namespace MQTTnet.Core.Tests
|
|
||||||
{
|
|
||||||
[TestClass]
|
|
||||||
public class ByteReaderTests
|
|
||||||
{
|
|
||||||
[TestMethod]
|
|
||||||
public void ByteReader_ReadToEnd()
|
|
||||||
{
|
|
||||||
var reader = new ByteReader(85);
|
|
||||||
Assert.IsTrue(reader.Read());
|
|
||||||
Assert.IsFalse(reader.Read());
|
|
||||||
Assert.IsTrue(reader.Read());
|
|
||||||
Assert.IsFalse(reader.Read());
|
|
||||||
Assert.IsTrue(reader.Read());
|
|
||||||
Assert.IsFalse(reader.Read());
|
|
||||||
Assert.IsTrue(reader.Read());
|
|
||||||
Assert.IsFalse(reader.Read());
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void ByteReader_ReadPartial()
|
|
||||||
{
|
|
||||||
var reader = new ByteReader(15);
|
|
||||||
Assert.AreEqual(3, reader.Read(2));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
|
||||||
using MQTTnet.Serializer;
|
|
||||||
|
|
||||||
namespace MQTTnet.Core.Tests
|
|
||||||
{
|
|
||||||
[TestClass]
|
|
||||||
public class ByteWriterTests
|
|
||||||
{
|
|
||||||
[TestMethod]
|
|
||||||
public void ByteWriter_WriteMultipleAll()
|
|
||||||
{
|
|
||||||
var b = new ByteWriter();
|
|
||||||
Assert.AreEqual(0, b.Value);
|
|
||||||
b.Write(3, 2);
|
|
||||||
Assert.AreEqual(3, b.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void ByteWriter_WriteMultiplePartial()
|
|
||||||
{
|
|
||||||
var b = new ByteWriter();
|
|
||||||
Assert.AreEqual(0, b.Value);
|
|
||||||
b.Write(255, 2);
|
|
||||||
Assert.AreEqual(3, b.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void ByteWriter_WriteTo0xFF()
|
|
||||||
{
|
|
||||||
var b = new ByteWriter();
|
|
||||||
|
|
||||||
Assert.AreEqual(0, b.Value);
|
|
||||||
b.Write(true);
|
|
||||||
Assert.AreEqual(1, b.Value);
|
|
||||||
b.Write(true);
|
|
||||||
Assert.AreEqual(3, b.Value);
|
|
||||||
b.Write(true);
|
|
||||||
Assert.AreEqual(7, b.Value);
|
|
||||||
b.Write(true);
|
|
||||||
Assert.AreEqual(15, b.Value);
|
|
||||||
b.Write(true);
|
|
||||||
Assert.AreEqual(31, b.Value);
|
|
||||||
b.Write(true);
|
|
||||||
Assert.AreEqual(63, b.Value);
|
|
||||||
b.Write(true);
|
|
||||||
Assert.AreEqual(127, b.Value);
|
|
||||||
b.Write(true);
|
|
||||||
Assert.AreEqual(255, b.Value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -14,20 +14,20 @@ namespace MQTTnet.Core.Tests
|
|||||||
[TestMethod]
|
[TestMethod]
|
||||||
public async Task TimeoutAfter()
|
public async Task TimeoutAfter()
|
||||||
{
|
{
|
||||||
await MQTTnet.Internal.TaskExtensions.TimeoutAfter(ct => Task.Delay(TimeSpan.FromMilliseconds(500), ct), TimeSpan.FromMilliseconds(100), CancellationToken.None);
|
await MQTTnet.Internal.TaskExtensions.TimeoutAfterAsync(ct => Task.Delay(TimeSpan.FromMilliseconds(500), ct), TimeSpan.FromMilliseconds(100), CancellationToken.None);
|
||||||
}
|
}
|
||||||
|
|
||||||
[ExpectedException(typeof(MqttCommunicationTimedOutException))]
|
[ExpectedException(typeof(MqttCommunicationTimedOutException))]
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public async Task TimeoutAfterWithResult()
|
public async Task TimeoutAfterWithResult()
|
||||||
{
|
{
|
||||||
await MQTTnet.Internal.TaskExtensions.TimeoutAfter(ct => Task.Delay(TimeSpan.FromMilliseconds(500), ct).ContinueWith(t => 5, ct), TimeSpan.FromMilliseconds(100), CancellationToken.None);
|
await MQTTnet.Internal.TaskExtensions.TimeoutAfterAsync(ct => Task.Delay(TimeSpan.FromMilliseconds(500), ct).ContinueWith(t => 5, ct), TimeSpan.FromMilliseconds(100), CancellationToken.None);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public async Task TimeoutAfterCompleteInTime()
|
public async Task TimeoutAfterCompleteInTime()
|
||||||
{
|
{
|
||||||
var result = await MQTTnet.Internal.TaskExtensions.TimeoutAfter(ct => Task.Delay(TimeSpan.FromMilliseconds(100), ct).ContinueWith(t => 5, ct), TimeSpan.FromMilliseconds(500), CancellationToken.None);
|
var result = await MQTTnet.Internal.TaskExtensions.TimeoutAfterAsync(ct => Task.Delay(TimeSpan.FromMilliseconds(100), ct).ContinueWith(t => 5, ct), TimeSpan.FromMilliseconds(500), CancellationToken.None);
|
||||||
Assert.AreEqual(5, result);
|
Assert.AreEqual(5, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ namespace MQTTnet.Core.Tests
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await MQTTnet.Internal.TaskExtensions.TimeoutAfter(ct => Task.Run(() =>
|
await MQTTnet.Internal.TaskExtensions.TimeoutAfterAsync(ct => Task.Run(() =>
|
||||||
{
|
{
|
||||||
var iis = new int[0];
|
var iis = new int[0];
|
||||||
iis[1] = 0;
|
iis[1] = 0;
|
||||||
@@ -55,7 +55,7 @@ namespace MQTTnet.Core.Tests
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await MQTTnet.Internal.TaskExtensions.TimeoutAfter(ct => Task.Run(() =>
|
await MQTTnet.Internal.TaskExtensions.TimeoutAfterAsync(ct => Task.Run(() =>
|
||||||
{
|
{
|
||||||
var iis = new int[0];
|
var iis = new int[0];
|
||||||
iis[1] = 0;
|
iis[1] = 0;
|
||||||
@@ -76,7 +76,7 @@ namespace MQTTnet.Core.Tests
|
|||||||
var tasks = Enumerable.Range(0, 100000)
|
var tasks = Enumerable.Range(0, 100000)
|
||||||
.Select(i =>
|
.Select(i =>
|
||||||
{
|
{
|
||||||
return MQTTnet.Internal.TaskExtensions.TimeoutAfter(ct => Task.Delay(TimeSpan.FromMilliseconds(1), ct), TimeSpan.FromMinutes(1), CancellationToken.None);
|
return MQTTnet.Internal.TaskExtensions.TimeoutAfterAsync(ct => Task.Delay(TimeSpan.FromMilliseconds(1), ct), TimeSpan.FromMinutes(1), CancellationToken.None);
|
||||||
});
|
});
|
||||||
|
|
||||||
await Task.WhenAll(tasks);
|
await Task.WhenAll(tasks);
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
using System.Threading;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using MQTTnet.Adapter;
|
||||||
using MQTTnet.Diagnostics;
|
using MQTTnet.Diagnostics;
|
||||||
using MQTTnet.Packets;
|
using MQTTnet.Packets;
|
||||||
using MQTTnet.Server;
|
using MQTTnet.Server;
|
||||||
@@ -12,39 +16,31 @@ namespace MQTTnet.Core.Tests
|
|||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void KeepAlive_Timeout()
|
public void KeepAlive_Timeout()
|
||||||
{
|
{
|
||||||
var timeoutCalledCount = 0;
|
var clientSession = new TestClientSession();
|
||||||
|
var monitor = new MqttClientKeepAliveMonitor(clientSession, new MqttNetLogger().CreateChildLogger());
|
||||||
|
|
||||||
var monitor = new MqttClientKeepAliveMonitor(string.Empty, delegate
|
Assert.AreEqual(0, clientSession.StopCalledCount);
|
||||||
{
|
|
||||||
timeoutCalledCount++;
|
|
||||||
}, new MqttNetLogger().CreateChildLogger(""));
|
|
||||||
|
|
||||||
Assert.AreEqual(0, timeoutCalledCount);
|
|
||||||
|
|
||||||
monitor.Start(1, CancellationToken.None);
|
monitor.Start(1, CancellationToken.None);
|
||||||
|
|
||||||
Assert.AreEqual(0, timeoutCalledCount);
|
Assert.AreEqual(0, clientSession.StopCalledCount);
|
||||||
|
|
||||||
Thread.Sleep(2000); // Internally the keep alive timeout is multiplied with 1.5 as per protocol specification.
|
Thread.Sleep(2000); // Internally the keep alive timeout is multiplied with 1.5 as per protocol specification.
|
||||||
|
|
||||||
Assert.AreEqual(1, timeoutCalledCount);
|
Assert.AreEqual(1, clientSession.StopCalledCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void KeepAlive_NoTimeout()
|
public void KeepAlive_NoTimeout()
|
||||||
{
|
{
|
||||||
var timeoutCalledCount = 0;
|
var clientSession = new TestClientSession();
|
||||||
|
var monitor = new MqttClientKeepAliveMonitor(clientSession, new MqttNetLogger().CreateChildLogger());
|
||||||
|
|
||||||
var monitor = new MqttClientKeepAliveMonitor(string.Empty, delegate
|
Assert.AreEqual(0, clientSession.StopCalledCount);
|
||||||
{
|
|
||||||
timeoutCalledCount++;
|
|
||||||
}, new MqttNetLogger().CreateChildLogger(""));
|
|
||||||
|
|
||||||
Assert.AreEqual(0, timeoutCalledCount);
|
|
||||||
|
|
||||||
monitor.Start(1, CancellationToken.None);
|
monitor.Start(1, CancellationToken.None);
|
||||||
|
|
||||||
Assert.AreEqual(0, timeoutCalledCount);
|
Assert.AreEqual(0, clientSession.StopCalledCount);
|
||||||
|
|
||||||
// Simulate traffic.
|
// Simulate traffic.
|
||||||
Thread.Sleep(1000); // Internally the keep alive timeout is multiplied with 1.5 as per protocol specification.
|
Thread.Sleep(1000); // Internally the keep alive timeout is multiplied with 1.5 as per protocol specification.
|
||||||
@@ -53,11 +49,57 @@ namespace MQTTnet.Core.Tests
|
|||||||
monitor.PacketReceived(new MqttPublishPacket());
|
monitor.PacketReceived(new MqttPublishPacket());
|
||||||
Thread.Sleep(1000);
|
Thread.Sleep(1000);
|
||||||
|
|
||||||
Assert.AreEqual(0, timeoutCalledCount);
|
Assert.AreEqual(0, clientSession.StopCalledCount);
|
||||||
|
|
||||||
Thread.Sleep(2000);
|
Thread.Sleep(2000);
|
||||||
|
|
||||||
Assert.AreEqual(1, timeoutCalledCount);
|
Assert.AreEqual(1, clientSession.StopCalledCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestClientSession : IMqttClientSession
|
||||||
|
{
|
||||||
|
public string ClientId { get; }
|
||||||
|
|
||||||
|
public int StopCalledCount { get; set; }
|
||||||
|
|
||||||
|
public void FillStatus(MqttClientSessionStatus status)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void EnqueueApplicationMessage(MqttClientSession senderClientSession, MqttPublishPacket publishPacket)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ClearPendingApplicationMessages()
|
||||||
|
{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<bool> RunAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter adapter)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Stop(MqttClientDisconnectType disconnectType)
|
||||||
|
{
|
||||||
|
StopCalledCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task SubscribeAsync(IList<TopicFilter> topicFilters)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task UnsubscribeAsync(IList<string> topicFilters)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ namespace MQTTnet.Core.Tests
|
|||||||
public class MqttPacketReaderTests
|
public class MqttPacketReaderTests
|
||||||
{
|
{
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
[ExpectedException(typeof(MqttCommunicationException))]
|
[ExpectedException(typeof(MqttCommunicationClosedGracefullyException))]
|
||||||
public void MqttPacketReader_EmptyStream()
|
public void MqttPacketReader_EmptyStream()
|
||||||
{
|
{
|
||||||
MqttPacketReader.ReadFixedHeaderAsync(new TestMqttChannel(new MemoryStream()), CancellationToken.None).GetAwaiter().GetResult();
|
MqttPacketReader.ReadFixedHeaderAsync(new TestMqttChannel(new MemoryStream()), CancellationToken.None).GetAwaiter().GetResult();
|
||||||
|
|||||||
@@ -422,7 +422,7 @@ namespace MQTTnet.Core.Tests
|
|||||||
|
|
||||||
using (var bodyStream = new MemoryStream(Join(buffer1), (int)headerStream.Position, header.RemainingLength))
|
using (var bodyStream = new MemoryStream(Join(buffer1), (int)headerStream.Position, header.RemainingLength))
|
||||||
{
|
{
|
||||||
var deserializedPacket = serializer.Deserialize(new ReceivedMqttPacket(header.Flags, new MqttPacketBodyReader(bodyStream.ToArray())));
|
var deserializedPacket = serializer.Deserialize(new ReceivedMqttPacket(header.Flags, new MqttPacketBodyReader(bodyStream.ToArray(), 0)));
|
||||||
var buffer2 = serializer.Serialize(deserializedPacket);
|
var buffer2 = serializer.Serialize(deserializedPacket);
|
||||||
|
|
||||||
Assert.AreEqual(expectedBase64Value, Convert.ToBase64String(Join(buffer2)));
|
Assert.AreEqual(expectedBase64Value, Convert.ToBase64String(Join(buffer2)));
|
||||||
|
|||||||
@@ -20,13 +20,7 @@ namespace MQTTnet.Core.Tests
|
|||||||
|
|
||||||
sm.Subscribe(sp);
|
sm.Subscribe(sp);
|
||||||
|
|
||||||
var pp = new MqttApplicationMessage
|
var result = sm.CheckSubscriptions("A/B/C", MqttQualityOfServiceLevel.AtMostOnce);
|
||||||
{
|
|
||||||
Topic = "A/B/C",
|
|
||||||
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce
|
|
||||||
};
|
|
||||||
|
|
||||||
var result = sm.CheckSubscriptions(pp);
|
|
||||||
Assert.IsTrue(result.IsSubscribed);
|
Assert.IsTrue(result.IsSubscribed);
|
||||||
Assert.AreEqual(result.QualityOfServiceLevel, MqttQualityOfServiceLevel.AtMostOnce);
|
Assert.AreEqual(result.QualityOfServiceLevel, MqttQualityOfServiceLevel.AtMostOnce);
|
||||||
}
|
}
|
||||||
@@ -41,13 +35,7 @@ namespace MQTTnet.Core.Tests
|
|||||||
|
|
||||||
sm.Subscribe(sp);
|
sm.Subscribe(sp);
|
||||||
|
|
||||||
var pp = new MqttApplicationMessage
|
var result = sm.CheckSubscriptions("A/B/C", MqttQualityOfServiceLevel.ExactlyOnce);
|
||||||
{
|
|
||||||
Topic = "A/B/C",
|
|
||||||
QualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce
|
|
||||||
};
|
|
||||||
|
|
||||||
var result = sm.CheckSubscriptions(pp);
|
|
||||||
Assert.IsTrue(result.IsSubscribed);
|
Assert.IsTrue(result.IsSubscribed);
|
||||||
Assert.AreEqual(result.QualityOfServiceLevel, MqttQualityOfServiceLevel.AtMostOnce);
|
Assert.AreEqual(result.QualityOfServiceLevel, MqttQualityOfServiceLevel.AtMostOnce);
|
||||||
}
|
}
|
||||||
@@ -63,13 +51,7 @@ namespace MQTTnet.Core.Tests
|
|||||||
|
|
||||||
sm.Subscribe(sp);
|
sm.Subscribe(sp);
|
||||||
|
|
||||||
var pp = new MqttApplicationMessage
|
var result = sm.CheckSubscriptions("A/B/C", MqttQualityOfServiceLevel.ExactlyOnce);
|
||||||
{
|
|
||||||
Topic = "A/B/C",
|
|
||||||
QualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce
|
|
||||||
};
|
|
||||||
|
|
||||||
var result = sm.CheckSubscriptions(pp);
|
|
||||||
Assert.IsTrue(result.IsSubscribed);
|
Assert.IsTrue(result.IsSubscribed);
|
||||||
Assert.AreEqual(result.QualityOfServiceLevel, MqttQualityOfServiceLevel.AtLeastOnce);
|
Assert.AreEqual(result.QualityOfServiceLevel, MqttQualityOfServiceLevel.AtLeastOnce);
|
||||||
}
|
}
|
||||||
@@ -84,13 +66,7 @@ namespace MQTTnet.Core.Tests
|
|||||||
|
|
||||||
sm.Subscribe(sp);
|
sm.Subscribe(sp);
|
||||||
|
|
||||||
var pp = new MqttApplicationMessage
|
Assert.IsFalse(sm.CheckSubscriptions("A/B/X", MqttQualityOfServiceLevel.AtMostOnce).IsSubscribed);
|
||||||
{
|
|
||||||
Topic = "A/B/X",
|
|
||||||
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce
|
|
||||||
};
|
|
||||||
|
|
||||||
Assert.IsFalse(sm.CheckSubscriptions(pp).IsSubscribed);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
@@ -103,19 +79,13 @@ namespace MQTTnet.Core.Tests
|
|||||||
|
|
||||||
sm.Subscribe(sp);
|
sm.Subscribe(sp);
|
||||||
|
|
||||||
var pp = new MqttApplicationMessage
|
Assert.IsTrue(sm.CheckSubscriptions("A/B/C", MqttQualityOfServiceLevel.AtMostOnce).IsSubscribed);
|
||||||
{
|
|
||||||
Topic = "A/B/C",
|
|
||||||
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce
|
|
||||||
};
|
|
||||||
|
|
||||||
Assert.IsTrue(sm.CheckSubscriptions(pp).IsSubscribed);
|
|
||||||
|
|
||||||
var up = new MqttUnsubscribePacket();
|
var up = new MqttUnsubscribePacket();
|
||||||
up.TopicFilters.Add("A/B/C");
|
up.TopicFilters.Add("A/B/C");
|
||||||
sm.Unsubscribe(up);
|
sm.Unsubscribe(up);
|
||||||
|
|
||||||
Assert.IsFalse(sm.CheckSubscriptions(pp).IsSubscribed);
|
Assert.IsFalse(sm.CheckSubscriptions("A/B/C", MqttQualityOfServiceLevel.AtMostOnce).IsSubscribed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MQTTnet.Adapter;
|
using MQTTnet.Adapter;
|
||||||
@@ -36,7 +35,7 @@ namespace MQTTnet.Core.Tests
|
|||||||
return Task.FromResult(0);
|
return Task.FromResult(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task SendPacketAsync(TimeSpan timeout, MqttBasePacket packet, CancellationToken cancellationToken)
|
public Task SendPacketAsync(MqttBasePacket packet, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
ThrowIfPartnerIsNull();
|
ThrowIfPartnerIsNull();
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||||
<TypeScriptToolsVersion>Latest</TypeScriptToolsVersion>
|
<TypeScriptToolsVersion>Latest</TypeScriptToolsVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
@@ -10,7 +10,8 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.8" />
|
<PackageReference Include="Microsoft.AspNetCore" Version="2.1.0" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.App" Version="2.1.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Microsoft.AspNetCore;
|
using Microsoft.AspNetCore;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using MQTTnet.AspNetCore;
|
||||||
|
|
||||||
namespace MQTTnet.TestApp.AspNetCore2
|
namespace MQTTnet.TestApp.AspNetCore2
|
||||||
{
|
{
|
||||||
@@ -12,6 +13,10 @@ namespace MQTTnet.TestApp.AspNetCore2
|
|||||||
|
|
||||||
private static IWebHost BuildWebHost(string[] args) =>
|
private static IWebHost BuildWebHost(string[] args) =>
|
||||||
WebHost.CreateDefaultBuilder(args)
|
WebHost.CreateDefaultBuilder(args)
|
||||||
|
.UseKestrel(o => {
|
||||||
|
o.ListenAnyIP(1883, l => l.UseMqtt());
|
||||||
|
o.ListenAnyIP(5000); // default http pipeline
|
||||||
|
})
|
||||||
.UseStartup<Startup>()
|
.UseStartup<Startup>()
|
||||||
.Build();
|
.Build();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,8 +17,12 @@ namespace MQTTnet.TestApp.AspNetCore2
|
|||||||
|
|
||||||
public void ConfigureServices(IServiceCollection services)
|
public void ConfigureServices(IServiceCollection services)
|
||||||
{
|
{
|
||||||
var mqttServerOptions = new MqttServerOptionsBuilder().Build();
|
var mqttServerOptions = new MqttServerOptionsBuilder()
|
||||||
services.AddHostedMqttServer(mqttServerOptions);
|
.WithoutDefaultEndpoint()
|
||||||
|
.Build();
|
||||||
|
services
|
||||||
|
.AddHostedMqttServer(mqttServerOptions)
|
||||||
|
.AddMqttConnectionHandler();
|
||||||
}
|
}
|
||||||
|
|
||||||
// In class _Startup_ of the ASP.NET Core 2.0 project.
|
// In class _Startup_ of the ASP.NET Core 2.0 project.
|
||||||
|
|||||||
@@ -12,16 +12,87 @@ namespace MQTTnet.TestApp.NetCore
|
|||||||
{
|
{
|
||||||
public static class PerformanceTest
|
public static class PerformanceTest
|
||||||
{
|
{
|
||||||
public static async Task RunAsync()
|
public static void RunClientOnly()
|
||||||
{
|
{
|
||||||
Console.WriteLine("Press 'c' for concurrent sends. Otherwise in one batch.");
|
try
|
||||||
var concurrent = Console.ReadKey(true).KeyChar == 'c';
|
{
|
||||||
|
var options = new MqttClientOptions
|
||||||
|
{
|
||||||
|
ChannelOptions = new MqttClientTcpOptions
|
||||||
|
{
|
||||||
|
Server = "127.0.0.1"
|
||||||
|
},
|
||||||
|
CleanSession = true
|
||||||
|
};
|
||||||
|
|
||||||
var server = Task.Run(RunServerAsync);
|
var client = new MqttFactory().CreateMqttClient();
|
||||||
await Task.Delay(1000);
|
client.ConnectAsync(options).GetAwaiter().GetResult();
|
||||||
var client = Task.Run(() => RunClientAsync(2000, TimeSpan.FromMilliseconds(10), concurrent));
|
|
||||||
|
|
||||||
await Task.WhenAll(server, client).ConfigureAwait(false);
|
var message = CreateMessage();
|
||||||
|
var stopwatch = new Stopwatch();
|
||||||
|
|
||||||
|
for (var i = 0; i < 10; i++)
|
||||||
|
{
|
||||||
|
var sentMessagesCount = 0;
|
||||||
|
|
||||||
|
stopwatch.Restart();
|
||||||
|
while (stopwatch.ElapsedMilliseconds < 1000)
|
||||||
|
{
|
||||||
|
client.PublishAsync(message).GetAwaiter().GetResult();
|
||||||
|
sentMessagesCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine($"Sending {sentMessagesCount} messages per second. #" + (i + 1));
|
||||||
|
|
||||||
|
GC.Collect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
Console.WriteLine(exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RunClientAndServer()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var mqttServer = new MqttFactory().CreateMqttServer();
|
||||||
|
mqttServer.StartAsync(new MqttServerOptions()).GetAwaiter().GetResult();
|
||||||
|
|
||||||
|
var options = new MqttClientOptions
|
||||||
|
{
|
||||||
|
ChannelOptions = new MqttClientTcpOptions
|
||||||
|
{
|
||||||
|
Server = "127.0.0.1"
|
||||||
|
},
|
||||||
|
CleanSession = true
|
||||||
|
};
|
||||||
|
|
||||||
|
var client = new MqttFactory().CreateMqttClient();
|
||||||
|
client.ConnectAsync(options).GetAwaiter().GetResult();
|
||||||
|
|
||||||
|
var message = CreateMessage();
|
||||||
|
var stopwatch = new Stopwatch();
|
||||||
|
|
||||||
|
for (var i = 0; i < 10; i++)
|
||||||
|
{
|
||||||
|
stopwatch.Restart();
|
||||||
|
|
||||||
|
var sentMessagesCount = 0;
|
||||||
|
while (stopwatch.ElapsedMilliseconds < 1000)
|
||||||
|
{
|
||||||
|
client.PublishAsync(message).GetAwaiter().GetResult();
|
||||||
|
sentMessagesCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine($"Sending {sentMessagesCount} messages per second. #" + (i + 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
Console.WriteLine(exception);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Task RunClientsAsync(int msgChunkSize, TimeSpan interval, bool concurrent)
|
private static Task RunClientsAsync(int msgChunkSize, TimeSpan interval, bool concurrent)
|
||||||
@@ -53,29 +124,8 @@ namespace MQTTnet.TestApp.NetCore
|
|||||||
}
|
}
|
||||||
|
|
||||||
var message = CreateMessage();
|
var message = CreateMessage();
|
||||||
var messages = new[] { message };
|
|
||||||
|
|
||||||
var stopwatch = Stopwatch.StartNew();
|
var stopwatch = Stopwatch.StartNew();
|
||||||
|
|
||||||
var sentMessagesCount = 0;
|
|
||||||
while (stopwatch.ElapsedMilliseconds < 1000)
|
|
||||||
{
|
|
||||||
client.PublishAsync(messages).GetAwaiter().GetResult();
|
|
||||||
sentMessagesCount++;
|
|
||||||
}
|
|
||||||
|
|
||||||
Console.WriteLine($"Sending {sentMessagesCount} messages per second. #1");
|
|
||||||
|
|
||||||
sentMessagesCount = 0;
|
|
||||||
stopwatch.Restart();
|
|
||||||
while (stopwatch.ElapsedMilliseconds < 1000)
|
|
||||||
{
|
|
||||||
await client.PublishAsync(messages).ConfigureAwait(false);
|
|
||||||
sentMessagesCount++;
|
|
||||||
}
|
|
||||||
|
|
||||||
Console.WriteLine($"Sending {sentMessagesCount} messages per second. #2");
|
|
||||||
|
|
||||||
var testMessageCount = 10000;
|
var testMessageCount = 10000;
|
||||||
for (var i = 0; i < testMessageCount; i++)
|
for (var i = 0; i < testMessageCount; i++)
|
||||||
{
|
{
|
||||||
@@ -142,38 +192,5 @@ namespace MQTTnet.TestApp.NetCore
|
|||||||
Interlocked.Increment(ref count);
|
Interlocked.Increment(ref count);
|
||||||
return Task.Run(() => client.PublishAsync(applicationMessage));
|
return Task.Run(() => client.PublishAsync(applicationMessage));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task RunServerAsync()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var mqttServer = new MqttFactory().CreateMqttServer();
|
|
||||||
|
|
||||||
////var msgs = 0;
|
|
||||||
////var stopwatch = Stopwatch.StartNew();
|
|
||||||
////mqttServer.ApplicationMessageReceived += (sender, args) =>
|
|
||||||
////{
|
|
||||||
//// msgs++;
|
|
||||||
//// if (stopwatch.ElapsedMilliseconds > 1000)
|
|
||||||
//// {
|
|
||||||
//// Console.WriteLine($"received {msgs}");
|
|
||||||
//// msgs = 0;
|
|
||||||
//// stopwatch.Restart();
|
|
||||||
//// }
|
|
||||||
////};
|
|
||||||
await mqttServer.StartAsync(new MqttServerOptions());
|
|
||||||
|
|
||||||
Console.WriteLine("Press any key to exit.");
|
|
||||||
Console.ReadLine();
|
|
||||||
|
|
||||||
await mqttServer.StopAsync().ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Console.WriteLine(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
Console.ReadLine();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ namespace MQTTnet.TestApp.NetCore
|
|||||||
Console.WriteLine("5 = Start public broker test");
|
Console.WriteLine("5 = Start public broker test");
|
||||||
Console.WriteLine("6 = Start server & client");
|
Console.WriteLine("6 = Start server & client");
|
||||||
Console.WriteLine("7 = Client flow test");
|
Console.WriteLine("7 = Client flow test");
|
||||||
|
Console.WriteLine("8 = Start performance test (client only)");
|
||||||
|
Console.WriteLine("9 = Start server (no trace)");
|
||||||
|
|
||||||
var pressedKey = Console.ReadKey(true);
|
var pressedKey = Console.ReadKey(true);
|
||||||
if (pressedKey.KeyChar == '1')
|
if (pressedKey.KeyChar == '1')
|
||||||
@@ -34,7 +36,8 @@ namespace MQTTnet.TestApp.NetCore
|
|||||||
}
|
}
|
||||||
else if (pressedKey.KeyChar == '3')
|
else if (pressedKey.KeyChar == '3')
|
||||||
{
|
{
|
||||||
Task.Run(PerformanceTest.RunAsync);
|
PerformanceTest.RunClientAndServer();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
else if (pressedKey.KeyChar == '4')
|
else if (pressedKey.KeyChar == '4')
|
||||||
{
|
{
|
||||||
@@ -52,6 +55,16 @@ namespace MQTTnet.TestApp.NetCore
|
|||||||
{
|
{
|
||||||
Task.Run(ClientFlowTest.RunAsync);
|
Task.Run(ClientFlowTest.RunAsync);
|
||||||
}
|
}
|
||||||
|
else if (pressedKey.KeyChar == '8')
|
||||||
|
{
|
||||||
|
PerformanceTest.RunClientOnly();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (pressedKey.KeyChar == '9')
|
||||||
|
{
|
||||||
|
ServerTest.RunEmptyServer();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Thread.Sleep(Timeout.Infinite);
|
Thread.Sleep(Timeout.Infinite);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,12 +8,19 @@ namespace MQTTnet.TestApp.NetCore
|
|||||||
{
|
{
|
||||||
public static class ServerTest
|
public static class ServerTest
|
||||||
{
|
{
|
||||||
|
public static void RunEmptyServer()
|
||||||
|
{
|
||||||
|
var mqttServer = new MqttFactory().CreateMqttServer();
|
||||||
|
mqttServer.StartAsync(new MqttServerOptions()).GetAwaiter().GetResult();
|
||||||
|
|
||||||
|
Console.WriteLine("Press any key to exit.");
|
||||||
|
Console.ReadLine();
|
||||||
|
}
|
||||||
|
|
||||||
public static async Task RunAsync()
|
public static async Task RunAsync()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
MqttNetConsoleLogger.ForwardToConsole();
|
|
||||||
|
|
||||||
var options = new MqttServerOptions
|
var options = new MqttServerOptions
|
||||||
{
|
{
|
||||||
ConnectionValidator = p =>
|
ConnectionValidator = p =>
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:server="using:MQTTnet.Server"
|
||||||
|
xmlns:interop="using:Windows.UI.Xaml.Interop"
|
||||||
d:DesignHeight="800"
|
d:DesignHeight="800"
|
||||||
d:DesignWidth="800"
|
d:DesignWidth="800"
|
||||||
mc:Ignorable="d">
|
mc:Ignorable="d">
|
||||||
@@ -31,7 +33,7 @@
|
|||||||
<CheckBox x:Name="CleanSession" IsChecked="True"></CheckBox>
|
<CheckBox x:Name="CleanSession" IsChecked="True"></CheckBox>
|
||||||
<TextBlock>Keep alive interval:</TextBlock>
|
<TextBlock>Keep alive interval:</TextBlock>
|
||||||
<TextBox x:Name="KeepAliveInterval" Text="5"></TextBox>
|
<TextBox x:Name="KeepAliveInterval" Text="5"></TextBox>
|
||||||
|
|
||||||
<StackPanel Orientation="Horizontal">
|
<StackPanel Orientation="Horizontal">
|
||||||
<RadioButton x:Name="UseTcp" IsChecked="True" GroupName="connection">TCP</RadioButton>
|
<RadioButton x:Name="UseTcp" IsChecked="True" GroupName="connection">TCP</RadioButton>
|
||||||
<RadioButton x:Name="UseWs" GroupName="connection">WS</RadioButton>
|
<RadioButton x:Name="UseWs" GroupName="connection">WS</RadioButton>
|
||||||
@@ -142,6 +144,7 @@
|
|||||||
|
|
||||||
<CheckBox x:Name="ServerPersistRetainedMessages" IsChecked="True">Persist retained messages in JSON format</CheckBox>
|
<CheckBox x:Name="ServerPersistRetainedMessages" IsChecked="True">Persist retained messages in JSON format</CheckBox>
|
||||||
<CheckBox x:Name="ServerClearRetainedMessages">Clear previously retained messages on startup</CheckBox>
|
<CheckBox x:Name="ServerClearRetainedMessages">Clear previously retained messages on startup</CheckBox>
|
||||||
|
<CheckBox x:Name="ServerAllowPersistentSessions">Allow persistent sessions</CheckBox>
|
||||||
|
|
||||||
<StackPanel Orientation="Horizontal">
|
<StackPanel Orientation="Horizontal">
|
||||||
<Button Width="120" Margin="0,0,10,0" Click="StartServer">Start</Button>
|
<Button Width="120" Margin="0,0,10,0" Click="StartServer">Start</Button>
|
||||||
@@ -149,11 +152,6 @@
|
|||||||
</StackPanel>
|
</StackPanel>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</PivotItem>
|
</PivotItem>
|
||||||
<PivotItem Header="Log">
|
|
||||||
<StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
|
||||||
<Button Click="ClearLog" Width="120">Clear</Button>
|
|
||||||
</StackPanel>
|
|
||||||
</PivotItem>
|
|
||||||
|
|
||||||
<PivotItem Header="Sessions">
|
<PivotItem Header="Sessions">
|
||||||
<StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
<StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||||
@@ -162,12 +160,23 @@
|
|||||||
<Button Width="120" Margin="0,0,10,0" Click="ClearSessions">Clear</Button>
|
<Button Width="120" Margin="0,0,10,0" Click="ClearSessions">Clear</Button>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<ListView>
|
<ListView ItemsSource="{Binding}" x:Name="ListViewSessions">
|
||||||
<ListView.ItemTemplate>
|
<ListView.ItemTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate x:DataType="server:IMqttClientSessionStatus">
|
||||||
<Grid>
|
<StackPanel Orientation="Horizontal">
|
||||||
<TextBlock></TextBlock>
|
<StackPanel.Resources>
|
||||||
</Grid>
|
<Style TargetType="TextBlock">
|
||||||
|
<Setter Property="Margin" Value="0,0,10,0"></Setter>
|
||||||
|
</Style>
|
||||||
|
</StackPanel.Resources>
|
||||||
|
<TextBlock Text="{Binding ClientId}"></TextBlock>
|
||||||
|
<TextBlock Text="{Binding Endpoint}"></TextBlock>
|
||||||
|
<TextBlock Text="{Binding IsConnected}"></TextBlock>
|
||||||
|
<TextBlock Text="{Binding LastPacketReceived}"></TextBlock>
|
||||||
|
<TextBlock Text="{Binding LastNonKeepAlivePacketReceived}"></TextBlock>
|
||||||
|
<TextBlock Text="{Binding ProtocolVersion}"></TextBlock>
|
||||||
|
<TextBlock Text="{Binding PendingApplicationMessagesCount}"></TextBlock>
|
||||||
|
</StackPanel>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</ListView.ItemTemplate>
|
</ListView.ItemTemplate>
|
||||||
</ListView>
|
</ListView>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Windows.Security.Cryptography.Certificates;
|
using Windows.Security.Cryptography.Certificates;
|
||||||
@@ -21,6 +22,7 @@ namespace MQTTnet.TestApp.UniversalWindows
|
|||||||
public sealed partial class MainPage
|
public sealed partial class MainPage
|
||||||
{
|
{
|
||||||
private readonly ConcurrentQueue<MqttNetLogMessage> _traceMessages = new ConcurrentQueue<MqttNetLogMessage>();
|
private readonly ConcurrentQueue<MqttNetLogMessage> _traceMessages = new ConcurrentQueue<MqttNetLogMessage>();
|
||||||
|
private readonly ObservableCollection<IMqttClientSessionStatus> _sessions = new ObservableCollection<IMqttClientSessionStatus>();
|
||||||
|
|
||||||
private IMqttClient _mqttClient;
|
private IMqttClient _mqttClient;
|
||||||
private IMqttServer _mqttServer;
|
private IMqttServer _mqttServer;
|
||||||
@@ -306,6 +308,7 @@ namespace MQTTnet.TestApp.UniversalWindows
|
|||||||
var options = new MqttServerOptions();
|
var options = new MqttServerOptions();
|
||||||
options.DefaultEndpointOptions.Port = int.Parse(ServerPort.Text);
|
options.DefaultEndpointOptions.Port = int.Parse(ServerPort.Text);
|
||||||
options.Storage = storage;
|
options.Storage = storage;
|
||||||
|
options.EnablePersistentSessions = ServerAllowPersistentSessions.IsChecked == true;
|
||||||
|
|
||||||
await _mqttServer.StartAsync(options);
|
await _mqttServer.StartAsync(options);
|
||||||
}
|
}
|
||||||
@@ -374,10 +377,25 @@ namespace MQTTnet.TestApp.UniversalWindows
|
|||||||
|
|
||||||
private void ClearSessions(object sender, RoutedEventArgs e)
|
private void ClearSessions(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
|
_sessions.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RefreshSessions(object sender, RoutedEventArgs e)
|
private async void RefreshSessions(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
|
if (_mqttServer == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var sessions = await _mqttServer.GetClientSessionsStatusAsync();
|
||||||
|
_sessions.Clear();
|
||||||
|
|
||||||
|
foreach (var session in sessions)
|
||||||
|
{
|
||||||
|
_sessions.Add(session);
|
||||||
|
}
|
||||||
|
|
||||||
|
ListViewSessions.DataContext = _sessions;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task WikiCode()
|
private async Task WikiCode()
|
||||||
|
|||||||
Reference in New Issue
Block a user