Compare commits

...
18 Commits
Author SHA1 Message Date
Christian c5d22f4b27 Update docs 2018-03-13 20:23:36 +01:00
Christian e57ec11ed8 Update libs 2018-03-13 20:23:31 +01:00
Christian 56acc82445 Fix null ref in server when using interceptor 2018-03-13 20:23:22 +01:00
Christian 6e828d091f Override ToString() for LogMessage 2018-03-13 20:23:07 +01:00
Christian e1d8697331 Fix wrong parsing and generating of MqttConnAck for version 3.1.0 2018-03-13 20:22:48 +01:00
Christian 90098c2006 Optimize dispatcher 2018-03-13 20:22:13 +01:00
Christian b3a25fb9e8 Fix subscription handling of _ManagedClient_. 2018-03-05 20:43:37 +01:00
Christian f85ad4965b Refactor keep alive monitoring and add unit tests. 2018-02-28 21:43:19 +01:00
Christian f5696b6fc0 Merge branch 'develop' 2018-02-21 21:33:01 +01:00
Christian c26555aca5 Refactor ManagedClient 2018-02-21 21:32:21 +01:00
Christian 0accb467b5 Merge branch 'develop' of https://github.com/chkr1011/MQTTnet into develop 2018-02-21 20:44:10 +01:00
Christian 3b34fd4750 Fix build 2018-02-21 20:44:05 +01:00
Christian abb216c055 Fix retained message handling 2018-02-21 20:43:22 +01:00
Christian 1760dc7d35 Fix async issues 2018-02-21 20:43:09 +01:00
Christian 034e7fd750 Add event for processed application message (managed client only) 2018-02-21 20:42:39 +01:00
Christian 76c7b319b8 Fix build errors 2018-02-21 20:42:05 +01:00
Christian 09010e95a2 Merge pull request #180 from lerppana/managed_unsubscribe_fix
Fix ManagedMqttClient missing call to unsubscribe topics
2018-02-21 20:09:10 +01:00
Lassi Laati 2341d66192 Fix ManagedMqttClient missing call to unsubscribe topics 2018-02-20 09:23:47 +02:00
24 changed files with 371 additions and 196 deletions
+10 -11
View File
@@ -2,7 +2,7 @@
<package > <package >
<metadata> <metadata>
<id>MQTTnet</id> <id>MQTTnet</id>
<version>2.7.0</version> <version>2.7.1</version>
<authors>Christian Kratky</authors> <authors>Christian Kratky</authors>
<owners>Christian Kratky</owners> <owners>Christian Kratky</owners>
<licenseUrl>https://github.com/chkr1011/MQTTnet/blob/master/LICENSE</licenseUrl> <licenseUrl>https://github.com/chkr1011/MQTTnet/blob/master/LICENSE</licenseUrl>
@@ -10,16 +10,15 @@
<iconUrl>https://raw.githubusercontent.com/chkr1011/MQTTnet/master/Images/Logo_128x128.png</iconUrl> <iconUrl>https://raw.githubusercontent.com/chkr1011/MQTTnet/master/Images/Logo_128x128.png</iconUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>MQTTnet is a high performance .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker).</description> <description>MQTTnet is a high performance .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker).</description>
<releaseNotes>* [Core] Fixed some still thread blocking parts in the code (thanks to @kpreisser). <releaseNotes>* [Core] Fixed wrong parsing of ConnAck packet for protocol version 3.1.0.
* [Core] Updated 3rd-Party packages. * [Core] Log messages are now overriding ToString() and providing a ready to use text representation.
* [Core] Fixed wrong packet identifier calculation (thanks to @benpittoors). * [Client] Optimized package dispatcher and added several new exceptions.
* [Core] Fixed an issue when reading the body of a package from a disconnected sender (thanks to @kpreisser). * [Client] The _ManagedClient_ now has an event which is fired after a queued application message was processed (including exception).
* [Core] Fixed wrong parsing of the body length (thanks to @kpreisser). * [Client] The _ManagedClient_ now supports unsubscribing (thanks to @lerppana)
* [Client] The client interfaces are now implementing _IDisposable_. * [Server] Fixed some minor async issues.
* [Client] The disconnected event now contains the exception which was thrown and causing the disconnect. * [Server] Fixed wrong comparison of the topic and QoS for retained messages.
* [Server] Fixed an issue which lets the server block 1 second after accepting a new connection (thanks to @kpreisser). * [Server] Added a property which provides access to the used options (read only).
* [Server] The server now allows managing client subscriptions. * [Server] Fixed a null ref expection when using an interceptor and publishing via the server directly.
* [Server] Added events for topic subscriptions.
</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>
+10 -8
View File
@@ -1,6 +1,7 @@
param([string]$assemblyVersion, [string]$nugetVersion) param([string]$assemblyVersion, [string]$nugetVersion)
if ([string]::IsNullOrEmpty($version)) {$version = "0.0.1"} if ([string]::IsNullOrEmpty($assemblyVersion)) {$assemblyVersion = "0.0.1"}
if ([string]::IsNullOrEmpty($nugetVersion)) {$nugetVersion = "0.0.1"}
$vswhere = ${Env:\ProgramFiles(x86)} + '\Microsoft Visual Studio\Installer\vswhere' $vswhere = ${Env:\ProgramFiles(x86)} + '\Microsoft Visual Studio\Installer\vswhere'
@@ -9,14 +10,14 @@ if ($path) {
$msbuild = join-path $path 'MSBuild\15.0\Bin\MSBuild.exe' $msbuild = join-path $path 'MSBuild\15.0\Bin\MSBuild.exe'
# Build the core library # Build the core library
&$msbuild ..\Frameworks\MQTTnet.Netstandard\MQTTnet.Netstandard.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net452" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m &$msbuild ..\Frameworks\MQTTnet.Netstandard\MQTTnet.Netstandard.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net452" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
&$msbuild ..\Frameworks\MQTTnet.Netstandard\MQTTnet.Netstandard.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net461" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m &$msbuild ..\Frameworks\MQTTnet.Netstandard\MQTTnet.Netstandard.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net461" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
&$msbuild ..\Frameworks\MQTTnet.Netstandard\MQTTnet.Netstandard.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard1.3" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m &$msbuild ..\Frameworks\MQTTnet.Netstandard\MQTTnet.Netstandard.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard1.3" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
&$msbuild ..\Frameworks\MQTTnet.Netstandard\MQTTnet.Netstandard.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard2.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m &$msbuild ..\Frameworks\MQTTnet.Netstandard\MQTTnet.Netstandard.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard2.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
&$msbuild ..\Frameworks\MQTTnet.Netstandard\MQTTnet.Netstandard.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="uap10.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m &$msbuild ..\Frameworks\MQTTnet.Netstandard\MQTTnet.Netstandard.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="uap10.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
# Build the ASP.NET Core 2.0 extension # Build the ASP.NET Core 2.0 extension
&$msbuild ..\Frameworks\MQTTnet.AspNetCore\MQTTnet.AspNetCore.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard2.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m &$msbuild ..\Frameworks\MQTTnet.AspNetCore\MQTTnet.AspNetCore.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard2.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
# Build the RPC extension # Build the RPC extension
&$msbuild ..\Extensions\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net452" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m &$msbuild ..\Extensions\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net452" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m
@@ -25,7 +26,8 @@ if ($path) {
&$msbuild ..\Extensions\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard2.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m &$msbuild ..\Extensions\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard2.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m
&$msbuild ..\Extensions\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="uap10.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m &$msbuild ..\Extensions\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="uap10.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m
Remove-Item .\NuGet -Force -Recurse 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
Binary file not shown.
+4 -1
View File
@@ -3,4 +3,7 @@ In order to import the key for code signing on a new developer machine use the f
> sn –i codeSigningKey.pfx VS_KEY_EFCA4C5B6DFD4B4F > sn –i codeSigningKey.pfx VS_KEY_EFCA4C5B6DFD4B4F
The container name may be different. The container name may be different.
# Check if the assembly has a strong name
> sn -vf MQTTnet.dll
@@ -20,6 +20,7 @@
<TargetFrameworkIdentifier>.NETCore</TargetFrameworkIdentifier> <TargetFrameworkIdentifier>.NETCore</TargetFrameworkIdentifier>
<TargetFrameworkVersion>v5.0</TargetFrameworkVersion> <TargetFrameworkVersion>v5.0</TargetFrameworkVersion>
<DefineConstants>$(DefineConstants);WINDOWS_UWP</DefineConstants> <DefineConstants>$(DefineConstants);WINDOWS_UWP</DefineConstants>
<DefaultLanguage>en</DefaultLanguage>
<LanguageTargets>$(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets</LanguageTargets> <LanguageTargets>$(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets</LanguageTargets>
</PropertyGroup> </PropertyGroup>
@@ -335,7 +335,13 @@ namespace MQTTnet.Client
private async Task<TResponsePacket> SendAndReceiveAsync<TResponsePacket>(MqttBasePacket requestPacket) where TResponsePacket : MqttBasePacket private async Task<TResponsePacket> SendAndReceiveAsync<TResponsePacket>(MqttBasePacket requestPacket) where TResponsePacket : MqttBasePacket
{ {
var packetAwaiter = _packetDispatcher.WaitForPacketAsync(requestPacket, typeof(TResponsePacket), _options.CommunicationTimeout); ushort identifier = 0;
if (requestPacket is IMqttPacketWithIdentifier requestPacketWithIdentifier)
{
identifier = requestPacketWithIdentifier.PacketIdentifier;
}
var packetAwaiter = _packetDispatcher.WaitForPacketAsync(typeof(TResponsePacket), identifier, _options.CommunicationTimeout);
await _adapter.SendPacketsAsync(_options.CommunicationTimeout, _cancellationTokenSource.Token, requestPacket).ConfigureAwait(false); await _adapter.SendPacketsAsync(_options.CommunicationTimeout, _cancellationTokenSource.Token, requestPacket).ConfigureAwait(false);
return (TResponsePacket)await packetAwaiter.ConfigureAwait(false); return (TResponsePacket)await packetAwaiter.ConfigureAwait(false);
} }
@@ -10,8 +10,7 @@ namespace MQTTnet.Client
{ {
public class MqttPacketDispatcher public class MqttPacketDispatcher
{ {
private readonly ConcurrentDictionary<Type, TaskCompletionSource<MqttBasePacket>> _packetByResponseType = new ConcurrentDictionary<Type, TaskCompletionSource<MqttBasePacket>>(); private readonly ConcurrentDictionary<Type, ConcurrentDictionary<ushort, TaskCompletionSource<MqttBasePacket>>> _awaiters = new ConcurrentDictionary<Type, ConcurrentDictionary<ushort, TaskCompletionSource<MqttBasePacket>>>();
private readonly ConcurrentDictionary<Type, ConcurrentDictionary<ushort, TaskCompletionSource<MqttBasePacket>>> _packetByResponseTypeAndIdentifier = new ConcurrentDictionary<Type, ConcurrentDictionary<ushort, TaskCompletionSource<MqttBasePacket>>>();
private readonly IMqttNetLogger _logger; private readonly IMqttNetLogger _logger;
public MqttPacketDispatcher(IMqttNetLogger logger) public MqttPacketDispatcher(IMqttNetLogger logger)
@@ -19,11 +18,9 @@ namespace MQTTnet.Client
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); _logger = logger ?? throw new ArgumentNullException(nameof(logger));
} }
public async Task<MqttBasePacket> WaitForPacketAsync(MqttBasePacket request, Type responseType, TimeSpan timeout) public async Task<MqttBasePacket> WaitForPacketAsync(Type responseType, ushort identifier, TimeSpan timeout)
{ {
if (request == null) throw new ArgumentNullException(nameof(request)); var packetAwaiter = AddPacketAwaiter(responseType, identifier);
var packetAwaiter = AddPacketAwaiter(request, responseType);
try try
{ {
return await packetAwaiter.Task.TimeoutAfter(timeout).ConfigureAwait(false); return await packetAwaiter.Task.TimeoutAfter(timeout).ConfigureAwait(false);
@@ -35,7 +32,7 @@ namespace MQTTnet.Client
} }
finally finally
{ {
RemovePacketAwaiter(request, responseType); RemovePacketAwaiter(responseType, identifier);
} }
} }
@@ -44,21 +41,20 @@ namespace MQTTnet.Client
if (packet == null) throw new ArgumentNullException(nameof(packet)); if (packet == null) throw new ArgumentNullException(nameof(packet));
var type = packet.GetType(); var type = packet.GetType();
if (packet is IMqttPacketWithIdentifier withIdentifier)
if (_awaiters.TryGetValue(type, out var byId))
{ {
if (_packetByResponseTypeAndIdentifier.TryGetValue(type, out var byid)) ushort identifier = 0;
if (packet is IMqttPacketWithIdentifier packetWithIdentifier)
{ {
if (byid.TryRemove(withIdentifier.PacketIdentifier, out var tcs)) identifier = packetWithIdentifier.PacketIdentifier;
{ }
tcs.TrySetResult(packet);
return; if (byId.TryRemove(identifier, out var tcs))
} {
tcs.TrySetResult(packet);
return;
} }
}
else if (_packetByResponseType.TryRemove(type, out var tcs))
{
tcs.TrySetResult(packet);
return;
} }
throw new InvalidOperationException($"Packet of type '{type.Name}' not handled or dispatched."); throw new InvalidOperationException($"Packet of type '{type.Name}' not handled or dispatched.");
@@ -66,38 +62,26 @@ namespace MQTTnet.Client
public void Reset() public void Reset()
{ {
_packetByResponseTypeAndIdentifier.Clear(); _awaiters.Clear();
_packetByResponseType.Clear();
} }
private TaskCompletionSource<MqttBasePacket> AddPacketAwaiter(MqttBasePacket request, Type responseType) private TaskCompletionSource<MqttBasePacket> AddPacketAwaiter(Type responseType, ushort identifier)
{ {
var tcs = new TaskCompletionSource<MqttBasePacket>(); var tcs = new TaskCompletionSource<MqttBasePacket>();
if (request is IMqttPacketWithIdentifier requestWithIdentifier) var byId = _awaiters.GetOrAdd(responseType, key => new ConcurrentDictionary<ushort, TaskCompletionSource<MqttBasePacket>>());
if (!byId.TryAdd(identifier, tcs))
{ {
var byId = _packetByResponseTypeAndIdentifier.GetOrAdd(responseType, key => new ConcurrentDictionary<ushort, TaskCompletionSource<MqttBasePacket>>()); throw new InvalidOperationException($"The packet dispatcher already has an awaiter for packet of type '{responseType}' with identifier {identifier}.");
byId[requestWithIdentifier.PacketIdentifier] = tcs;
} }
else
{
_packetByResponseType[responseType] = tcs;
}
return tcs; return tcs;
} }
private void RemovePacketAwaiter(MqttBasePacket request, Type responseType) private void RemovePacketAwaiter(Type responseType, ushort identifier)
{ {
if (request is IMqttPacketWithIdentifier requestWithIdentifier) var byId = _awaiters.GetOrAdd(responseType, key => new ConcurrentDictionary<ushort, TaskCompletionSource<MqttBasePacket>>());
{ byId.TryRemove(identifier, out var _);
var byId = _packetByResponseTypeAndIdentifier.GetOrAdd(responseType, key => new ConcurrentDictionary<ushort, TaskCompletionSource<MqttBasePacket>>());
byId.TryRemove(requestWithIdentifier.PacketIdentifier, out var _);
}
else
{
_packetByResponseType.TryRemove(responseType, out var _);
}
} }
} }
} }
@@ -28,5 +28,16 @@ namespace MQTTnet.Diagnostics
public string Message { get; } public string Message { get; }
public Exception Exception { get; } public Exception Exception { get; }
public override string ToString()
{
var result = $"[{Timestamp:O}] [{LogId}] [{ThreadId}] [{Source}] [{Level}]: {Message}";
if (Exception != null)
{
result += Environment.NewLine + Exception;
}
return result;
}
} }
} }
@@ -15,8 +15,7 @@
<Description /> <Description />
<Authors /> <Authors />
<PackageId /> <PackageId />
<SignAssembly>true</SignAssembly> <SignAssembly>false</SignAssembly>
<AssemblyOriginatorKeyFile>codeSigningKey.pfx</AssemblyOriginatorKeyFile>
<DelaySign>false</DelaySign> <DelaySign>false</DelaySign>
</PropertyGroup> </PropertyGroup>
@@ -29,6 +28,7 @@
<TargetFrameworkIdentifier>.NETCore</TargetFrameworkIdentifier> <TargetFrameworkIdentifier>.NETCore</TargetFrameworkIdentifier>
<TargetFrameworkVersion>v5.0</TargetFrameworkVersion> <TargetFrameworkVersion>v5.0</TargetFrameworkVersion>
<DefineConstants>$(DefineConstants);WINDOWS_UWP</DefineConstants> <DefineConstants>$(DefineConstants);WINDOWS_UWP</DefineConstants>
<DefaultLanguage>en</DefaultLanguage>
<LanguageTargets>$(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets</LanguageTargets> <LanguageTargets>$(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets</LanguageTargets>
</PropertyGroup> </PropertyGroup>
@@ -0,0 +1,19 @@
using System;
namespace MQTTnet.ManagedClient
{
public class ApplicationMessageProcessedEventArgs : EventArgs
{
public ApplicationMessageProcessedEventArgs(MqttApplicationMessage applicationMessage, Exception exception)
{
ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
Exception = exception;
}
public MqttApplicationMessage ApplicationMessage { get; }
public Exception Exception { get; }
public bool HasFailed => Exception != null;
public bool HasSucceeded => Exception == null;
}
}
@@ -12,6 +12,8 @@ namespace MQTTnet.ManagedClient
event EventHandler<MqttClientConnectedEventArgs> Connected; event EventHandler<MqttClientConnectedEventArgs> Connected;
event EventHandler<MqttClientDisconnectedEventArgs> Disconnected; event EventHandler<MqttClientDisconnectedEventArgs> Disconnected;
event EventHandler<ApplicationMessageProcessedEventArgs> ApplicationMessageProcessed;
Task StartAsync(IManagedMqttClientOptions options); Task StartAsync(IManagedMqttClientOptions options);
Task StopAsync(); Task StopAsync();
@@ -16,6 +16,7 @@ namespace MQTTnet.ManagedClient
private readonly BlockingCollection<MqttApplicationMessage> _messageQueue = new BlockingCollection<MqttApplicationMessage>(); private readonly BlockingCollection<MqttApplicationMessage> _messageQueue = new BlockingCollection<MqttApplicationMessage>();
private readonly Dictionary<string, MqttQualityOfServiceLevel> _subscriptions = new Dictionary<string, MqttQualityOfServiceLevel>(); private readonly Dictionary<string, MqttQualityOfServiceLevel> _subscriptions = new Dictionary<string, MqttQualityOfServiceLevel>();
private readonly SemaphoreSlim _subscriptionsSemaphore = new SemaphoreSlim(1, 1); private readonly SemaphoreSlim _subscriptionsSemaphore = new SemaphoreSlim(1, 1);
private readonly List<string> _unsubscriptions = new List<string>();
private readonly IMqttClient _mqttClient; private readonly IMqttClient _mqttClient;
private readonly IMqttNetLogger _logger; private readonly IMqttNetLogger _logger;
@@ -27,7 +28,7 @@ namespace MQTTnet.ManagedClient
private IManagedMqttClientOptions _options; private IManagedMqttClientOptions _options;
private bool _subscriptionsNotPushed; private bool _subscriptionsNotPushed;
public ManagedMqttClient(IMqttClient mqttClient, IMqttNetLogger logger) public ManagedMqttClient(IMqttClient mqttClient, IMqttNetLogger logger)
{ {
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); _logger = logger ?? throw new ArgumentNullException(nameof(logger));
@@ -43,6 +44,8 @@ namespace MQTTnet.ManagedClient
public event EventHandler<MqttClientConnectedEventArgs> Connected; public event EventHandler<MqttClientConnectedEventArgs> Connected;
public event EventHandler<MqttClientDisconnectedEventArgs> Disconnected; public event EventHandler<MqttClientDisconnectedEventArgs> Disconnected;
public event EventHandler<MqttApplicationMessageReceivedEventArgs> ApplicationMessageReceived; public event EventHandler<MqttApplicationMessageReceivedEventArgs> ApplicationMessageReceived;
public event EventHandler<ApplicationMessageProcessedEventArgs> ApplicationMessageProcessed;
public event EventHandler SynchronizingSubscriptionsFailed;
public async Task StartAsync(IManagedMqttClientOptions options) public async Task StartAsync(IManagedMqttClientOptions options)
{ {
@@ -57,7 +60,7 @@ namespace MQTTnet.ManagedClient
if (_connectionCancellationToken != null) throw new InvalidOperationException("The managed client is already started."); if (_connectionCancellationToken != null) throw new InvalidOperationException("The managed client is already started.");
_options = options; _options = options;
if (_options.Storage != null) if (_options.Storage != null)
{ {
_storageManager = new ManagedMqttClientStorageManager(_options.Storage); _storageManager = new ManagedMqttClientStorageManager(_options.Storage);
@@ -65,9 +68,9 @@ namespace MQTTnet.ManagedClient
} }
_connectionCancellationToken = new CancellationTokenSource(); _connectionCancellationToken = new CancellationTokenSource();
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed #pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
Task.Run(async () => await MaintainConnectionAsync(_connectionCancellationToken.Token), _connectionCancellationToken.Token).ConfigureAwait(false); Task.Run(async () => await MaintainConnectionAsync(_connectionCancellationToken.Token).ConfigureAwait(false), _connectionCancellationToken.Token).ConfigureAwait(false);
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed #pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
_logger.Info<ManagedMqttClient>("Started"); _logger.Info<ManagedMqttClient>("Started");
@@ -85,7 +88,7 @@ namespace MQTTnet.ManagedClient
return Task.FromResult(0); return Task.FromResult(0);
} }
public async Task PublishAsync(IEnumerable<MqttApplicationMessage> applicationMessages) public async Task PublishAsync(IEnumerable<MqttApplicationMessage> applicationMessages)
{ {
if (applicationMessages == null) throw new ArgumentNullException(nameof(applicationMessages)); if (applicationMessages == null) throw new ArgumentNullException(nameof(applicationMessages));
@@ -96,7 +99,7 @@ namespace MQTTnet.ManagedClient
{ {
await _storageManager.AddAsync(applicationMessage).ConfigureAwait(false); await _storageManager.AddAsync(applicationMessage).ConfigureAwait(false);
} }
_messageQueue.Add(applicationMessage); _messageQueue.Add(applicationMessage);
} }
} }
@@ -129,6 +132,7 @@ namespace MQTTnet.ManagedClient
{ {
if (_subscriptions.Remove(topic)) if (_subscriptions.Remove(topic))
{ {
_unsubscriptions.Add(topic);
_subscriptionsNotPushed = true; _subscriptionsNotPushed = true;
} }
} }
@@ -153,7 +157,7 @@ namespace MQTTnet.ManagedClient
{ {
while (!cancellationToken.IsCancellationRequested) while (!cancellationToken.IsCancellationRequested)
{ {
await TryMaintainConnectionAsync(cancellationToken); await TryMaintainConnectionAsync(cancellationToken).ConfigureAwait(false);
} }
} }
catch (OperationCanceledException) catch (OperationCanceledException)
@@ -184,11 +188,11 @@ namespace MQTTnet.ManagedClient
if (connectionState == ReconnectionResult.Reconnected || _subscriptionsNotPushed) if (connectionState == ReconnectionResult.Reconnected || _subscriptionsNotPushed)
{ {
await PushSubscriptionsAsync().ConfigureAwait(false); await SynchronizeSubscriptionsAsync().ConfigureAwait(false);
StartPublishing(); StartPublishing();
return; return;
} }
@@ -209,7 +213,7 @@ namespace MQTTnet.ManagedClient
_logger.Error<ManagedMqttClient>(exception, "Unhandled exception while maintaining connection."); _logger.Error<ManagedMqttClient>(exception, "Unhandled exception while maintaining connection.");
} }
} }
private async Task PublishQueuedMessagesAsync(CancellationToken cancellationToken) private async Task PublishQueuedMessagesAsync(CancellationToken cancellationToken)
{ {
try try
@@ -227,7 +231,7 @@ namespace MQTTnet.ManagedClient
continue; continue;
} }
await TryPublishQueuedMessageAsync(message).ConfigureAwait(false); await TryPublishQueuedMessageAsync(message).ConfigureAwait(false);
} }
} }
catch (OperationCanceledException) catch (OperationCanceledException)
@@ -245,6 +249,7 @@ namespace MQTTnet.ManagedClient
private async Task TryPublishQueuedMessageAsync(MqttApplicationMessage message) private async Task TryPublishQueuedMessageAsync(MqttApplicationMessage message)
{ {
Exception transmitException = null;
try try
{ {
await _mqttClient.PublishAsync(message).ConfigureAwait(false); await _mqttClient.PublishAsync(message).ConfigureAwait(false);
@@ -256,6 +261,8 @@ namespace MQTTnet.ManagedClient
} }
catch (MqttCommunicationException exception) catch (MqttCommunicationException exception)
{ {
transmitException = exception;
_logger.Warning<ManagedMqttClient>(exception, "Publishing application message failed."); _logger.Warning<ManagedMqttClient>(exception, "Publishing application message failed.");
if (message.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce) if (message.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce)
@@ -265,39 +272,60 @@ namespace MQTTnet.ManagedClient
} }
catch (Exception exception) catch (Exception exception)
{ {
transmitException = exception;
_logger.Error<ManagedMqttClient>(exception, "Unhandled exception while publishing queued application message."); _logger.Error<ManagedMqttClient>(exception, "Unhandled exception while publishing queued application message.");
} }
finally
{
ApplicationMessageProcessed?.Invoke(this, new ApplicationMessageProcessedEventArgs(message, transmitException));
}
} }
private async Task PushSubscriptionsAsync() private async Task SynchronizeSubscriptionsAsync()
{ {
_logger.Info<ManagedMqttClient>(nameof(ManagedMqttClient), "Synchronizing subscriptions"); _logger.Info<ManagedMqttClient>(nameof(ManagedMqttClient), "Synchronizing subscriptions");
List<TopicFilter> subscriptions; List<TopicFilter> subscriptions;
List<string> unsubscriptions;
await _subscriptionsSemaphore.WaitAsync().ConfigureAwait(false); await _subscriptionsSemaphore.WaitAsync().ConfigureAwait(false);
try try
{ {
subscriptions = _subscriptions.Select(i => new TopicFilter(i.Key, i.Value)).ToList(); subscriptions = _subscriptions.Select(i => new TopicFilter(i.Key, i.Value)).ToList();
unsubscriptions = new List<string>(_unsubscriptions);
_unsubscriptions.Clear();
_subscriptionsNotPushed = false; _subscriptionsNotPushed = false;
} }
finally finally
{ {
_subscriptionsSemaphore.Release(); _subscriptionsSemaphore.Release();
} }
if (!subscriptions.Any()) if (!subscriptions.Any() && !unsubscriptions.Any())
{ {
return; return;
} }
try try
{ {
await _mqttClient.SubscribeAsync(subscriptions).ConfigureAwait(false); if (subscriptions.Any())
{
await _mqttClient.SubscribeAsync(subscriptions).ConfigureAwait(false);
}
if (unsubscriptions.Any())
{
await _mqttClient.UnsubscribeAsync(unsubscriptions).ConfigureAwait(false);
}
} }
catch (Exception exception) catch (Exception exception)
{ {
_logger.Warning<ManagedMqttClient>(exception, "Synchronizing subscriptions failed"); _logger.Warning<ManagedMqttClient>(exception, "Synchronizing subscriptions failed.");
_subscriptionsNotPushed = true; _subscriptionsNotPushed = true;
SynchronizingSubscriptionsFailed?.Invoke(this, EventArgs.Empty);
} }
} }
@@ -75,7 +75,7 @@ namespace MQTTnet.Serializer
} }
} }
private static MqttBasePacket Deserialize(MqttPacketHeader header, MqttPacketReader reader) private MqttBasePacket Deserialize(MqttPacketHeader header, MqttPacketReader reader)
{ {
switch (header.ControlPacketType) switch (header.ControlPacketType)
{ {
@@ -278,20 +278,22 @@ namespace MQTTnet.Serializer
return packet; return packet;
} }
private static MqttBasePacket DeserializeConnAck(MqttPacketReader reader) private MqttBasePacket DeserializeConnAck(MqttPacketReader reader)
{ {
var variableHeader1 = reader.ReadByte(); var packet = new MqttConnAckPacket();
var variableHeader2 = reader.ReadByte();
var packet = new MqttConnAckPacket var firstByteReader = new ByteReader(reader.ReadByte());
if (ProtocolVersion == MqttProtocolVersion.V311)
{ {
IsSessionPresent = new ByteReader(variableHeader1).Read(), packet.IsSessionPresent = firstByteReader.Read();
ConnectReturnCode = (MqttConnectReturnCode)variableHeader2 }
};
packet.ConnectReturnCode = (MqttConnectReturnCode)reader.ReadByte();
return packet; return packet;
} }
private static void ValidateConnectPacket(MqttConnectPacket packet) private static void ValidateConnectPacket(MqttConnectPacket packet)
{ {
if (packet == null) throw new ArgumentNullException(nameof(packet)); if (packet == null) throw new ArgumentNullException(nameof(packet));
@@ -373,14 +375,21 @@ namespace MQTTnet.Serializer
private byte Serialize(MqttConnAckPacket packet, MqttPacketWriter writer) private byte Serialize(MqttConnAckPacket packet, MqttPacketWriter writer)
{ {
var connectAcknowledgeFlags = new ByteWriter(); if (ProtocolVersion == MqttProtocolVersion.V310)
if (ProtocolVersion == MqttProtocolVersion.V311)
{ {
writer.Write(0);
}
else if (ProtocolVersion == MqttProtocolVersion.V311)
{
var connectAcknowledgeFlags = new ByteWriter();
connectAcknowledgeFlags.Write(packet.IsSessionPresent); connectAcknowledgeFlags.Write(packet.IsSessionPresent);
writer.Write(connectAcknowledgeFlags);
}
else
{
throw new MqttProtocolViolationException("Protocol version not supported.");
} }
writer.Write(connectAcknowledgeFlags);
writer.Write((byte)packet.ConnectReturnCode); writer.Write((byte)packet.ConnectReturnCode);
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.ConnAck); return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.ConnAck);
@@ -13,6 +13,8 @@ namespace MQTTnet.Server
event EventHandler<MqttClientSubscribedTopicEventArgs> ClientSubscribedTopic; event EventHandler<MqttClientSubscribedTopicEventArgs> ClientSubscribedTopic;
event EventHandler<MqttClientUnsubscribedTopicEventArgs> ClientUnsubscribedTopic; event EventHandler<MqttClientUnsubscribedTopicEventArgs> ClientUnsubscribedTopic;
IMqttServerOptions Options { get; }
Task<IList<ConnectedMqttClient>> GetConnectedClientsAsync(); Task<IList<ConnectedMqttClient>> GetConnectedClientsAsync();
Task SubscribeAsync(string clientId, IList<TopicFilter> topicFilters); Task SubscribeAsync(string clientId, IList<TopicFilter> topicFilters);
@@ -0,0 +1,88 @@
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Diagnostics;
using MQTTnet.Packets;
namespace MQTTnet.Server
{
public sealed class MqttClientKeepAliveMonitor
{
private readonly Stopwatch _lastPacketReceivedTracker = new Stopwatch();
private readonly Stopwatch _lastNonKeepAlivePacketReceivedTracker = new Stopwatch();
private readonly string _clientId;
private readonly Func<Task> _timeoutCallback;
private readonly IMqttNetLogger _logger;
public MqttClientKeepAliveMonitor(string clientId, Func<Task> timeoutCallback, IMqttNetLogger logger)
{
_clientId = clientId;
_timeoutCallback = timeoutCallback;
_logger = logger;
}
public TimeSpan LastPacketReceived => _lastPacketReceivedTracker.Elapsed;
public TimeSpan LastNonKeepAlivePacketReceived => _lastNonKeepAlivePacketReceivedTracker.Elapsed;
public void Start(int keepAlivePeriod, CancellationToken cancellationToken)
{
if (keepAlivePeriod == 0)
{
return;
}
Task.Run(async () => await RunAsync(keepAlivePeriod, cancellationToken).ConfigureAwait(false), cancellationToken).ConfigureAwait(false);
}
private async Task RunAsync(int keepAlivePeriod, CancellationToken cancellationToken)
{
try
{
_lastPacketReceivedTracker.Restart();
_lastNonKeepAlivePacketReceivedTracker.Restart();
while (!cancellationToken.IsCancellationRequested)
{
// Values described here: [MQTT-3.1.2-24].
if (_lastPacketReceivedTracker.Elapsed.TotalSeconds > keepAlivePeriod * 1.5D)
{
_logger.Warning<MqttClientSession>("Client '{0}': Did not receive any packet or keep alive signal.", _clientId);
if (_timeoutCallback != null)
{
await _timeoutCallback().ConfigureAwait(false);
}
return;
}
await Task.Delay(keepAlivePeriod, cancellationToken).ConfigureAwait(false);
}
}
catch (OperationCanceledException)
{
}
catch (Exception exception)
{
_logger.Error<MqttClientSession>(exception, "Client '{0}': Unhandled exception while checking keep alive timeouts.", _clientId);
}
finally
{
_logger.Trace<MqttClientSession>("Client {0}: Stopped checking keep alive timeout.", _clientId);
}
}
public void PacketReceived(MqttBasePacket packet)
{
_lastPacketReceivedTracker.Restart();
if (!(packet is MqttPingReqPacket))
{
_lastNonKeepAlivePacketReceivedTracker.Restart();
}
}
}
}
@@ -1,6 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Adapter; using MQTTnet.Adapter;
@@ -15,12 +14,8 @@ namespace MQTTnet.Server
{ {
public sealed class MqttClientSession : IDisposable public sealed class MqttClientSession : IDisposable
{ {
private readonly Stopwatch _lastPacketReceivedTracker = Stopwatch.StartNew();
private readonly Stopwatch _lastNonKeepAlivePacketReceivedTracker = Stopwatch.StartNew();
private readonly IMqttServerOptions _options; private readonly IMqttServerOptions _options;
private readonly IMqttNetLogger _logger; private readonly IMqttNetLogger _logger;
private readonly MqttRetainedMessagesManager _retainedMessagesManager; private readonly MqttRetainedMessagesManager _retainedMessagesManager;
private IMqttChannelAdapter _adapter; private IMqttChannelAdapter _adapter;
@@ -38,7 +33,8 @@ namespace MQTTnet.Server
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); _logger = logger ?? throw new ArgumentNullException(nameof(logger));
ClientId = clientId; ClientId = clientId;
KeepAliveMonitor = new MqttClientKeepAliveMonitor(clientId, StopDueToKeepAliveTimeoutAsync, _logger);
SubscriptionsManager = new MqttClientSubscriptionsManager(_options, clientId); SubscriptionsManager = new MqttClientSubscriptionsManager(_options, clientId);
PendingMessagesQueue = new MqttClientPendingMessagesQueue(_options, this, _logger); PendingMessagesQueue = new MqttClientPendingMessagesQueue(_options, this, _logger);
} }
@@ -49,14 +45,12 @@ namespace MQTTnet.Server
public MqttClientPendingMessagesQueue PendingMessagesQueue { get; } public MqttClientPendingMessagesQueue PendingMessagesQueue { get; }
public MqttClientKeepAliveMonitor KeepAliveMonitor { get; }
public string ClientId { get; } public string ClientId { get; }
public MqttProtocolVersion? ProtocolVersion => _adapter?.PacketSerializer.ProtocolVersion; public MqttProtocolVersion? ProtocolVersion => _adapter?.PacketSerializer.ProtocolVersion;
public TimeSpan LastPacketReceived => _lastPacketReceivedTracker.Elapsed;
public TimeSpan LastNonKeepAlivePacketReceived => _lastNonKeepAlivePacketReceivedTracker.Elapsed;
public bool IsConnected => _adapter != null; public bool IsConnected => _adapter != null;
public async Task RunAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter adapter) public async Task RunAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter adapter)
@@ -73,14 +67,7 @@ namespace MQTTnet.Server
_cancellationTokenSource = cancellationTokenSource; _cancellationTokenSource = cancellationTokenSource;
PendingMessagesQueue.Start(adapter, cancellationTokenSource.Token); PendingMessagesQueue.Start(adapter, cancellationTokenSource.Token);
KeepAliveMonitor.Start(connectPacket.KeepAlivePeriod, cancellationTokenSource.Token);
_lastPacketReceivedTracker.Restart();
_lastNonKeepAlivePacketReceivedTracker.Restart();
if (connectPacket.KeepAlivePeriod > 0)
{
StartCheckingKeepAliveTimeout(TimeSpan.FromSeconds(connectPacket.KeepAlivePeriod), cancellationTokenSource.Token);
}
await ReceivePacketsAsync(adapter, cancellationTokenSource.Token).ConfigureAwait(false); await ReceivePacketsAsync(adapter, cancellationTokenSource.Token).ConfigureAwait(false);
} }
@@ -123,7 +110,7 @@ namespace MQTTnet.Server
var willMessage = _willMessage; var willMessage = _willMessage;
if (willMessage != null) if (willMessage != null)
{ {
_willMessage = null; //clear willmessage so it is send just once _willMessage = null; // clear willmessage so it is send just once
await ApplicationMessageReceivedCallback(this, willMessage).ConfigureAwait(false); await ApplicationMessageReceivedCallback(this, willMessage).ConfigureAwait(false);
} }
} }
@@ -145,28 +132,26 @@ namespace MQTTnet.Server
PendingMessagesQueue.Enqueue(publishPacket); PendingMessagesQueue.Enqueue(publishPacket);
} }
public Task SubscribeAsync(IList<TopicFilter> topicFilters) public async Task SubscribeAsync(IList<TopicFilter> topicFilters)
{ {
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
var response = SubscriptionsManager.SubscribeAsync(new MqttSubscribePacket await SubscriptionsManager.SubscribeAsync(new MqttSubscribePacket
{ {
TopicFilters = topicFilters TopicFilters = topicFilters
}); }).ConfigureAwait(false);
return response; await EnqueueSubscribedRetainedMessagesAsync(topicFilters).ConfigureAwait(false);
} }
public Task UnsubscribeAsync(IList<string> topicFilters) public Task UnsubscribeAsync(IList<string> topicFilters)
{ {
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
var response = SubscriptionsManager.UnsubscribeAsync(new MqttUnsubscribePacket return SubscriptionsManager.UnsubscribeAsync(new MqttUnsubscribePacket
{ {
TopicFilters = topicFilters TopicFilters = topicFilters
}); });
return response;
} }
public void Dispose() public void Dispose()
@@ -179,6 +164,12 @@ namespace MQTTnet.Server
_cancellationTokenSource?.Dispose(); _cancellationTokenSource?.Dispose();
} }
private Task StopDueToKeepAliveTimeoutAsync()
{
_logger.Info<MqttClientSession>("Client '{0}': Timeout while waiting for KeepAlive packet.", ClientId);
return StopAsync();
}
private async Task ReceivePacketsAsync(IMqttChannelAdapter adapter, CancellationToken cancellationToken) private async Task ReceivePacketsAsync(IMqttChannelAdapter adapter, CancellationToken cancellationToken)
{ {
try try
@@ -186,14 +177,7 @@ namespace MQTTnet.Server
while (!cancellationToken.IsCancellationRequested) while (!cancellationToken.IsCancellationRequested)
{ {
var packet = await adapter.ReceivePacketAsync(TimeSpan.Zero, cancellationToken).ConfigureAwait(false); var packet = await adapter.ReceivePacketAsync(TimeSpan.Zero, cancellationToken).ConfigureAwait(false);
KeepAliveMonitor.PacketReceived(packet);
_lastPacketReceivedTracker.Restart();
if (!(packet is MqttPingReqPacket))
{
_lastNonKeepAlivePacketReceivedTracker.Restart();
}
await ProcessReceivedPacketAsync(adapter, packet, cancellationToken).ConfigureAwait(false); await ProcessReceivedPacketAsync(adapter, packet, cancellationToken).ConfigureAwait(false);
} }
} }
@@ -270,7 +254,7 @@ namespace MQTTnet.Server
await StopAsync().ConfigureAwait(false); await StopAsync().ConfigureAwait(false);
} }
await EnqueueSubscribedRetainedMessagesAsync(subscribePacket).ConfigureAwait(false); await EnqueueSubscribedRetainedMessagesAsync(subscribePacket.TopicFilters).ConfigureAwait(false);
} }
private async Task HandleIncomingUnsubscribePacketAsync(IMqttChannelAdapter adapter, MqttUnsubscribePacket unsubscribePacket, CancellationToken cancellationToken) private async Task HandleIncomingUnsubscribePacketAsync(IMqttChannelAdapter adapter, MqttUnsubscribePacket unsubscribePacket, CancellationToken cancellationToken)
@@ -279,12 +263,12 @@ namespace MQTTnet.Server
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, unsubscribeResult); await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, unsubscribeResult);
} }
private async Task EnqueueSubscribedRetainedMessagesAsync(MqttSubscribePacket subscribePacket) private async Task EnqueueSubscribedRetainedMessagesAsync(ICollection<TopicFilter> topicFilters)
{ {
var retainedMessages = await _retainedMessagesManager.GetSubscribedMessagesAsync(subscribePacket); var retainedMessages = await _retainedMessagesManager.GetSubscribedMessagesAsync(topicFilters);
foreach (var publishPacket in retainedMessages) foreach (var applicationMessage in retainedMessages)
{ {
await EnqueueApplicationMessageAsync(publishPacket); await EnqueueApplicationMessageAsync(applicationMessage).ConfigureAwait(false);
} }
} }
@@ -335,42 +319,5 @@ namespace MQTTnet.Server
var response = new MqttPubCompPacket { PacketIdentifier = pubRelPacket.PacketIdentifier }; var response = new MqttPubCompPacket { PacketIdentifier = pubRelPacket.PacketIdentifier };
return adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, response); return adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, response);
} }
private void StartCheckingKeepAliveTimeout(TimeSpan keepAlivePeriod, CancellationToken cancellationToken)
{
Task.Run(
async () => await CheckKeepAliveTimeoutAsync(keepAlivePeriod, cancellationToken).ConfigureAwait(false)
, cancellationToken);
}
private async Task CheckKeepAliveTimeoutAsync(TimeSpan keepAlivePeriod, CancellationToken cancellationToken)
{
try
{
while (!cancellationToken.IsCancellationRequested)
{
// Values described here: [MQTT-3.1.2-24].
if (_lastPacketReceivedTracker.Elapsed.TotalSeconds > keepAlivePeriod.TotalSeconds * 1.5D)
{
_logger.Warning<MqttClientSession>("Client '{0}': Did not receive any packet or keep alive signal.", ClientId);
await StopAsync();
return;
}
await Task.Delay(keepAlivePeriod, cancellationToken);
}
}
catch (OperationCanceledException)
{
}
catch (Exception exception)
{
_logger.Error<MqttClientSession>(exception, "Client '{0}': Unhandled exception while checking keep alive timeouts.", ClientId);
}
finally
{
_logger.Trace<MqttClientSession>("Client {0}: Stopped checking keep alive timeout.", ClientId);
}
}
} }
} }
@@ -129,8 +129,8 @@ namespace MQTTnet.Server
{ {
ClientId = s.Value.ClientId, ClientId = s.Value.ClientId,
ProtocolVersion = s.Value.ProtocolVersion ?? MqttProtocolVersion.V311, ProtocolVersion = s.Value.ProtocolVersion ?? MqttProtocolVersion.V311,
LastPacketReceived = s.Value.LastPacketReceived, LastPacketReceived = s.Value.KeepAliveMonitor.LastPacketReceived,
LastNonKeepAlivePacketReceived = s.Value.LastNonKeepAlivePacketReceived, LastNonKeepAlivePacketReceived = s.Value.KeepAliveMonitor.LastNonKeepAlivePacketReceived,
PendingApplicationMessages = s.Value.PendingMessagesQueue.Count PendingApplicationMessages = s.Value.PendingMessagesQueue.Count
}).ToList(); }).ToList();
} }
@@ -220,16 +220,17 @@ namespace MQTTnet.Server
private MqttApplicationMessage InterceptApplicationMessage(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage) private MqttApplicationMessage InterceptApplicationMessage(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage)
{ {
if (_options.ApplicationMessageInterceptor == null) var interceptor = _options.ApplicationMessageInterceptor;
if (interceptor == null)
{ {
return applicationMessage; return applicationMessage;
} }
var interceptorContext = new MqttApplicationMessageInterceptorContext( var interceptorContext = new MqttApplicationMessageInterceptorContext(
senderClientSession.ClientId, senderClientSession?.ClientId,
applicationMessage); applicationMessage);
_options.ApplicationMessageInterceptor(interceptorContext); interceptor(interceptorContext);
return interceptorContext.ApplicationMessage; return interceptorContext.ApplicationMessage;
} }
@@ -4,7 +4,6 @@ using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Diagnostics; using MQTTnet.Diagnostics;
using MQTTnet.Packets;
namespace MQTTnet.Server namespace MQTTnet.Server
{ {
@@ -68,7 +67,7 @@ namespace MQTTnet.Server
} }
} }
public async Task<List<MqttApplicationMessage>> GetSubscribedMessagesAsync(MqttSubscribePacket subscribePacket) public async Task<List<MqttApplicationMessage>> GetSubscribedMessagesAsync(ICollection<TopicFilter> topicFilters)
{ {
var retainedMessages = new List<MqttApplicationMessage>(); var retainedMessages = new List<MqttApplicationMessage>();
@@ -77,13 +76,8 @@ namespace MQTTnet.Server
{ {
foreach (var retainedMessage in _retainedMessages.Values) foreach (var retainedMessage in _retainedMessages.Values)
{ {
foreach (var topicFilter in subscribePacket.TopicFilters) foreach (var topicFilter in topicFilters)
{ {
if (retainedMessage.QualityOfServiceLevel < topicFilter.QualityOfServiceLevel)
{
continue;
}
if (!MqttTopicFilterComparer.IsMatch(retainedMessage.Topic, topicFilter.Topic)) if (!MqttTopicFilterComparer.IsMatch(retainedMessage.Topic, topicFilter.Topic))
{ {
continue; continue;
@@ -16,7 +16,6 @@ namespace MQTTnet.Server
private MqttClientSessionsManager _clientSessionsManager; private MqttClientSessionsManager _clientSessionsManager;
private MqttRetainedMessagesManager _retainedMessagesManager; private MqttRetainedMessagesManager _retainedMessagesManager;
private CancellationTokenSource _cancellationTokenSource; private CancellationTokenSource _cancellationTokenSource;
private IMqttServerOptions _options;
public MqttServer(IEnumerable<IMqttServerAdapter> adapters, IMqttNetLogger logger) public MqttServer(IEnumerable<IMqttServerAdapter> adapters, IMqttNetLogger logger)
{ {
@@ -35,6 +34,8 @@ namespace MQTTnet.Server
public event EventHandler<MqttApplicationMessageReceivedEventArgs> ApplicationMessageReceived; public event EventHandler<MqttApplicationMessageReceivedEventArgs> ApplicationMessageReceived;
public IMqttServerOptions Options { get; private set; }
public Task<IList<ConnectedMqttClient>> GetConnectedClientsAsync() public Task<IList<ConnectedMqttClient>> GetConnectedClientsAsync()
{ {
return _clientSessionsManager.GetConnectedClientsAsync(); return _clientSessionsManager.GetConnectedClientsAsync();
@@ -70,16 +71,16 @@ namespace MQTTnet.Server
public async Task StartAsync(IMqttServerOptions options) public async Task StartAsync(IMqttServerOptions options)
{ {
_options = options ?? throw new ArgumentNullException(nameof(options)); Options = options ?? throw new ArgumentNullException(nameof(options));
if (_cancellationTokenSource != null) throw new InvalidOperationException("The server is already started."); if (_cancellationTokenSource != null) throw new InvalidOperationException("The server is already started.");
_cancellationTokenSource = new CancellationTokenSource(); _cancellationTokenSource = new CancellationTokenSource();
_retainedMessagesManager = new MqttRetainedMessagesManager(_options, _logger); _retainedMessagesManager = new MqttRetainedMessagesManager(Options, _logger);
await _retainedMessagesManager.LoadMessagesAsync(); await _retainedMessagesManager.LoadMessagesAsync();
_clientSessionsManager = new MqttClientSessionsManager(_options, _retainedMessagesManager, _logger) _clientSessionsManager = new MqttClientSessionsManager(Options, _retainedMessagesManager, _logger)
{ {
ClientConnectedCallback = OnClientConnected, ClientConnectedCallback = OnClientConnected,
ClientDisconnectedCallback = OnClientDisconnected, ClientDisconnectedCallback = OnClientDisconnected,
@@ -91,7 +92,7 @@ namespace MQTTnet.Server
foreach (var adapter in _adapters) foreach (var adapter in _adapters)
{ {
adapter.ClientAccepted += OnClientAccepted; adapter.ClientAccepted += OnClientAccepted;
await adapter.StartAsync(_options); await adapter.StartAsync(Options);
} }
_logger.Info<MqttServer>("Started."); _logger.Info<MqttServer>("Started.");
@@ -142,7 +143,7 @@ namespace MQTTnet.Server
_logger.Info<MqttServer>("Client '{0}': Disconnected.", client.ClientId); _logger.Info<MqttServer>("Client '{0}': Disconnected.", client.ClientId);
ClientDisconnected?.Invoke(this, new MqttClientDisconnectedEventArgs(client)); ClientDisconnected?.Invoke(this, new MqttClientDisconnectedEventArgs(client));
} }
private void OnClientSubscribedTopic(string clientId, TopicFilter topicFilter) private void OnClientSubscribedTopic(string clientId, TopicFilter topicFilter)
{ {
ClientSubscribedTopic?.Invoke(this, new MqttClientSubscribedTopicEventArgs(clientId, topicFilter)); ClientSubscribedTopic?.Invoke(this, new MqttClientSubscribedTopicEventArgs(clientId, topicFilter));
@@ -152,7 +153,7 @@ namespace MQTTnet.Server
{ {
ClientUnsubscribedTopic?.Invoke(this, new MqttClientUnsubscribedTopicEventArgs(clientId, topicFilter)); ClientUnsubscribedTopic?.Invoke(this, new MqttClientUnsubscribedTopicEventArgs(clientId, topicFilter));
} }
private void OnApplicationMessageReceived(string clientId, MqttApplicationMessage applicationMessage) private void OnApplicationMessageReceived(string clientId, MqttApplicationMessage applicationMessage)
{ {
ApplicationMessageReceived?.Invoke(this, new MqttApplicationMessageReceivedEventArgs(clientId, applicationMessage)); ApplicationMessageReceived?.Invoke(this, new MqttApplicationMessageReceivedEventArgs(clientId, applicationMessage));
@@ -15,8 +15,7 @@
<Description /> <Description />
<Authors /> <Authors />
<PackageId /> <PackageId />
<SignAssembly>true</SignAssembly> <SignAssembly>false</SignAssembly>
<AssemblyOriginatorKeyFile>codeSigningKey.pfx</AssemblyOriginatorKeyFile>
<DelaySign>false</DelaySign> <DelaySign>false</DelaySign>
</PropertyGroup> </PropertyGroup>
@@ -29,6 +28,7 @@
<TargetFrameworkIdentifier>.NETCore</TargetFrameworkIdentifier> <TargetFrameworkIdentifier>.NETCore</TargetFrameworkIdentifier>
<TargetFrameworkVersion>v5.0</TargetFrameworkVersion> <TargetFrameworkVersion>v5.0</TargetFrameworkVersion>
<DefineConstants>$(DefineConstants);WINDOWS_UWP</DefineConstants> <DefineConstants>$(DefineConstants);WINDOWS_UWP</DefineConstants>
<DefaultLanguage>en</DefaultLanguage>
<LanguageTargets>$(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets</LanguageTargets> <LanguageTargets>$(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets</LanguageTargets>
</PropertyGroup> </PropertyGroup>
+1
View File
@@ -43,6 +43,7 @@ MQTTnet is a high performance .NET library for MQTT based communication. It prov
* Retained messages are supported including persisting via interface methods (own implementation required) * Retained messages are supported including persisting via interface methods (own implementation required)
* WebSockets supported (via ASP.NET Core 2.0, separate nuget) * WebSockets supported (via ASP.NET Core 2.0, separate nuget)
* A custom message interceptor can be added which allows transforming or extending every received application message * A custom message interceptor can be added which allows transforming or extending every received application message
* Validate subscriptions and deny subscribing of certain topics depending on requesting clients
## Supported frameworks ## Supported frameworks
@@ -0,0 +1,66 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Diagnostics;
using MQTTnet.Packets;
using MQTTnet.Server;
namespace MQTTnet.Core.Tests
{
[TestClass]
public class MqttKeepAliveMonitorTests
{
[TestMethod]
public void KeepAlive_Timeout()
{
var timeoutCalledCount = 0;
var monitor = new MqttClientKeepAliveMonitor(string.Empty, delegate
{
timeoutCalledCount++;
return Task.FromResult(0);
}, new MqttNetLogger());
Assert.AreEqual(0, timeoutCalledCount);
monitor.Start(1, CancellationToken.None);
Assert.AreEqual(0, timeoutCalledCount);
Thread.Sleep(2000); // Internally the keep alive timeout is multiplied with 1.5 as per protocol specification.
Assert.AreEqual(1, timeoutCalledCount);
}
[TestMethod]
public void KeepAlive_NoTimeout()
{
var timeoutCalledCount = 0;
var monitor = new MqttClientKeepAliveMonitor(string.Empty, delegate
{
timeoutCalledCount++;
return Task.FromResult(0);
}, new MqttNetLogger());
Assert.AreEqual(0, timeoutCalledCount);
monitor.Start(1, CancellationToken.None);
Assert.AreEqual(0, timeoutCalledCount);
// Simulate traffic.
Thread.Sleep(1000); // Internally the keep alive timeout is multiplied with 1.5 as per protocol specification.
monitor.PacketReceived(new MqttPublishPacket());
Thread.Sleep(1000);
monitor.PacketReceived(new MqttPublishPacket());
Thread.Sleep(1000);
Assert.AreEqual(0, timeoutCalledCount);
Thread.Sleep(2000);
Assert.AreEqual(1, timeoutCalledCount);
}
}
}
@@ -119,10 +119,10 @@ namespace MQTTnet.Core.Tests
{ {
var p = new MqttConnAckPacket var p = new MqttConnAckPacket
{ {
ConnectReturnCode = MqttConnectReturnCode.ConnectionAccepted ConnectReturnCode = MqttConnectReturnCode.ConnectionRefusedNotAuthorized
}; };
SerializeAndCompare(p, "IAIAAA==", MqttProtocolVersion.V310); SerializeAndCompare(p, "IAIABQ==", MqttProtocolVersion.V310);
} }
[TestMethod] [TestMethod]
@@ -137,6 +137,17 @@ namespace MQTTnet.Core.Tests
DeserializeAndCompare(p, "IAIBBQ=="); DeserializeAndCompare(p, "IAIBBQ==");
} }
[TestMethod]
public void DeserializeV310_MqttConnAckPacket()
{
var p = new MqttConnAckPacket
{
ConnectReturnCode = MqttConnectReturnCode.ConnectionRefusedNotAuthorized
};
DeserializeAndCompare(p, "IAIABQ==", MqttProtocolVersion.V310);
}
[TestMethod] [TestMethod]
public void SerializeV311_MqttDisconnectPacket() public void SerializeV311_MqttDisconnectPacket()
{ {
@@ -397,9 +408,9 @@ namespace MQTTnet.Core.Tests
Assert.AreEqual(expectedBase64Value, Convert.ToBase64String(Join(chunks))); Assert.AreEqual(expectedBase64Value, Convert.ToBase64String(Join(chunks)));
} }
private static void DeserializeAndCompare(MqttBasePacket packet, string expectedBase64Value) private static void DeserializeAndCompare(MqttBasePacket packet, string expectedBase64Value, MqttProtocolVersion protocolVersion = MqttProtocolVersion.V311)
{ {
var serializer = new MqttPacketSerializer(); var serializer = new MqttPacketSerializer { ProtocolVersion = protocolVersion };
var buffer1 = serializer.Serialize(packet); var buffer1 = serializer.Serialize(packet);
@@ -138,10 +138,10 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform"> <PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform">
<Version>5.4.1</Version> <Version>6.0.8</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.Toolkit.Uwp.UI.Controls"> <PackageReference Include="Microsoft.Toolkit.Uwp.UI.Controls">
<Version>2.1.1</Version> <Version>2.2.0</Version>
</PackageReference> </PackageReference>
</ItemGroup> </ItemGroup>
<PropertyGroup Condition=" '$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' &lt; '14.0' "> <PropertyGroup Condition=" '$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' &lt; '14.0' ">