mirror of
https://github.com/dotnet/MQTTnet.git
synced 2026-10-16 17:23:04 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
54a0265ef9 | ||
|
|
8f96acaec4 | ||
|
|
cea984cc2f | ||
|
|
fcf748e980 | ||
|
|
e58cb3c9cd | ||
|
|
3587a8ebd1 | ||
|
|
3193381b69 | ||
|
|
7a9ddfc2b5 | ||
|
|
447b2aa2c6 | ||
|
|
b3eaeea8a4 | ||
|
|
45b4c05867 | ||
|
|
49da66dcff | ||
|
|
bb9d5586f0 | ||
|
|
c5d22f4b27 | ||
|
|
e57ec11ed8 | ||
|
|
56acc82445 | ||
|
|
6e828d091f | ||
|
|
e1d8697331 | ||
|
|
90098c2006 | ||
|
|
b3a25fb9e8 | ||
|
|
f85ad4965b | ||
|
|
f5696b6fc0 | ||
|
|
c26555aca5 | ||
|
|
0accb467b5 | ||
|
|
3b34fd4750 | ||
|
|
abb216c055 | ||
|
|
1760dc7d35 | ||
|
|
034e7fd750 | ||
|
|
76c7b319b8 | ||
|
|
09010e95a2 | ||
|
|
2341d66192 |
@@ -2,7 +2,7 @@
|
|||||||
<package >
|
<package >
|
||||||
<metadata>
|
<metadata>
|
||||||
<id>MQTTnet.AspNetCore</id>
|
<id>MQTTnet.AspNetCore</id>
|
||||||
<version>2.7.0</version>
|
<version>2.7.3</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,13 +10,13 @@
|
|||||||
<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>This is a support library to integrate MQTTnet into AspNetCore.</description>
|
<description>This is a support library to integrate MQTTnet into AspNetCore.</description>
|
||||||
<releaseNotes>* Updated to MQTTnet 2.7.0.
|
<releaseNotes>* Updated to MQTTnet 2.7.3.
|
||||||
</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">
|
<group targetFramework="netstandard2.0">
|
||||||
<dependency id="MQTTnet" version="2.7.0" />
|
<dependency id="MQTTnet" version="2.7.3" />
|
||||||
</group>
|
</group>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</metadata>
|
</metadata>
|
||||||
|
|||||||
+9
-11
@@ -2,7 +2,7 @@
|
|||||||
<package >
|
<package >
|
||||||
<metadata>
|
<metadata>
|
||||||
<id>MQTTnet</id>
|
<id>MQTTnet</id>
|
||||||
<version>2.7.0</version>
|
<version>2.7.3</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,14 @@
|
|||||||
<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] Add several new extension methods.
|
||||||
* [Core] Updated 3rd-Party packages.
|
* [Client] Fixed an issue in _ManagedMqttClientOptionsBuilder_ when using _WithClientOptions_ and an options builder.
|
||||||
* [Core] Fixed wrong packet identifier calculation (thanks to @benpittoors).
|
* [Client] Added the "IsStarted" property for the managed client.
|
||||||
* [Core] Fixed an issue when reading the body of a package from a disconnected sender (thanks to @kpreisser).
|
* [Client] Optimized stream buffer for UWP apps.
|
||||||
* [Core] Fixed wrong parsing of the body length (thanks to @kpreisser).
|
* [Client] Added the _BufferSize_ to the TCP options.
|
||||||
* [Client] The client interfaces are now implementing _IDisposable_.
|
* [Client] Fixed a race condition which leads to exceptions when reconnecting rapidly.
|
||||||
* [Client] The disconnected event now contains the exception which was thrown and causing the disconnect.
|
* [Server] Fixed a race condition which leads to exceptions when clients are reconnecting rapidly.
|
||||||
* [Server] Fixed an issue which lets the server block 1 second after accepting a new connection (thanks to @kpreisser).
|
* [Core] Fixed some issues in stream and socket handling.
|
||||||
* [Server] The server now allows managing client subscriptions.
|
|
||||||
* [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
@@ -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,3 +4,6 @@ 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>
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ using MQTTnet.Serializer;
|
|||||||
|
|
||||||
namespace MQTTnet.Adapter
|
namespace MQTTnet.Adapter
|
||||||
{
|
{
|
||||||
public interface IMqttChannelAdapter
|
public interface IMqttChannelAdapter : IDisposable
|
||||||
{
|
{
|
||||||
IMqttPacketSerializer PacketSerializer { get; }
|
IMqttPacketSerializer PacketSerializer { get; }
|
||||||
|
|
||||||
|
|||||||
@@ -14,11 +14,12 @@ using MQTTnet.Serializer;
|
|||||||
|
|
||||||
namespace MQTTnet.Adapter
|
namespace MQTTnet.Adapter
|
||||||
{
|
{
|
||||||
public class MqttChannelAdapter : IMqttChannelAdapter
|
public sealed class MqttChannelAdapter : IMqttChannelAdapter
|
||||||
{
|
{
|
||||||
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 bool _isDisposed;
|
||||||
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
|
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
|
||||||
private readonly IMqttNetLogger _logger;
|
private readonly IMqttNetLogger _logger;
|
||||||
private readonly IMqttChannel _channel;
|
private readonly IMqttChannel _channel;
|
||||||
@@ -34,6 +35,7 @@ namespace MQTTnet.Adapter
|
|||||||
|
|
||||||
public Task ConnectAsync(TimeSpan timeout)
|
public Task ConnectAsync(TimeSpan timeout)
|
||||||
{
|
{
|
||||||
|
ThrowIfDisposed();
|
||||||
_logger.Trace<MqttChannelAdapter>("Connecting [Timeout={0}]", timeout);
|
_logger.Trace<MqttChannelAdapter>("Connecting [Timeout={0}]", timeout);
|
||||||
|
|
||||||
return ExecuteAndWrapExceptionAsync(() => _channel.ConnectAsync().TimeoutAfter(timeout));
|
return ExecuteAndWrapExceptionAsync(() => _channel.ConnectAsync().TimeoutAfter(timeout));
|
||||||
@@ -41,6 +43,7 @@ namespace MQTTnet.Adapter
|
|||||||
|
|
||||||
public Task DisconnectAsync(TimeSpan timeout)
|
public Task DisconnectAsync(TimeSpan timeout)
|
||||||
{
|
{
|
||||||
|
ThrowIfDisposed();
|
||||||
_logger.Trace<MqttChannelAdapter>("Disconnecting [Timeout={0}]", timeout);
|
_logger.Trace<MqttChannelAdapter>("Disconnecting [Timeout={0}]", timeout);
|
||||||
|
|
||||||
return ExecuteAndWrapExceptionAsync(() => _channel.DisconnectAsync().TimeoutAfter(timeout));
|
return ExecuteAndWrapExceptionAsync(() => _channel.DisconnectAsync().TimeoutAfter(timeout));
|
||||||
@@ -48,6 +51,8 @@ namespace MQTTnet.Adapter
|
|||||||
|
|
||||||
public Task SendPacketsAsync(TimeSpan timeout, CancellationToken cancellationToken, IEnumerable<MqttBasePacket> packets)
|
public Task SendPacketsAsync(TimeSpan timeout, CancellationToken cancellationToken, IEnumerable<MqttBasePacket> packets)
|
||||||
{
|
{
|
||||||
|
ThrowIfDisposed();
|
||||||
|
|
||||||
return ExecuteAndWrapExceptionAsync(async () =>
|
return ExecuteAndWrapExceptionAsync(async () =>
|
||||||
{
|
{
|
||||||
await _semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
await _semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||||
@@ -55,6 +60,11 @@ namespace MQTTnet.Adapter
|
|||||||
{
|
{
|
||||||
foreach (var packet in packets)
|
foreach (var packet in packets)
|
||||||
{
|
{
|
||||||
|
if (cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (packet == null)
|
if (packet == null)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
@@ -65,10 +75,20 @@ namespace MQTTnet.Adapter
|
|||||||
var chunks = PacketSerializer.Serialize(packet);
|
var chunks = PacketSerializer.Serialize(packet);
|
||||||
foreach (var chunk in chunks)
|
foreach (var chunk in chunks)
|
||||||
{
|
{
|
||||||
|
if (cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await _channel.SendStream.WriteAsync(chunk.Array, chunk.Offset, chunk.Count, cancellationToken).ConfigureAwait(false);
|
await _channel.SendStream.WriteAsync(chunk.Array, chunk.Offset, chunk.Count, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (timeout > TimeSpan.Zero)
|
if (timeout > TimeSpan.Zero)
|
||||||
{
|
{
|
||||||
await _channel.SendStream.FlushAsync(cancellationToken).TimeoutAfter(timeout).ConfigureAwait(false);
|
await _channel.SendStream.FlushAsync(cancellationToken).TimeoutAfter(timeout).ConfigureAwait(false);
|
||||||
@@ -87,6 +107,8 @@ namespace MQTTnet.Adapter
|
|||||||
|
|
||||||
public async Task<MqttBasePacket> ReceivePacketAsync(TimeSpan timeout, CancellationToken cancellationToken)
|
public async Task<MqttBasePacket> ReceivePacketAsync(TimeSpan timeout, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
ThrowIfDisposed();
|
||||||
|
|
||||||
MqttBasePacket packet = null;
|
MqttBasePacket packet = null;
|
||||||
await ExecuteAndWrapExceptionAsync(async () =>
|
await ExecuteAndWrapExceptionAsync(async () =>
|
||||||
{
|
{
|
||||||
@@ -190,7 +212,7 @@ namespace MQTTnet.Adapter
|
|||||||
}
|
}
|
||||||
catch (COMException comException)
|
catch (COMException comException)
|
||||||
{
|
{
|
||||||
if ((uint) comException.HResult == ErrorOperationAborted)
|
if ((uint)comException.HResult == ErrorOperationAborted)
|
||||||
{
|
{
|
||||||
throw new OperationCanceledException();
|
throw new OperationCanceledException();
|
||||||
}
|
}
|
||||||
@@ -214,5 +236,20 @@ namespace MQTTnet.Adapter
|
|||||||
throw new MqttCommunicationException(exception);
|
throw new MqttCommunicationException(exception);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_isDisposed = true;
|
||||||
|
_semaphore?.Dispose();
|
||||||
|
_channel?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ThrowIfDisposed()
|
||||||
|
{
|
||||||
|
if (_isDisposed)
|
||||||
|
{
|
||||||
|
throw new ObjectDisposedException(nameof(MqttChannelAdapter));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using MQTTnet.Packets;
|
|
||||||
|
|
||||||
namespace MQTTnet.Adapter
|
|
||||||
{
|
|
||||||
public static class MqttChannelAdapterExtensions
|
|
||||||
{
|
|
||||||
public static Task SendPacketsAsync(this IMqttChannelAdapter adapter, TimeSpan timeout, CancellationToken cancellationToken, params MqttBasePacket[] packets)
|
|
||||||
{
|
|
||||||
if (adapter == null) throw new ArgumentNullException(nameof(adapter));
|
|
||||||
|
|
||||||
return adapter.SendPacketsAsync(timeout, cancellationToken, packets);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +1,41 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using MQTTnet.Protocol;
|
||||||
|
|
||||||
namespace MQTTnet
|
namespace MQTTnet
|
||||||
{
|
{
|
||||||
public static class ApplicationMessagePublisherExtensions
|
public static class ApplicationMessagePublisherExtensions
|
||||||
{
|
{
|
||||||
public static Task PublishAsync(this IApplicationMessagePublisher client, params MqttApplicationMessage[] applicationMessages)
|
public static Task PublishAsync(this IApplicationMessagePublisher publisher, params MqttApplicationMessage[] applicationMessages)
|
||||||
{
|
{
|
||||||
if (client == null) throw new ArgumentNullException(nameof(client));
|
if (publisher == null) throw new ArgumentNullException(nameof(publisher));
|
||||||
if (applicationMessages == null) throw new ArgumentNullException(nameof(applicationMessages));
|
if (applicationMessages == null) throw new ArgumentNullException(nameof(applicationMessages));
|
||||||
|
|
||||||
return client.PublishAsync(applicationMessages);
|
return publisher.PublishAsync(applicationMessages);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Task PublishAsync(this IApplicationMessagePublisher publisher, string topic)
|
||||||
|
{
|
||||||
|
if (publisher == null) throw new ArgumentNullException(nameof(publisher));
|
||||||
|
if (topic == null) throw new ArgumentNullException(nameof(topic));
|
||||||
|
|
||||||
|
return publisher.PublishAsync(new MqttApplicationMessageBuilder().WithTopic(topic).Build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Task PublishAsync(this IApplicationMessagePublisher publisher, string topic, string payload)
|
||||||
|
{
|
||||||
|
if (publisher == null) throw new ArgumentNullException(nameof(publisher));
|
||||||
|
if (topic == null) throw new ArgumentNullException(nameof(topic));
|
||||||
|
|
||||||
|
return publisher.PublishAsync(new MqttApplicationMessageBuilder().WithTopic(topic).WithPayload(payload).Build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Task PublishAsync(this IApplicationMessagePublisher publisher, string topic, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel)
|
||||||
|
{
|
||||||
|
if (publisher == null) throw new ArgumentNullException(nameof(publisher));
|
||||||
|
if (topic == null) throw new ArgumentNullException(nameof(topic));
|
||||||
|
|
||||||
|
return publisher.PublishAsync(new MqttApplicationMessageBuilder().WithTopic(topic).WithPayload(payload).WithQualityOfServiceLevel(qualityOfServiceLevel).Build());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
using System.IO;
|
using System;
|
||||||
|
using System.IO;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace MQTTnet.Channel
|
namespace MQTTnet.Channel
|
||||||
{
|
{
|
||||||
public interface IMqttChannel
|
public interface IMqttChannel : IDisposable
|
||||||
{
|
{
|
||||||
Stream SendStream { get; }
|
Stream SendStream { get; }
|
||||||
Stream ReceiveStream { get; }
|
Stream ReceiveStream { get; }
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ namespace MQTTnet.Client
|
|||||||
|
|
||||||
TimeSpan CommunicationTimeout { get; }
|
TimeSpan CommunicationTimeout { get; }
|
||||||
TimeSpan KeepAlivePeriod { get; }
|
TimeSpan KeepAlivePeriod { get; }
|
||||||
|
TimeSpan? KeepAliveSendInterval { get; set; }
|
||||||
|
|
||||||
MqttProtocolVersion ProtocolVersion { get; }
|
MqttProtocolVersion ProtocolVersion { get; }
|
||||||
|
|
||||||
IMqttClientChannelOptions ChannelOptions { get; }
|
IMqttClientChannelOptions ChannelOptions { get; }
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -15,6 +16,8 @@ namespace MQTTnet.Client
|
|||||||
public class MqttClient : IMqttClient
|
public class MqttClient : IMqttClient
|
||||||
{
|
{
|
||||||
private readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider();
|
private readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider();
|
||||||
|
private readonly Stopwatch _sendTracker = new Stopwatch();
|
||||||
|
private readonly SemaphoreSlim _disconnectLock = new SemaphoreSlim(1, 1);
|
||||||
private readonly IMqttClientAdapterFactory _adapterFactory;
|
private readonly IMqttClientAdapterFactory _adapterFactory;
|
||||||
private readonly MqttPacketDispatcher _packetDispatcher;
|
private readonly MqttPacketDispatcher _packetDispatcher;
|
||||||
private readonly IMqttNetLogger _logger;
|
private readonly IMqttNetLogger _logger;
|
||||||
@@ -22,6 +25,8 @@ namespace MQTTnet.Client
|
|||||||
private IMqttClientOptions _options;
|
private IMqttClientOptions _options;
|
||||||
private bool _isReceivingPackets;
|
private bool _isReceivingPackets;
|
||||||
private CancellationTokenSource _cancellationTokenSource;
|
private CancellationTokenSource _cancellationTokenSource;
|
||||||
|
private Task _packetReceiverTask;
|
||||||
|
private Task _keepAliveMessageSenderTask;
|
||||||
private IMqttChannelAdapter _adapter;
|
private IMqttChannelAdapter _adapter;
|
||||||
|
|
||||||
public MqttClient(IMqttClientAdapterFactory channelFactory, IMqttNetLogger logger)
|
public MqttClient(IMqttClientAdapterFactory channelFactory, IMqttNetLogger logger)
|
||||||
@@ -58,14 +63,16 @@ namespace MQTTnet.Client
|
|||||||
await _adapter.ConnectAsync(_options.CommunicationTimeout).ConfigureAwait(false);
|
await _adapter.ConnectAsync(_options.CommunicationTimeout).ConfigureAwait(false);
|
||||||
_logger.Trace<MqttClient>("Connection with server established.");
|
_logger.Trace<MqttClient>("Connection with server established.");
|
||||||
|
|
||||||
await StartReceivingPacketsAsync(_cancellationTokenSource.Token).ConfigureAwait(false);
|
await StartReceivingPacketsAsync().ConfigureAwait(false);
|
||||||
var connectResponse = await AuthenticateAsync(options.WillMessage).ConfigureAwait(false);
|
|
||||||
|
|
||||||
|
var connectResponse = await AuthenticateAsync(options.WillMessage).ConfigureAwait(false);
|
||||||
_logger.Trace<MqttClient>("MQTT connection with server established.");
|
_logger.Trace<MqttClient>("MQTT connection with server established.");
|
||||||
|
|
||||||
|
_sendTracker.Restart();
|
||||||
|
|
||||||
if (_options.KeepAlivePeriod != TimeSpan.Zero)
|
if (_options.KeepAlivePeriod != TimeSpan.Zero)
|
||||||
{
|
{
|
||||||
StartSendingKeepAliveMessages(_cancellationTokenSource.Token);
|
StartSendingKeepAliveMessages();
|
||||||
}
|
}
|
||||||
|
|
||||||
IsConnected = true;
|
IsConnected = true;
|
||||||
@@ -90,7 +97,10 @@ namespace MQTTnet.Client
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await SendAsync(new MqttDisconnectPacket()).ConfigureAwait(false);
|
if (!_cancellationTokenSource.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
await SendAsync(new MqttDisconnectPacket()).ConfigureAwait(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -149,7 +159,7 @@ namespace MQTTnet.Client
|
|||||||
case MqttQualityOfServiceLevel.AtMostOnce:
|
case MqttQualityOfServiceLevel.AtMostOnce:
|
||||||
{
|
{
|
||||||
// No packet identifier is used for QoS 0 [3.3.2.2 Packet Identifier]
|
// No packet identifier is used for QoS 0 [3.3.2.2 Packet Identifier]
|
||||||
await _adapter.SendPacketsAsync(_options.CommunicationTimeout, _cancellationTokenSource.Token, qosGroup).ConfigureAwait(false);
|
await SendAsync((MqttPublishPacket[])qosGroup.ToArray()).ConfigureAwait(false);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case MqttQualityOfServiceLevel.AtLeastOnce:
|
case MqttQualityOfServiceLevel.AtLeastOnce:
|
||||||
@@ -167,8 +177,14 @@ namespace MQTTnet.Client
|
|||||||
foreach (var publishPacket in qosGroup)
|
foreach (var publishPacket in qosGroup)
|
||||||
{
|
{
|
||||||
publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNewPacketIdentifier();
|
publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNewPacketIdentifier();
|
||||||
|
|
||||||
var pubRecPacket = await SendAndReceiveAsync<MqttPubRecPacket>(publishPacket).ConfigureAwait(false);
|
var pubRecPacket = await SendAndReceiveAsync<MqttPubRecPacket>(publishPacket).ConfigureAwait(false);
|
||||||
await SendAndReceiveAsync<MqttPubCompPacket>(pubRecPacket.CreateResponse<MqttPubRelPacket>()).ConfigureAwait(false);
|
var pubRelPacket = new MqttPubRelPacket
|
||||||
|
{
|
||||||
|
PacketIdentifier = pubRecPacket.PacketIdentifier
|
||||||
|
};
|
||||||
|
|
||||||
|
await SendAndReceiveAsync<MqttPubCompPacket>(pubRelPacket).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@@ -184,6 +200,9 @@ namespace MQTTnet.Client
|
|||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_cancellationTokenSource?.Dispose();
|
_cancellationTokenSource?.Dispose();
|
||||||
|
_cancellationTokenSource = null;
|
||||||
|
|
||||||
|
_adapter?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<MqttConnAckPacket> AuthenticateAsync(MqttApplicationMessage willApplicationMessage)
|
private async Task<MqttConnAckPacket> AuthenticateAsync(MqttApplicationMessage willApplicationMessage)
|
||||||
@@ -219,23 +238,31 @@ namespace MQTTnet.Client
|
|||||||
|
|
||||||
private async Task DisconnectInternalAsync(Exception exception)
|
private async Task DisconnectInternalAsync(Exception exception)
|
||||||
{
|
{
|
||||||
|
await _disconnectLock.WaitAsync();
|
||||||
var clientWasConnected = IsConnected;
|
var clientWasConnected = IsConnected;
|
||||||
IsConnected = false;
|
|
||||||
|
|
||||||
var cts = _cancellationTokenSource;
|
|
||||||
if (cts == null || cts.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
cts.Cancel(false);
|
|
||||||
cts.Dispose();
|
|
||||||
_cancellationTokenSource = null;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
IsConnected = false;
|
||||||
|
|
||||||
|
if (_cancellationTokenSource == null || _cancellationTokenSource.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_cancellationTokenSource.Cancel(false);
|
||||||
|
|
||||||
|
if (_packetReceiverTask != null)
|
||||||
|
{
|
||||||
|
Task.WaitAll(_packetReceiverTask);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_keepAliveMessageSenderTask != null)
|
||||||
|
{
|
||||||
|
Task.WaitAll(_keepAliveMessageSenderTask);
|
||||||
|
}
|
||||||
|
|
||||||
await _adapter.DisconnectAsync(_options.CommunicationTimeout).ConfigureAwait(false);
|
await _adapter.DisconnectAsync(_options.CommunicationTimeout).ConfigureAwait(false);
|
||||||
_logger.Info<MqttClient>("Disconnected from adapter.");
|
_logger.Trace<MqttClient>("Disconnected from adapter.");
|
||||||
}
|
}
|
||||||
catch (Exception adapterException)
|
catch (Exception adapterException)
|
||||||
{
|
{
|
||||||
@@ -243,6 +270,14 @@ namespace MQTTnet.Client
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
_adapter?.Dispose();
|
||||||
|
_adapter = null;
|
||||||
|
|
||||||
|
_cancellationTokenSource?.Dispose();
|
||||||
|
_cancellationTokenSource = null;
|
||||||
|
|
||||||
|
_disconnectLock.Release();
|
||||||
|
|
||||||
_logger.Info<MqttClient>("Disconnected.");
|
_logger.Info<MqttClient>("Disconnected.");
|
||||||
Disconnected?.Invoke(this, new MqttClientDisconnectedEventArgs(clientWasConnected, exception));
|
Disconnected?.Invoke(this, new MqttClientDisconnectedEventArgs(clientWasConnected, exception));
|
||||||
}
|
}
|
||||||
@@ -301,6 +336,11 @@ namespace MQTTnet.Client
|
|||||||
|
|
||||||
private Task ProcessReceivedPublishPacketAsync(MqttPublishPacket publishPacket)
|
private Task ProcessReceivedPublishPacketAsync(MqttPublishPacket publishPacket)
|
||||||
{
|
{
|
||||||
|
if (_cancellationTokenSource.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
return Task.FromResult(0);
|
||||||
|
}
|
||||||
|
|
||||||
if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce)
|
if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce)
|
||||||
{
|
{
|
||||||
FireApplicationMessageReceivedEvent(publishPacket);
|
FireApplicationMessageReceivedEvent(publishPacket);
|
||||||
@@ -325,55 +365,76 @@ namespace MQTTnet.Client
|
|||||||
|
|
||||||
private Task ProcessReceivedPubRelPacket(MqttPubRelPacket pubRelPacket)
|
private Task ProcessReceivedPubRelPacket(MqttPubRelPacket pubRelPacket)
|
||||||
{
|
{
|
||||||
return SendAsync(pubRelPacket.CreateResponse<MqttPubCompPacket>());
|
var response = new MqttPubCompPacket
|
||||||
|
{
|
||||||
|
PacketIdentifier = pubRelPacket.PacketIdentifier
|
||||||
|
};
|
||||||
|
|
||||||
|
return SendAsync(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task SendAsync(MqttBasePacket packet)
|
private Task SendAsync(params MqttBasePacket[] packets)
|
||||||
{
|
{
|
||||||
return _adapter.SendPacketsAsync(_options.CommunicationTimeout, _cancellationTokenSource.Token, packet);
|
_sendTracker.Restart();
|
||||||
|
return _adapter.SendPacketsAsync(_options.CommunicationTimeout, _cancellationTokenSource.Token, packets);
|
||||||
}
|
}
|
||||||
|
|
||||||
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 = null;
|
||||||
await _adapter.SendPacketsAsync(_options.CommunicationTimeout, _cancellationTokenSource.Token, requestPacket).ConfigureAwait(false);
|
if (requestPacket is IMqttPacketWithIdentifier requestPacketWithIdentifier)
|
||||||
|
{
|
||||||
|
identifier = requestPacketWithIdentifier.PacketIdentifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
var packetAwaiter = _packetDispatcher.WaitForPacketAsync(typeof(TResponsePacket), identifier, _options.CommunicationTimeout);
|
||||||
|
await SendAsync(requestPacket).ConfigureAwait(false);
|
||||||
|
|
||||||
return (TResponsePacket)await packetAwaiter.ConfigureAwait(false);
|
return (TResponsePacket)await packetAwaiter.ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SendKeepAliveMessagesAsync(CancellationToken cancellationToken)
|
private async Task SendKeepAliveMessagesAsync()
|
||||||
{
|
{
|
||||||
_logger.Info<MqttClient>("Start sending keep alive packets.");
|
_logger.Info<MqttClient>("Start sending keep alive packets.");
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
while (!cancellationToken.IsCancellationRequested)
|
while (!_cancellationTokenSource.Token.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
await SendAndReceiveAsync<MqttPingRespPacket>(new MqttPingReqPacket()).ConfigureAwait(false);
|
var keepAliveSendInterval = TimeSpan.FromSeconds(_options.KeepAlivePeriod.TotalSeconds * 0.75);
|
||||||
await Task.Delay(_options.KeepAlivePeriod, cancellationToken).ConfigureAwait(false);
|
if (_options.KeepAliveSendInterval.HasValue)
|
||||||
|
{
|
||||||
|
keepAliveSendInterval = _options.KeepAliveSendInterval.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_sendTracker.Elapsed > keepAliveSendInterval)
|
||||||
|
{
|
||||||
|
await SendAndReceiveAsync<MqttPingRespPacket>(new MqttPingReqPacket()).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.Delay(keepAliveSendInterval, _cancellationTokenSource.Token).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
if (cancellationToken.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await DisconnectInternalAsync(null).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
catch (MqttCommunicationException exception)
|
|
||||||
{
|
|
||||||
if (cancellationToken.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_logger.Warning<MqttClient>(exception, "MQTT communication exception while sending/receiving keep alive packets.");
|
|
||||||
await DisconnectInternalAsync(exception).ConfigureAwait(false);
|
|
||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
_logger.Warning<MqttClient>(exception, "Unhandled exception while sending/receiving keep alive packets.");
|
if (_cancellationTokenSource.Token.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exception is MqttCommunicationException)
|
||||||
|
{
|
||||||
|
_logger.Warning<MqttClient>(exception, "MQTT communication exception while sending/receiving keep alive packets.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.Warning<MqttClient>(exception, "Unhandled exception while sending/receiving keep alive packets.");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
await DisconnectInternalAsync(exception).ConfigureAwait(false);
|
await DisconnectInternalAsync(exception).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
@@ -382,48 +443,46 @@ namespace MQTTnet.Client
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ReceivePacketsAsync(CancellationToken cancellationToken)
|
private async Task ReceivePacketsAsync()
|
||||||
{
|
{
|
||||||
_logger.Info<MqttClient>("Start receiving packets.");
|
_logger.Info<MqttClient>("Start receiving packets.");
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
while (!cancellationToken.IsCancellationRequested)
|
while (!_cancellationTokenSource.Token.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
_isReceivingPackets = true;
|
_isReceivingPackets = true;
|
||||||
|
|
||||||
var packet = await _adapter.ReceivePacketAsync(TimeSpan.Zero, cancellationToken).ConfigureAwait(false);
|
var packet = await _adapter.ReceivePacketAsync(TimeSpan.Zero, _cancellationTokenSource.Token).ConfigureAwait(false);
|
||||||
|
|
||||||
if (cancellationToken.IsCancellationRequested)
|
if (_cancellationTokenSource.Token.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
StartProcessReceivedPacket(packet, cancellationToken);
|
StartProcessReceivedPacket(packet);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
if (cancellationToken.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await DisconnectInternalAsync(null).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
catch (MqttCommunicationException exception)
|
|
||||||
{
|
|
||||||
if (cancellationToken.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_logger.Warning<MqttClient>(exception, "MQTT communication exception while receiving packets.");
|
|
||||||
await DisconnectInternalAsync(exception).ConfigureAwait(false);
|
|
||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
_logger.Error<MqttClient>(exception, "Unhandled exception while receiving packets.");
|
if (_cancellationTokenSource.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exception is MqttCommunicationException)
|
||||||
|
{
|
||||||
|
_logger.Warning<MqttClient>(exception, "MQTT communication exception while receiving packets.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.Error<MqttClient>(exception, "Unhandled exception while receiving packets.");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
await DisconnectInternalAsync(exception).ConfigureAwait(false);
|
await DisconnectInternalAsync(exception).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
@@ -432,38 +491,26 @@ namespace MQTTnet.Client
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void StartProcessReceivedPacket(MqttBasePacket packet, CancellationToken cancellationToken)
|
private void StartProcessReceivedPacket(MqttBasePacket packet)
|
||||||
{
|
{
|
||||||
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
|
Task.Run(() => ProcessReceivedPacketAsync(packet), _cancellationTokenSource.Token);
|
||||||
Task.Run(
|
|
||||||
async () => await ProcessReceivedPacketAsync(packet).ConfigureAwait(false),
|
|
||||||
cancellationToken).ConfigureAwait(false);
|
|
||||||
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task StartReceivingPacketsAsync(CancellationToken cancellationToken)
|
private async Task StartReceivingPacketsAsync()
|
||||||
{
|
{
|
||||||
_isReceivingPackets = false;
|
_isReceivingPackets = false;
|
||||||
|
|
||||||
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
|
_packetReceiverTask = Task.Run(ReceivePacketsAsync, _cancellationTokenSource.Token);
|
||||||
Task.Run(
|
|
||||||
async () => await ReceivePacketsAsync(cancellationToken).ConfigureAwait(false),
|
|
||||||
cancellationToken).ConfigureAwait(false);
|
|
||||||
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
|
|
||||||
|
|
||||||
while (!_isReceivingPackets && !cancellationToken.IsCancellationRequested)
|
while (!_isReceivingPackets && !_cancellationTokenSource.Token.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
await Task.Delay(TimeSpan.FromMilliseconds(100), cancellationToken).ConfigureAwait(false);
|
await Task.Delay(TimeSpan.FromMilliseconds(100), _cancellationTokenSource.Token).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void StartSendingKeepAliveMessages(CancellationToken cancellationToken)
|
private void StartSendingKeepAliveMessages()
|
||||||
{
|
{
|
||||||
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
|
_keepAliveMessageSenderTask = Task.Run(SendKeepAliveMessagesAsync, _cancellationTokenSource.Token);
|
||||||
Task.Run(
|
|
||||||
async () => await SendKeepAliveMessagesAsync(cancellationToken).ConfigureAwait(false),
|
|
||||||
cancellationToken).ConfigureAwait(false);
|
|
||||||
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -24,6 +24,14 @@ namespace MQTTnet.Client
|
|||||||
return client.SubscribeAsync(new TopicFilterBuilder().WithTopic(topic).WithQualityOfServiceLevel(qualityOfServiceLevel).Build());
|
return client.SubscribeAsync(new TopicFilterBuilder().WithTopic(topic).WithQualityOfServiceLevel(qualityOfServiceLevel).Build());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Task<IList<MqttSubscribeResult>> SubscribeAsync(this IMqttClient client, string topic)
|
||||||
|
{
|
||||||
|
if (client == null) throw new ArgumentNullException(nameof(client));
|
||||||
|
if (topic == null) throw new ArgumentNullException(nameof(topic));
|
||||||
|
|
||||||
|
return client.SubscribeAsync(new TopicFilterBuilder().WithTopic(topic).Build());
|
||||||
|
}
|
||||||
|
|
||||||
public static Task UnsubscribeAsync(this IMqttClient client, params string[] topicFilters)
|
public static Task UnsubscribeAsync(this IMqttClient client, params string[] topicFilters)
|
||||||
{
|
{
|
||||||
if (client == null) throw new ArgumentNullException(nameof(client));
|
if (client == null) throw new ArgumentNullException(nameof(client));
|
||||||
|
|||||||
@@ -13,7 +13,9 @@ namespace MQTTnet.Client
|
|||||||
|
|
||||||
public IMqttClientCredentials Credentials { get; set; } = new MqttClientCredentials();
|
public IMqttClientCredentials Credentials { get; set; } = new MqttClientCredentials();
|
||||||
|
|
||||||
public TimeSpan KeepAlivePeriod { get; set; } = TimeSpan.FromSeconds(5);
|
public TimeSpan KeepAlivePeriod { get; set; } = TimeSpan.FromSeconds(15);
|
||||||
|
|
||||||
|
public TimeSpan? KeepAliveSendInterval { get; set; }
|
||||||
|
|
||||||
public TimeSpan CommunicationTimeout { get; set; } = TimeSpan.FromSeconds(10);
|
public TimeSpan CommunicationTimeout { get; set; } = TimeSpan.FromSeconds(10);
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
|
|
||||||
public int? Port { get; set; }
|
public int? Port { get; set; }
|
||||||
|
|
||||||
|
public int BufferSize { get; set; } = 20 * 4096;
|
||||||
|
|
||||||
public MqttClientTlsOptions TlsOptions { get; set; } = new MqttClientTlsOptions();
|
public MqttClientTlsOptions TlsOptions { get; set; } = new MqttClientTlsOptions();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ namespace MQTTnet.Client
|
|||||||
|
|
||||||
public IDictionary<string, string> RequestHeaders { get; set; }
|
public IDictionary<string, string> RequestHeaders { get; set; }
|
||||||
|
|
||||||
public ICollection<string> SubProtocols { get; set; }
|
public ICollection<string> SubProtocols { get; set; } = new List<string> { "mqtt" };
|
||||||
|
|
||||||
public CookieContainer CookieContainer { get; set; }
|
public CookieContainer CookieContainer { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -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.Value, 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,36 @@ 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)
|
if (!identifier.HasValue)
|
||||||
{
|
{
|
||||||
var byId = _packetByResponseTypeAndIdentifier.GetOrAdd(responseType, key => new ConcurrentDictionary<ushort, TaskCompletionSource<MqttBasePacket>>());
|
identifier = 0;
|
||||||
byId[requestWithIdentifier.PacketIdentifier] = tcs;
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
var byId = _awaiters.GetOrAdd(responseType, key => new ConcurrentDictionary<ushort, TaskCompletionSource<MqttBasePacket>>());
|
||||||
|
if (!byId.TryAdd(identifier.Value, tcs))
|
||||||
{
|
{
|
||||||
_packetByResponseType[responseType] = tcs;
|
throw new InvalidOperationException($"The packet dispatcher already has an awaiter for packet of type '{responseType}' with identifier {identifier}.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return tcs;
|
return tcs;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RemovePacketAwaiter(MqttBasePacket request, Type responseType)
|
private void RemovePacketAwaiter(Type responseType, ushort? identifier)
|
||||||
{
|
{
|
||||||
if (request is IMqttPacketWithIdentifier requestWithIdentifier)
|
if (!identifier.HasValue)
|
||||||
{
|
{
|
||||||
var byId = _packetByResponseTypeAndIdentifier.GetOrAdd(responseType, key => new ConcurrentDictionary<ushort, TaskCompletionSource<MqttBasePacket>>());
|
identifier = 0;
|
||||||
byId.TryRemove(requestWithIdentifier.PacketIdentifier, out var _);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_packetByResponseType.TryRemove(responseType, out var _);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var byId = _awaiters.GetOrAdd(responseType, key => new ConcurrentDictionary<ushort, TaskCompletionSource<MqttBasePacket>>());
|
||||||
|
byId.TryRemove(identifier.Value, 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,14 +13,21 @@ using MQTTnet.Client;
|
|||||||
|
|
||||||
namespace MQTTnet.Implementations
|
namespace MQTTnet.Implementations
|
||||||
{
|
{
|
||||||
public sealed class MqttTcpChannel : IMqttChannel, IDisposable
|
public sealed class MqttTcpChannel : IMqttChannel
|
||||||
{
|
{
|
||||||
|
// ReSharper disable once MemberCanBePrivate.Global
|
||||||
|
// ReSharper disable once AutoPropertyCanBeMadeGetOnly.Global
|
||||||
|
public static int BufferSize { get; set; } = 4096 * 20; // Can be changed for fine tuning by library user.
|
||||||
|
|
||||||
|
private readonly int _bufferSize = BufferSize;
|
||||||
private readonly MqttClientTcpOptions _options;
|
private readonly MqttClientTcpOptions _options;
|
||||||
|
|
||||||
private StreamSocket _socket;
|
private StreamSocket _socket;
|
||||||
|
|
||||||
public MqttTcpChannel(MqttClientTcpOptions options)
|
public MqttTcpChannel(MqttClientTcpOptions options)
|
||||||
{
|
{
|
||||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||||
|
_bufferSize = _options.BufferSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MqttTcpChannel(StreamSocket socket)
|
public MqttTcpChannel(StreamSocket socket)
|
||||||
@@ -69,20 +76,56 @@ namespace MQTTnet.Implementations
|
|||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
SendStream?.Dispose();
|
try
|
||||||
SendStream = null;
|
{
|
||||||
|
SendStream?.Dispose();
|
||||||
|
}
|
||||||
|
catch (ObjectDisposedException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
catch (NullReferenceException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
SendStream = null;
|
||||||
|
}
|
||||||
|
|
||||||
ReceiveStream?.Dispose();
|
try
|
||||||
ReceiveStream = null;
|
{
|
||||||
|
ReceiveStream?.Dispose();
|
||||||
|
}
|
||||||
|
catch (ObjectDisposedException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
catch (NullReferenceException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
ReceiveStream = null;
|
||||||
|
}
|
||||||
|
|
||||||
_socket?.Dispose();
|
try
|
||||||
_socket = null;
|
{
|
||||||
|
_socket?.Dispose();
|
||||||
|
}
|
||||||
|
catch (ObjectDisposedException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
catch (NullReferenceException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_socket = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CreateStreams()
|
private void CreateStreams()
|
||||||
{
|
{
|
||||||
SendStream = _socket.OutputStream.AsStreamForWrite();
|
SendStream = _socket.OutputStream.AsStreamForWrite(_bufferSize);
|
||||||
ReceiveStream = _socket.InputStream.AsStreamForRead();
|
ReceiveStream = _socket.InputStream.AsStreamForRead(_bufferSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Certificate LoadCertificate(MqttClientTcpOptions options)
|
private static Certificate LoadCertificate(MqttClientTcpOptions options)
|
||||||
|
|||||||
@@ -12,17 +12,20 @@ using MQTTnet.Client;
|
|||||||
|
|
||||||
namespace MQTTnet.Implementations
|
namespace MQTTnet.Implementations
|
||||||
{
|
{
|
||||||
public sealed class MqttTcpChannel : IMqttChannel, IDisposable
|
public sealed class MqttTcpChannel : IMqttChannel
|
||||||
{
|
{
|
||||||
private readonly MqttClientTcpOptions _options;
|
|
||||||
|
|
||||||
//todo: this can be used with min dependency NetStandard1.6
|
|
||||||
#if NET452 || NET461
|
#if NET452 || NET461
|
||||||
// ReSharper disable once MemberCanBePrivate.Global
|
// ReSharper disable once MemberCanBePrivate.Global
|
||||||
// ReSharper disable once AutoPropertyCanBeMadeGetOnly.Global
|
// ReSharper disable once AutoPropertyCanBeMadeGetOnly.Global
|
||||||
public static int BufferSize { get; set; } = 4096 * 20; // Can be changed for fine tuning by library user.
|
public static int BufferSize { get; set; } = 4096 * 20; // Can be changed for fine tuning by library user.
|
||||||
|
|
||||||
|
private readonly int _bufferSize = BufferSize;
|
||||||
|
#else
|
||||||
|
private readonly int _bufferSize = 0;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
private readonly MqttClientTcpOptions _options;
|
||||||
|
|
||||||
private Socket _socket;
|
private Socket _socket;
|
||||||
private SslStream _sslStream;
|
private SslStream _sslStream;
|
||||||
|
|
||||||
@@ -32,6 +35,7 @@ namespace MQTTnet.Implementations
|
|||||||
public MqttTcpChannel(MqttClientTcpOptions options)
|
public MqttTcpChannel(MqttClientTcpOptions options)
|
||||||
{
|
{
|
||||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||||
|
_bufferSize = options.BufferSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -43,7 +47,7 @@ namespace MQTTnet.Implementations
|
|||||||
_socket = socket ?? throw new ArgumentNullException(nameof(socket));
|
_socket = socket ?? throw new ArgumentNullException(nameof(socket));
|
||||||
_sslStream = sslStream;
|
_sslStream = sslStream;
|
||||||
|
|
||||||
CreateStreams(socket, sslStream);
|
CreateStreams();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Stream SendStream { get; private set; }
|
public Stream SendStream { get; private set; }
|
||||||
@@ -58,7 +62,6 @@ namespace MQTTnet.Implementations
|
|||||||
_socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
|
_socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
|
||||||
}
|
}
|
||||||
|
|
||||||
//todo: else brach can be used with min dependency NET46
|
|
||||||
#if NET452 || NET461
|
#if NET452 || NET461
|
||||||
await Task.Factory.FromAsync(_socket.BeginConnect, _socket.EndConnect, _options.Server, _options.GetPort(), null).ConfigureAwait(false);
|
await Task.Factory.FromAsync(_socket.BeginConnect, _socket.EndConnect, _options.Server, _options.GetPort(), null).ConfigureAwait(false);
|
||||||
#else
|
#else
|
||||||
@@ -68,10 +71,10 @@ namespace MQTTnet.Implementations
|
|||||||
if (_options.TlsOptions.UseTls)
|
if (_options.TlsOptions.UseTls)
|
||||||
{
|
{
|
||||||
_sslStream = new SslStream(new NetworkStream(_socket, true), false, InternalUserCertificateValidationCallback);
|
_sslStream = new SslStream(new NetworkStream(_socket, true), false, InternalUserCertificateValidationCallback);
|
||||||
await _sslStream.AuthenticateAsClientAsync(_options.Server, LoadCertificates(_options), SslProtocols.Tls12, _options.TlsOptions.IgnoreCertificateRevocationErrors).ConfigureAwait(false);
|
await _sslStream.AuthenticateAsClientAsync(_options.Server, LoadCertificates(), SslProtocols.Tls12, _options.TlsOptions.IgnoreCertificateRevocationErrors).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
CreateStreams(_socket, _sslStream);
|
CreateStreams();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task DisconnectAsync()
|
public Task DisconnectAsync()
|
||||||
@@ -82,11 +85,70 @@ namespace MQTTnet.Implementations
|
|||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_socket?.Dispose();
|
var oneStreamIsUsed = SendStream != null && ReceiveStream != null && ReferenceEquals(SendStream, ReceiveStream);
|
||||||
_socket = null;
|
|
||||||
|
|
||||||
_sslStream?.Dispose();
|
try
|
||||||
_sslStream = null;
|
{
|
||||||
|
SendStream?.Dispose();
|
||||||
|
}
|
||||||
|
catch (ObjectDisposedException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
catch (NullReferenceException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
SendStream = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!oneStreamIsUsed)
|
||||||
|
{
|
||||||
|
ReceiveStream?.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (ObjectDisposedException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
catch (NullReferenceException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
ReceiveStream = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_sslStream?.Dispose();
|
||||||
|
}
|
||||||
|
catch (ObjectDisposedException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
catch (NullReferenceException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_sslStream = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_socket?.Dispose();
|
||||||
|
}
|
||||||
|
catch (ObjectDisposedException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
catch (NullReferenceException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_socket = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool InternalUserCertificateValidationCallback(object sender, X509Certificate x509Certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
|
private bool InternalUserCertificateValidationCallback(object sender, X509Certificate x509Certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
|
||||||
@@ -120,15 +182,15 @@ namespace MQTTnet.Implementations
|
|||||||
return _options.TlsOptions.AllowUntrustedCertificates;
|
return _options.TlsOptions.AllowUntrustedCertificates;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static X509CertificateCollection LoadCertificates(MqttClientTcpOptions options)
|
private X509CertificateCollection LoadCertificates()
|
||||||
{
|
{
|
||||||
var certificates = new X509CertificateCollection();
|
var certificates = new X509CertificateCollection();
|
||||||
if (options.TlsOptions.Certificates == null)
|
if (_options.TlsOptions.Certificates == null)
|
||||||
{
|
{
|
||||||
return certificates;
|
return certificates;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var certificate in options.TlsOptions.Certificates)
|
foreach (var certificate in _options.TlsOptions.Certificates)
|
||||||
{
|
{
|
||||||
certificates.Add(new X509Certificate2(certificate));
|
certificates.Add(new X509Certificate2(certificate));
|
||||||
}
|
}
|
||||||
@@ -136,20 +198,26 @@ namespace MQTTnet.Implementations
|
|||||||
return certificates;
|
return certificates;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CreateStreams(Socket socket, Stream sslStream)
|
private void CreateStreams()
|
||||||
{
|
{
|
||||||
var stream = sslStream ?? new NetworkStream(socket);
|
Stream stream;
|
||||||
|
if (_sslStream != null)
|
||||||
|
{
|
||||||
|
stream = _sslStream;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
stream = new NetworkStream(_socket, true);
|
||||||
|
}
|
||||||
|
|
||||||
//todo: if branch can be used with min dependency NetStandard1.6
|
|
||||||
#if NET452 || NET461
|
#if NET452 || NET461
|
||||||
SendStream = new BufferedStream(stream, BufferSize);
|
SendStream = new BufferedStream(stream, _bufferSize);
|
||||||
ReceiveStream = new BufferedStream(stream, BufferSize);
|
ReceiveStream = new BufferedStream(stream, _bufferSize);
|
||||||
#else
|
#else
|
||||||
SendStream = stream;
|
SendStream = stream;
|
||||||
ReceiveStream = stream;
|
ReceiveStream = stream;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -96,8 +96,17 @@ namespace MQTTnet.Implementations
|
|||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_webSocket?.Dispose();
|
try
|
||||||
_webSocket = null;
|
{
|
||||||
|
_webSocket?.Dispose();
|
||||||
|
}
|
||||||
|
catch (ObjectDisposedException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_webSocket = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -11,9 +11,9 @@ namespace MQTTnet.Implementations
|
|||||||
{
|
{
|
||||||
public class WebSocketStream : Stream
|
public class WebSocketStream : Stream
|
||||||
{
|
{
|
||||||
private readonly WebSocket _webSocket;
|
private readonly byte[] _chunckBuffer = new byte[MqttWebSocketChannel.BufferSize];
|
||||||
private readonly byte[] _chunkBuffer = new byte[MqttWebSocketChannel.BufferSize];
|
|
||||||
private readonly Queue<byte> _buffer = new Queue<byte>(MqttWebSocketChannel.BufferSize);
|
private readonly Queue<byte> _buffer = new Queue<byte>(MqttWebSocketChannel.BufferSize);
|
||||||
|
private readonly WebSocket _webSocket;
|
||||||
|
|
||||||
public WebSocketStream(WebSocket webSocket)
|
public WebSocketStream(WebSocket webSocket)
|
||||||
{
|
{
|
||||||
@@ -55,9 +55,10 @@ namespace MQTTnet.Implementations
|
|||||||
// Use existing date from buffer.
|
// Use existing date from buffer.
|
||||||
while (count > 0 && _buffer.Any())
|
while (count > 0 && _buffer.Any())
|
||||||
{
|
{
|
||||||
buffer[offset++] = _buffer.Dequeue();
|
buffer[offset] = _buffer.Dequeue();
|
||||||
count--;
|
count--;
|
||||||
bytesRead++;
|
bytesRead++;
|
||||||
|
offset++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (count == 0)
|
if (count == 0)
|
||||||
@@ -65,15 +66,17 @@ namespace MQTTnet.Implementations
|
|||||||
return bytesRead;
|
return bytesRead;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fetch new data if the buffer is not full.
|
||||||
while (_webSocket.State == WebSocketState.Open)
|
while (_webSocket.State == WebSocketState.Open)
|
||||||
{
|
{
|
||||||
await FetchChunkAsync(cancellationToken);
|
await FetchChunkAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
while (count > 0 && _buffer.Any())
|
while (count > 0 && _buffer.Any())
|
||||||
{
|
{
|
||||||
buffer[offset++] = _buffer.Dequeue();
|
buffer[offset] = _buffer.Dequeue();
|
||||||
count--;
|
count--;
|
||||||
bytesRead++;
|
bytesRead++;
|
||||||
|
offset++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (count == 0)
|
if (count == 0)
|
||||||
@@ -112,18 +115,23 @@ namespace MQTTnet.Implementations
|
|||||||
|
|
||||||
private async Task FetchChunkAsync(CancellationToken cancellationToken)
|
private async Task FetchChunkAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var response = await _webSocket.ReceiveAsync(new ArraySegment<byte>(_chunkBuffer, 0, _chunkBuffer.Length), cancellationToken).ConfigureAwait(false);
|
var response = await _webSocket.ReceiveAsync(new ArraySegment<byte>(_chunckBuffer, 0, _chunckBuffer.Length), cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
for (var i = 0; i < response.Count; i++)
|
|
||||||
{
|
|
||||||
var @byte = _chunkBuffer[i];
|
|
||||||
_buffer.Enqueue(@byte);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.MessageType == WebSocketMessageType.Close)
|
if (response.MessageType == WebSocketMessageType.Close)
|
||||||
{
|
{
|
||||||
await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, cancellationToken).ConfigureAwait(false);
|
await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
else if (response.MessageType == WebSocketMessageType.Binary)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < response.Count; i++)
|
||||||
|
{
|
||||||
|
_buffer.Enqueue(_chunckBuffer[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (response.MessageType == WebSocketMessageType.Text)
|
||||||
|
{
|
||||||
|
throw new MqttProtocolViolationException("WebSocket channel received TEXT message.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,11 +7,14 @@ namespace MQTTnet.ManagedClient
|
|||||||
{
|
{
|
||||||
public interface IManagedMqttClient : IApplicationMessageReceiver, IApplicationMessagePublisher, IDisposable
|
public interface IManagedMqttClient : IApplicationMessageReceiver, IApplicationMessagePublisher, IDisposable
|
||||||
{
|
{
|
||||||
|
bool IsStarted { get; }
|
||||||
bool IsConnected { get; }
|
bool IsConnected { get; }
|
||||||
|
|
||||||
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;
|
||||||
@@ -39,10 +40,13 @@ namespace MQTTnet.ManagedClient
|
|||||||
}
|
}
|
||||||
|
|
||||||
public bool IsConnected => _mqttClient.IsConnected;
|
public bool IsConnected => _mqttClient.IsConnected;
|
||||||
|
public bool IsStarted => _connectionCancellationToken != null;
|
||||||
|
|
||||||
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)
|
||||||
{
|
{
|
||||||
@@ -67,7 +71,7 @@ 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");
|
||||||
@@ -129,6 +133,7 @@ namespace MQTTnet.ManagedClient
|
|||||||
{
|
{
|
||||||
if (_subscriptions.Remove(topic))
|
if (_subscriptions.Remove(topic))
|
||||||
{
|
{
|
||||||
|
_unsubscriptions.Add(topic);
|
||||||
_subscriptionsNotPushed = true;
|
_subscriptionsNotPushed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -153,7 +158,7 @@ namespace MQTTnet.ManagedClient
|
|||||||
{
|
{
|
||||||
while (!cancellationToken.IsCancellationRequested)
|
while (!cancellationToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
await TryMaintainConnectionAsync(cancellationToken);
|
await TryMaintainConnectionAsync(cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
@@ -184,7 +189,7 @@ namespace MQTTnet.ManagedClient
|
|||||||
|
|
||||||
if (connectionState == ReconnectionResult.Reconnected || _subscriptionsNotPushed)
|
if (connectionState == ReconnectionResult.Reconnected || _subscriptionsNotPushed)
|
||||||
{
|
{
|
||||||
await PushSubscriptionsAsync().ConfigureAwait(false);
|
await SynchronizeSubscriptionsAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
StartPublishing();
|
StartPublishing();
|
||||||
|
|
||||||
@@ -245,6 +250,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 +262,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,19 +273,30 @@ 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
|
||||||
@@ -285,19 +304,29 @@ namespace MQTTnet.ManagedClient
|
|||||||
_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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,14 @@ namespace MQTTnet.ManagedClient
|
|||||||
return managedClient.SubscribeAsync(new TopicFilterBuilder().WithTopic(topic).WithQualityOfServiceLevel(qualityOfServiceLevel).Build());
|
return managedClient.SubscribeAsync(new TopicFilterBuilder().WithTopic(topic).WithQualityOfServiceLevel(qualityOfServiceLevel).Build());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Task SubscribeAsync(this IManagedMqttClient managedClient, string topic)
|
||||||
|
{
|
||||||
|
if (managedClient == null) throw new ArgumentNullException(nameof(managedClient));
|
||||||
|
if (topic == null) throw new ArgumentNullException(nameof(topic));
|
||||||
|
|
||||||
|
return managedClient.SubscribeAsync(new TopicFilterBuilder().WithTopic(topic).Build());
|
||||||
|
}
|
||||||
|
|
||||||
public static Task UnsubscribeAsync(this IManagedMqttClient managedClient, params string[] topicFilters)
|
public static Task UnsubscribeAsync(this IManagedMqttClient managedClient, params string[] topicFilters)
|
||||||
{
|
{
|
||||||
if (managedClient == null) throw new ArgumentNullException(nameof(managedClient));
|
if (managedClient == null) throw new ArgumentNullException(nameof(managedClient));
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ namespace MQTTnet.ManagedClient
|
|||||||
public class ManagedMqttClientOptionsBuilder
|
public class ManagedMqttClientOptionsBuilder
|
||||||
{
|
{
|
||||||
private readonly ManagedMqttClientOptions _options = new ManagedMqttClientOptions();
|
private readonly ManagedMqttClientOptions _options = new ManagedMqttClientOptions();
|
||||||
|
private MqttClientOptionsBuilder _clientOptionsBuilder;
|
||||||
|
|
||||||
public ManagedMqttClientOptionsBuilder WithAutoReconnectDelay(TimeSpan value)
|
public ManagedMqttClientOptionsBuilder WithAutoReconnectDelay(TimeSpan value)
|
||||||
{
|
{
|
||||||
@@ -21,7 +22,24 @@ namespace MQTTnet.ManagedClient
|
|||||||
|
|
||||||
public ManagedMqttClientOptionsBuilder WithClientOptions(IMqttClientOptions value)
|
public ManagedMqttClientOptionsBuilder WithClientOptions(IMqttClientOptions value)
|
||||||
{
|
{
|
||||||
|
if (_clientOptionsBuilder != null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Cannot use client options builder and client options at the same time.");
|
||||||
|
}
|
||||||
|
|
||||||
_options.ClientOptions = value ?? throw new ArgumentNullException(nameof(value));
|
_options.ClientOptions = value ?? throw new ArgumentNullException(nameof(value));
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ManagedMqttClientOptionsBuilder WithClientOptions(MqttClientOptionsBuilder builder)
|
||||||
|
{
|
||||||
|
if (_options.ClientOptions != null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Cannot use client options builder and client options at the same time.");
|
||||||
|
}
|
||||||
|
|
||||||
|
_clientOptionsBuilder = builder;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,15 +47,22 @@ namespace MQTTnet.ManagedClient
|
|||||||
{
|
{
|
||||||
if (options == null) throw new ArgumentNullException(nameof(options));
|
if (options == null) throw new ArgumentNullException(nameof(options));
|
||||||
|
|
||||||
var builder = new MqttClientOptionsBuilder();
|
if (_clientOptionsBuilder == null)
|
||||||
options(builder);
|
{
|
||||||
_options.ClientOptions = builder.Build();
|
_clientOptionsBuilder = new MqttClientOptionsBuilder();
|
||||||
|
}
|
||||||
|
|
||||||
|
options(_clientOptionsBuilder);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ManagedMqttClientOptions Build()
|
public ManagedMqttClientOptions Build()
|
||||||
{
|
{
|
||||||
|
if (_clientOptionsBuilder != null)
|
||||||
|
{
|
||||||
|
_options.ClientOptions = _clientOptionsBuilder.Build();
|
||||||
|
}
|
||||||
|
|
||||||
if (_options.ClientOptions == null)
|
if (_options.ClientOptions == null)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("The ClientOptions cannot be null.");
|
throw new InvalidOperationException("The ClientOptions cannot be null.");
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
using System;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace MQTTnet
|
||||||
|
{
|
||||||
|
public static class MqttApplicationMessageExtensions
|
||||||
|
{
|
||||||
|
public static string ConvertPayloadToString(this MqttApplicationMessage applicationMessage)
|
||||||
|
{
|
||||||
|
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
|
||||||
|
|
||||||
|
if (applicationMessage.Payload == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (applicationMessage.Payload.Length == 0)
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Encoding.UTF8.GetString(applicationMessage.Payload, 0, applicationMessage.Payload.Length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,6 @@
|
|||||||
{
|
{
|
||||||
public interface IMqttPacketWithIdentifier
|
public interface IMqttPacketWithIdentifier
|
||||||
{
|
{
|
||||||
ushort PacketIdentifier { get; set; }
|
ushort? PacketIdentifier { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,6 @@
|
|||||||
{
|
{
|
||||||
public class MqttBasePublishPacket : MqttBasePacket, IMqttPacketWithIdentifier
|
public class MqttBasePublishPacket : MqttBasePacket, IMqttPacketWithIdentifier
|
||||||
{
|
{
|
||||||
public ushort PacketIdentifier { get; set; }
|
public ushort? PacketIdentifier { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace MQTTnet.Packets
|
|
||||||
{
|
|
||||||
public static class MqttPacketExtensions
|
|
||||||
{
|
|
||||||
public static TResponsePacket CreateResponse<TResponsePacket>(this MqttBasePacket packet)
|
|
||||||
{
|
|
||||||
if (packet == null) throw new ArgumentNullException(nameof(packet));
|
|
||||||
|
|
||||||
var responsePacket = Activator.CreateInstance<TResponsePacket>();
|
|
||||||
|
|
||||||
if (responsePacket is IMqttPacketWithIdentifier responsePacketWithIdentifier)
|
|
||||||
{
|
|
||||||
if (!(packet is IMqttPacketWithIdentifier requestPacketWithIdentifier))
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("Response packet has PacketIdentifier but request packet does not.");
|
|
||||||
}
|
|
||||||
|
|
||||||
responsePacketWithIdentifier.PacketIdentifier = requestPacketWithIdentifier.PacketIdentifier;
|
|
||||||
}
|
|
||||||
|
|
||||||
return responsePacket;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -6,7 +6,7 @@ namespace MQTTnet.Packets
|
|||||||
{
|
{
|
||||||
public sealed class MqttSubAckPacket : MqttBasePacket, IMqttPacketWithIdentifier
|
public sealed class MqttSubAckPacket : MqttBasePacket, IMqttPacketWithIdentifier
|
||||||
{
|
{
|
||||||
public ushort PacketIdentifier { get; set; }
|
public ushort? PacketIdentifier { get; set; }
|
||||||
|
|
||||||
public IList<MqttSubscribeReturnCode> SubscribeReturnCodes { get; } = new List<MqttSubscribeReturnCode>();
|
public IList<MqttSubscribeReturnCode> SubscribeReturnCodes { get; } = new List<MqttSubscribeReturnCode>();
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ namespace MQTTnet.Packets
|
|||||||
{
|
{
|
||||||
public sealed class MqttSubscribePacket : MqttBasePacket, IMqttPacketWithIdentifier
|
public sealed class MqttSubscribePacket : MqttBasePacket, IMqttPacketWithIdentifier
|
||||||
{
|
{
|
||||||
public ushort PacketIdentifier { get; set; }
|
public ushort? PacketIdentifier { get; set; }
|
||||||
|
|
||||||
public IList<TopicFilter> TopicFilters { get; set; } = new List<TopicFilter>();
|
public IList<TopicFilter> TopicFilters { get; set; } = new List<TopicFilter>();
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
{
|
{
|
||||||
public sealed class MqttUnsubAckPacket : MqttBasePacket, IMqttPacketWithIdentifier
|
public sealed class MqttUnsubAckPacket : MqttBasePacket, IMqttPacketWithIdentifier
|
||||||
{
|
{
|
||||||
public ushort PacketIdentifier { get; set; }
|
public ushort? PacketIdentifier { get; set; }
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ namespace MQTTnet.Packets
|
|||||||
{
|
{
|
||||||
public sealed class MqttUnsubscribePacket : MqttBasePacket, IMqttPacketWithIdentifier
|
public sealed class MqttUnsubscribePacket : MqttBasePacket, IMqttPacketWithIdentifier
|
||||||
{
|
{
|
||||||
public ushort PacketIdentifier { get; set; }
|
public ushort? PacketIdentifier { get; set; }
|
||||||
|
|
||||||
public IList<string> TopicFilters { get; set; } = new List<string>();
|
public IList<string> TopicFilters { get; set; } = new List<string>();
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,11 @@ namespace MQTTnet.Serializer
|
|||||||
|
|
||||||
public static async Task<MqttPacketHeader> ReadHeaderAsync(Stream stream, CancellationToken cancellationToken)
|
public static async Task<MqttPacketHeader> ReadHeaderAsync(Stream stream, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
if (cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// Wait for the next package which starts with the header. At this point there will probably
|
// Wait for the next package which starts with the header. At this point there will probably
|
||||||
// some large delay and thus the thread should be put back to the pool (await). So ReadByte()
|
// some large delay and thus the thread should be put back to the pool (await). So ReadByte()
|
||||||
// is not an option here.
|
// is not an option here.
|
||||||
|
|||||||
@@ -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,16 +278,18 @@ 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;
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
@@ -388,7 +397,12 @@ namespace MQTTnet.Serializer
|
|||||||
|
|
||||||
private static byte Serialize(MqttPubRelPacket packet, MqttPacketWriter writer)
|
private static byte Serialize(MqttPubRelPacket packet, MqttPacketWriter writer)
|
||||||
{
|
{
|
||||||
writer.Write(packet.PacketIdentifier);
|
if (!packet.PacketIdentifier.HasValue)
|
||||||
|
{
|
||||||
|
throw new MqttProtocolViolationException("PubRel packet has no packet identifier.");
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.Write(packet.PacketIdentifier.Value);
|
||||||
|
|
||||||
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.PubRel, 0x02);
|
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.PubRel, 0x02);
|
||||||
}
|
}
|
||||||
@@ -401,7 +415,12 @@ namespace MQTTnet.Serializer
|
|||||||
|
|
||||||
if (packet.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce)
|
if (packet.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce)
|
||||||
{
|
{
|
||||||
writer.Write(packet.PacketIdentifier);
|
if (!packet.PacketIdentifier.HasValue)
|
||||||
|
{
|
||||||
|
throw new MqttProtocolViolationException("Publish packet has no packet identifier.");
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.Write(packet.PacketIdentifier.Value);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -435,21 +454,36 @@ namespace MQTTnet.Serializer
|
|||||||
|
|
||||||
private static byte Serialize(MqttPubAckPacket packet, MqttPacketWriter writer)
|
private static byte Serialize(MqttPubAckPacket packet, MqttPacketWriter writer)
|
||||||
{
|
{
|
||||||
writer.Write(packet.PacketIdentifier);
|
if (!packet.PacketIdentifier.HasValue)
|
||||||
|
{
|
||||||
|
throw new MqttProtocolViolationException("PubAck packet has no packet identifier.");
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.Write(packet.PacketIdentifier.Value);
|
||||||
|
|
||||||
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.PubAck);
|
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.PubAck);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte Serialize(MqttPubRecPacket packet, MqttPacketWriter writer)
|
private static byte Serialize(MqttPubRecPacket packet, MqttPacketWriter writer)
|
||||||
{
|
{
|
||||||
writer.Write(packet.PacketIdentifier);
|
if (!packet.PacketIdentifier.HasValue)
|
||||||
|
{
|
||||||
|
throw new MqttProtocolViolationException("PubRec packet has no packet identifier.");
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.Write(packet.PacketIdentifier.Value);
|
||||||
|
|
||||||
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.PubRec);
|
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.PubRec);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte Serialize(MqttPubCompPacket packet, MqttPacketWriter writer)
|
private static byte Serialize(MqttPubCompPacket packet, MqttPacketWriter writer)
|
||||||
{
|
{
|
||||||
writer.Write(packet.PacketIdentifier);
|
if (!packet.PacketIdentifier.HasValue)
|
||||||
|
{
|
||||||
|
throw new MqttProtocolViolationException("PubComp packet has no packet identifier.");
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.Write(packet.PacketIdentifier.Value);
|
||||||
|
|
||||||
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.PubComp);
|
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.PubComp);
|
||||||
}
|
}
|
||||||
@@ -458,7 +492,12 @@ namespace MQTTnet.Serializer
|
|||||||
{
|
{
|
||||||
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].");
|
||||||
|
|
||||||
writer.Write(packet.PacketIdentifier);
|
if (!packet.PacketIdentifier.HasValue)
|
||||||
|
{
|
||||||
|
throw new MqttProtocolViolationException("Subscribe packet has no packet identifier.");
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.Write(packet.PacketIdentifier.Value);
|
||||||
|
|
||||||
if (packet.TopicFilters?.Count > 0)
|
if (packet.TopicFilters?.Count > 0)
|
||||||
{
|
{
|
||||||
@@ -474,7 +513,12 @@ namespace MQTTnet.Serializer
|
|||||||
|
|
||||||
private static byte Serialize(MqttSubAckPacket packet, MqttPacketWriter writer)
|
private static byte Serialize(MqttSubAckPacket packet, MqttPacketWriter writer)
|
||||||
{
|
{
|
||||||
writer.Write(packet.PacketIdentifier);
|
if (!packet.PacketIdentifier.HasValue)
|
||||||
|
{
|
||||||
|
throw new MqttProtocolViolationException("SubAck packet has no packet identifier.");
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.Write(packet.PacketIdentifier.Value);
|
||||||
|
|
||||||
if (packet.SubscribeReturnCodes?.Any() == true)
|
if (packet.SubscribeReturnCodes?.Any() == true)
|
||||||
{
|
{
|
||||||
@@ -491,7 +535,12 @@ namespace MQTTnet.Serializer
|
|||||||
{
|
{
|
||||||
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].");
|
||||||
|
|
||||||
writer.Write(packet.PacketIdentifier);
|
if (!packet.PacketIdentifier.HasValue)
|
||||||
|
{
|
||||||
|
throw new MqttProtocolViolationException("Unsubscribe packet has no packet identifier.");
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.Write(packet.PacketIdentifier.Value);
|
||||||
|
|
||||||
if (packet.TopicFilters?.Any() == true)
|
if (packet.TopicFilters?.Any() == true)
|
||||||
{
|
{
|
||||||
@@ -504,9 +553,14 @@ namespace MQTTnet.Serializer
|
|||||||
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.Unsubscibe, 0x02);
|
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.Unsubscibe, 0x02);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte Serialize(IMqttPacketWithIdentifier packet, BinaryWriter writer)
|
private static byte Serialize(MqttUnsubAckPacket packet, BinaryWriter writer)
|
||||||
{
|
{
|
||||||
writer.Write(packet.PacketIdentifier);
|
if (!packet.PacketIdentifier.HasValue)
|
||||||
|
{
|
||||||
|
throw new MqttProtocolViolationException("UnsubAck packet has no packet identifier.");
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.Write(packet.PacketIdentifier.Value);
|
||||||
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.UnsubAck);
|
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.UnsubAck);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,98 @@
|
|||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using MQTTnet.Diagnostics;
|
||||||
|
using MQTTnet.Packets;
|
||||||
|
|
||||||
|
namespace MQTTnet.Server
|
||||||
|
{
|
||||||
|
public sealed class MqttClientKeepAliveMonitor
|
||||||
|
{
|
||||||
|
private readonly Stopwatch _lastPacketReceivedTracker = new Stopwatch();
|
||||||
|
private readonly Stopwatch _lastNonKeepAlivePacketReceivedTracker = new Stopwatch();
|
||||||
|
|
||||||
|
private readonly string _clientId;
|
||||||
|
private readonly Func<Task> _timeoutCallback;
|
||||||
|
private readonly IMqttNetLogger _logger;
|
||||||
|
|
||||||
|
private Task _workerTask;
|
||||||
|
|
||||||
|
public MqttClientKeepAliveMonitor(string clientId, Func<Task> timeoutCallback, IMqttNetLogger logger)
|
||||||
|
{
|
||||||
|
_clientId = clientId;
|
||||||
|
_timeoutCallback = timeoutCallback;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TimeSpan LastPacketReceived => _lastPacketReceivedTracker.Elapsed;
|
||||||
|
|
||||||
|
public TimeSpan LastNonKeepAlivePacketReceived => _lastNonKeepAlivePacketReceivedTracker.Elapsed;
|
||||||
|
|
||||||
|
public void Start(int keepAlivePeriod, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (keepAlivePeriod == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_workerTask = Task.Run(() => RunAsync(keepAlivePeriod, cancellationToken).ConfigureAwait(false), cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WaitForCompletion()
|
||||||
|
{
|
||||||
|
if (_workerTask != null)
|
||||||
|
{
|
||||||
|
Task.WaitAll(_workerTask);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task RunAsync(int keepAlivePeriod, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_lastPacketReceivedTracker.Restart();
|
||||||
|
_lastNonKeepAlivePacketReceivedTracker.Restart();
|
||||||
|
|
||||||
|
while (!cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
// Values described here: [MQTT-3.1.2-24].
|
||||||
|
if (_lastPacketReceivedTracker.Elapsed.TotalSeconds > keepAlivePeriod * 1.5D)
|
||||||
|
{
|
||||||
|
_logger.Warning<MqttClientSession>("Client '{0}': Did not receive any packet or keep alive signal.", _clientId);
|
||||||
|
|
||||||
|
if (_timeoutCallback != null)
|
||||||
|
{
|
||||||
|
await _timeoutCallback().ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.Delay(keepAlivePeriod, cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
_logger.Error<MqttClientSession>(exception, "Client '{0}': Unhandled exception while checking keep alive timeouts.", _clientId);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_logger.Trace<MqttClientSession>("Client {0}: Stopped checking keep alive timeout.", _clientId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PacketReceived(MqttBasePacket packet)
|
||||||
|
{
|
||||||
|
_lastPacketReceivedTracker.Restart();
|
||||||
|
|
||||||
|
if (!(packet is MqttPingReqPacket))
|
||||||
|
{
|
||||||
|
_lastNonKeepAlivePacketReceivedTracker.Restart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MQTTnet.Adapter;
|
using MQTTnet.Adapter;
|
||||||
@@ -18,6 +19,8 @@ namespace MQTTnet.Server
|
|||||||
private readonly MqttClientSession _clientSession;
|
private readonly MqttClientSession _clientSession;
|
||||||
private readonly IMqttNetLogger _logger;
|
private readonly IMqttNetLogger _logger;
|
||||||
|
|
||||||
|
private Task _workerTask;
|
||||||
|
|
||||||
public MqttClientPendingMessagesQueue(IMqttServerOptions options, MqttClientSession clientSession, IMqttNetLogger logger)
|
public MqttClientPendingMessagesQueue(IMqttServerOptions options, MqttClientSession clientSession, IMqttNetLogger logger)
|
||||||
{
|
{
|
||||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||||
@@ -36,7 +39,15 @@ namespace MQTTnet.Server
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Task.Run(async () => await SendQueuedPacketsAsync(adapter, cancellationToken), cancellationToken).ConfigureAwait(false);
|
_workerTask = Task.Run(() => SendQueuedPacketsAsync(adapter, cancellationToken), cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WaitForCompletion()
|
||||||
|
{
|
||||||
|
if (_workerTask != null)
|
||||||
|
{
|
||||||
|
Task.WaitAll(_workerTask);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Enqueue(MqttBasePacket packet)
|
public void Enqueue(MqttBasePacket packet)
|
||||||
@@ -55,7 +66,7 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
while (!cancellationToken.IsCancellationRequested)
|
while (!cancellationToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
await SendQueuedPacketAsync(adapter, cancellationToken);
|
await SendNextQueuedPacketAsync(adapter, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
@@ -67,7 +78,7 @@ namespace MQTTnet.Server
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SendQueuedPacketAsync(IMqttChannelAdapter adapter, CancellationToken cancellationToken)
|
private async Task SendNextQueuedPacketAsync(IMqttChannelAdapter adapter, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
MqttBasePacket packet = null;
|
MqttBasePacket packet = null;
|
||||||
try
|
try
|
||||||
@@ -78,7 +89,12 @@ namespace MQTTnet.Server
|
|||||||
throw new InvalidOperationException(); // should not happen
|
throw new InvalidOperationException(); // should not happen
|
||||||
}
|
}
|
||||||
|
|
||||||
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, packet).ConfigureAwait(false);
|
if (cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { packet }).ConfigureAwait(false);
|
||||||
|
|
||||||
_logger.Trace<MqttClientPendingMessagesQueue>("Enqueued packet sent (ClientId: {0}).", _clientSession.ClientId);
|
_logger.Trace<MqttClientPendingMessagesQueue>("Enqueued packet sent (ClientId: {0}).", _clientSession.ClientId);
|
||||||
}
|
}
|
||||||
@@ -110,7 +126,10 @@ namespace MQTTnet.Server
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await _clientSession.StopAsync();
|
if (!cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
await _clientSession.StopAsync().ConfigureAwait(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
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;
|
||||||
|
using MQTTnet.Client;
|
||||||
using MQTTnet.Diagnostics;
|
using MQTTnet.Diagnostics;
|
||||||
using MQTTnet.Exceptions;
|
using MQTTnet.Exceptions;
|
||||||
using MQTTnet.Internal;
|
using MQTTnet.Internal;
|
||||||
@@ -15,12 +15,9 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
public sealed class MqttClientSession : IDisposable
|
public sealed class MqttClientSession : IDisposable
|
||||||
{
|
{
|
||||||
private readonly Stopwatch _lastPacketReceivedTracker = Stopwatch.StartNew();
|
private readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider();
|
||||||
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;
|
||||||
@@ -39,6 +36,7 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
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 +47,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 +69,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);
|
||||||
}
|
}
|
||||||
@@ -107,14 +96,14 @@ namespace MQTTnet.Server
|
|||||||
}
|
}
|
||||||
|
|
||||||
_cancellationTokenSource?.Cancel(false);
|
_cancellationTokenSource?.Cancel(false);
|
||||||
|
|
||||||
|
PendingMessagesQueue.WaitForCompletion();
|
||||||
|
KeepAliveMonitor.WaitForCompletion();
|
||||||
|
|
||||||
_cancellationTokenSource?.Dispose();
|
_cancellationTokenSource?.Dispose();
|
||||||
_cancellationTokenSource = null;
|
_cancellationTokenSource = null;
|
||||||
|
|
||||||
if (_adapter != null)
|
_adapter = null;
|
||||||
{
|
|
||||||
await _adapter.DisconnectAsync(_options.DefaultCommunicationTimeout).ConfigureAwait(false);
|
|
||||||
_adapter = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
_logger.Info<MqttClientSession>("Client '{0}': Session stopped.", ClientId);
|
_logger.Info<MqttClientSession>("Client '{0}': Session stopped.", ClientId);
|
||||||
}
|
}
|
||||||
@@ -123,7 +112,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -142,31 +131,34 @@ namespace MQTTnet.Server
|
|||||||
var publishPacket = applicationMessage.ToPublishPacket();
|
var publishPacket = applicationMessage.ToPublishPacket();
|
||||||
publishPacket.QualityOfServiceLevel = result.QualityOfServiceLevel;
|
publishPacket.QualityOfServiceLevel = result.QualityOfServiceLevel;
|
||||||
|
|
||||||
|
if (publishPacket.QualityOfServiceLevel > 0)
|
||||||
|
{
|
||||||
|
publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNewPacketIdentifier();
|
||||||
|
}
|
||||||
|
|
||||||
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 +171,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 +184,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -221,7 +212,7 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
if (packet is MqttPingReqPacket)
|
if (packet is MqttPingReqPacket)
|
||||||
{
|
{
|
||||||
return adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new MqttPingRespPacket());
|
return adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { new MqttPingRespPacket() });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (packet is MqttPubRelPacket pubRelPacket)
|
if (packet is MqttPubRelPacket pubRelPacket)
|
||||||
@@ -231,7 +222,12 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
if (packet is MqttPubRecPacket pubRecPacket)
|
if (packet is MqttPubRecPacket pubRecPacket)
|
||||||
{
|
{
|
||||||
return adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, pubRecPacket.CreateResponse<MqttPubRelPacket>());
|
var responsePacket = new MqttPubRelPacket
|
||||||
|
{
|
||||||
|
PacketIdentifier = pubRecPacket.PacketIdentifier
|
||||||
|
};
|
||||||
|
|
||||||
|
return adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { responsePacket });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (packet is MqttPubAckPacket || packet is MqttPubCompPacket)
|
if (packet is MqttPubAckPacket || packet is MqttPubCompPacket)
|
||||||
@@ -262,29 +258,29 @@ namespace MQTTnet.Server
|
|||||||
private async Task HandleIncomingSubscribePacketAsync(IMqttChannelAdapter adapter, MqttSubscribePacket subscribePacket, CancellationToken cancellationToken)
|
private async Task HandleIncomingSubscribePacketAsync(IMqttChannelAdapter adapter, MqttSubscribePacket subscribePacket, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var subscribeResult = await SubscriptionsManager.SubscribeAsync(subscribePacket).ConfigureAwait(false);
|
var subscribeResult = await SubscriptionsManager.SubscribeAsync(subscribePacket).ConfigureAwait(false);
|
||||||
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, subscribeResult.ResponsePacket).ConfigureAwait(false);
|
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { subscribeResult.ResponsePacket }).ConfigureAwait(false);
|
||||||
|
|
||||||
if (subscribeResult.CloseConnection)
|
if (subscribeResult.CloseConnection)
|
||||||
{
|
{
|
||||||
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new MqttDisconnectPacket()).ConfigureAwait(false);
|
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { new MqttDisconnectPacket() }).ConfigureAwait(false);
|
||||||
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)
|
||||||
{
|
{
|
||||||
var unsubscribeResult = await SubscriptionsManager.UnsubscribeAsync(unsubscribePacket).ConfigureAwait(false);
|
var unsubscribeResult = await SubscriptionsManager.UnsubscribeAsync(unsubscribePacket).ConfigureAwait(false);
|
||||||
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, unsubscribeResult);
|
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -318,7 +314,7 @@ namespace MQTTnet.Server
|
|||||||
await ApplicationMessageReceivedCallback(this, applicationMessage).ConfigureAwait(false);
|
await ApplicationMessageReceivedCallback(this, applicationMessage).ConfigureAwait(false);
|
||||||
|
|
||||||
var response = new MqttPubAckPacket { PacketIdentifier = publishPacket.PacketIdentifier };
|
var response = new MqttPubAckPacket { PacketIdentifier = publishPacket.PacketIdentifier };
|
||||||
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, response).ConfigureAwait(false);
|
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { response }).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task HandleIncomingPublishPacketWithQoS2(IMqttChannelAdapter adapter, MqttApplicationMessage applicationMessage, MqttPublishPacket publishPacket, CancellationToken cancellationToken)
|
private async Task HandleIncomingPublishPacketWithQoS2(IMqttChannelAdapter adapter, MqttApplicationMessage applicationMessage, MqttPublishPacket publishPacket, CancellationToken cancellationToken)
|
||||||
@@ -327,50 +323,13 @@ namespace MQTTnet.Server
|
|||||||
await ApplicationMessageReceivedCallback(this, applicationMessage).ConfigureAwait(false);
|
await ApplicationMessageReceivedCallback(this, applicationMessage).ConfigureAwait(false);
|
||||||
|
|
||||||
var response = new MqttPubRecPacket { PacketIdentifier = publishPacket.PacketIdentifier };
|
var response = new MqttPubRecPacket { PacketIdentifier = publishPacket.PacketIdentifier };
|
||||||
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, response).ConfigureAwait(false);
|
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { response }).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task HandleIncomingPubRelPacketAsync(IMqttChannelAdapter adapter, MqttPubRelPacket pubRelPacket, CancellationToken cancellationToken)
|
private Task HandleIncomingPubRelPacketAsync(IMqttChannelAdapter adapter, MqttPubRelPacket pubRelPacket, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
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, new[] { 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,9 +40,11 @@ namespace MQTTnet.Server
|
|||||||
MqttClientSession clientSession = null;
|
MqttClientSession clientSession = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!(await clientAdapter.ReceivePacketAsync(_options.DefaultCommunicationTimeout, cancellationToken).ConfigureAwait(false) is MqttConnectPacket connectPacket))
|
if (!(await clientAdapter.ReceivePacketAsync(_options.DefaultCommunicationTimeout, cancellationToken)
|
||||||
|
.ConfigureAwait(false) is MqttConnectPacket connectPacket))
|
||||||
{
|
{
|
||||||
throw new MqttProtocolViolationException("The first packet from a client must be a 'CONNECT' packet [MQTT-3.1.0-1].");
|
throw new MqttProtocolViolationException(
|
||||||
|
"The first packet from a client must be a 'CONNECT' packet [MQTT-3.1.0-1].");
|
||||||
}
|
}
|
||||||
|
|
||||||
clientId = connectPacket.ClientId;
|
clientId = connectPacket.ClientId;
|
||||||
@@ -53,9 +55,12 @@ namespace MQTTnet.Server
|
|||||||
var connectReturnCode = ValidateConnection(connectPacket);
|
var connectReturnCode = ValidateConnection(connectPacket);
|
||||||
if (connectReturnCode != MqttConnectReturnCode.ConnectionAccepted)
|
if (connectReturnCode != MqttConnectReturnCode.ConnectionAccepted)
|
||||||
{
|
{
|
||||||
await clientAdapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new MqttConnAckPacket
|
await clientAdapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[]
|
||||||
{
|
{
|
||||||
ConnectReturnCode = connectReturnCode
|
new MqttConnAckPacket
|
||||||
|
{
|
||||||
|
ConnectReturnCode = connectReturnCode
|
||||||
|
}
|
||||||
}).ConfigureAwait(false);
|
}).ConfigureAwait(false);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@@ -64,10 +69,13 @@ namespace MQTTnet.Server
|
|||||||
var result = await GetOrCreateClientSessionAsync(connectPacket).ConfigureAwait(false);
|
var result = await GetOrCreateClientSessionAsync(connectPacket).ConfigureAwait(false);
|
||||||
clientSession = result.Session;
|
clientSession = result.Session;
|
||||||
|
|
||||||
await clientAdapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new MqttConnAckPacket
|
await clientAdapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[]
|
||||||
{
|
{
|
||||||
ConnectReturnCode = connectReturnCode,
|
new MqttConnAckPacket
|
||||||
IsSessionPresent = result.IsExistingSession
|
{
|
||||||
|
ConnectReturnCode = connectReturnCode,
|
||||||
|
IsSessionPresent = result.IsExistingSession
|
||||||
|
}
|
||||||
}).ConfigureAwait(false);
|
}).ConfigureAwait(false);
|
||||||
|
|
||||||
ClientConnectedCallback?.Invoke(new ConnectedMqttClient
|
ClientConnectedCallback?.Invoke(new ConnectedMqttClient
|
||||||
@@ -78,6 +86,9 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
await clientSession.RunAsync(connectPacket, clientAdapter).ConfigureAwait(false);
|
await clientSession.RunAsync(connectPacket, clientAdapter).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
_logger.Error<MqttClientSessionsManager>(exception, exception.Message);
|
_logger.Error<MqttClientSessionsManager>(exception, exception.Message);
|
||||||
@@ -87,10 +98,11 @@ namespace MQTTnet.Server
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
await clientAdapter.DisconnectAsync(_options.DefaultCommunicationTimeout).ConfigureAwait(false);
|
await clientAdapter.DisconnectAsync(_options.DefaultCommunicationTimeout).ConfigureAwait(false);
|
||||||
|
clientAdapter.Dispose();
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
// ignored
|
_logger.Error<MqttClientSessionsManager>(exception, exception.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
ClientDisconnectedCallback?.Invoke(new ConnectedMqttClient
|
ClientDisconnectedCallback?.Invoke(new ConnectedMqttClient
|
||||||
@@ -129,8 +141,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 +232,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,11 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
var result = new MqttClientSubscribeResult
|
var result = new MqttClientSubscribeResult
|
||||||
{
|
{
|
||||||
ResponsePacket = subscribePacket.CreateResponse<MqttSubAckPacket>(),
|
ResponsePacket = new MqttSubAckPacket
|
||||||
|
{
|
||||||
|
PacketIdentifier = subscribePacket.PacketIdentifier
|
||||||
|
},
|
||||||
|
|
||||||
CloseConnection = false
|
CloseConnection = false
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -87,7 +91,10 @@ namespace MQTTnet.Server
|
|||||||
_semaphore.Release();
|
_semaphore.Release();
|
||||||
}
|
}
|
||||||
|
|
||||||
return unsubscribePacket.CreateResponse<MqttUnsubAckPacket>();
|
return new MqttUnsubAckPacket
|
||||||
|
{
|
||||||
|
PacketIdentifier = unsubscribePacket.PacketIdentifier
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<CheckSubscriptionsResult> CheckSubscriptionsAsync(MqttApplicationMessage applicationMessage)
|
public async Task<CheckSubscriptionsResult> CheckSubscriptionsAsync(MqttApplicationMessage 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.");
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -156,7 +156,7 @@ namespace MQTTnet.Core.Tests
|
|||||||
var message = new MqttApplicationMessageBuilder().WithTopic("a").WithAtLeastOnceQoS().Build();
|
var message = new MqttApplicationMessageBuilder().WithTopic("a").WithAtLeastOnceQoS().Build();
|
||||||
await c1.SubscribeAsync(new TopicFilter("a", MqttQualityOfServiceLevel.AtLeastOnce));
|
await c1.SubscribeAsync(new TopicFilter("a", MqttQualityOfServiceLevel.AtLeastOnce));
|
||||||
|
|
||||||
s.PublishAsync(message).Wait();
|
await s.PublishAsync(message);
|
||||||
await Task.Delay(500);
|
await Task.Delay(500);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
|
|||||||
@@ -17,6 +17,10 @@ namespace MQTTnet.Core.Tests
|
|||||||
|
|
||||||
public IMqttPacketSerializer PacketSerializer { get; } = new MqttPacketSerializer();
|
public IMqttPacketSerializer PacketSerializer { get; } = new MqttPacketSerializer();
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public Task ConnectAsync(TimeSpan timeout)
|
public Task ConnectAsync(TimeSpan timeout)
|
||||||
{
|
{
|
||||||
return Task.FromResult(0);
|
return Task.FromResult(0);
|
||||||
@@ -43,7 +47,17 @@ namespace MQTTnet.Core.Tests
|
|||||||
{
|
{
|
||||||
ThrowIfPartnerIsNull();
|
ThrowIfPartnerIsNull();
|
||||||
|
|
||||||
return Task.Run(() => _incomingPackets.Take(), cancellationToken);
|
return Task.Run(() =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return _incomingPackets.Take(cancellationToken);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void EnqueuePacketInternal(MqttBasePacket packet)
|
private void EnqueuePacketInternal(MqttBasePacket packet)
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ namespace MQTTnet.TestApp.NetCore
|
|||||||
c.ApplicationMessage.Payload = Encoding.UTF8.GetBytes(content.ToString());
|
c.ApplicationMessage.Payload = Encoding.UTF8.GetBytes(content.ToString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
<DefaultLanguage>en-US</DefaultLanguage>
|
<DefaultLanguage>en-US</DefaultLanguage>
|
||||||
<TargetPlatformIdentifier>UAP</TargetPlatformIdentifier>
|
<TargetPlatformIdentifier>UAP</TargetPlatformIdentifier>
|
||||||
<TargetPlatformVersion Condition=" '$(TargetPlatformVersion)' == '' ">10.0.16299.0</TargetPlatformVersion>
|
<TargetPlatformVersion Condition=" '$(TargetPlatformVersion)' == '' ">10.0.16299.0</TargetPlatformVersion>
|
||||||
<TargetPlatformMinVersion>10.0.15063.0</TargetPlatformMinVersion>
|
<TargetPlatformMinVersion>10.0.16299.0</TargetPlatformMinVersion>
|
||||||
<MinimumVisualStudioVersion>14</MinimumVisualStudioVersion>
|
<MinimumVisualStudioVersion>14</MinimumVisualStudioVersion>
|
||||||
<FileAlignment>512</FileAlignment>
|
<FileAlignment>512</FileAlignment>
|
||||||
<ProjectTypeGuids>{A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
<ProjectTypeGuids>{A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
||||||
@@ -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)' < '14.0' ">
|
<PropertyGroup Condition=" '$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' < '14.0' ">
|
||||||
|
|||||||
@@ -148,7 +148,7 @@
|
|||||||
</PivotItem>
|
</PivotItem>
|
||||||
<PivotItem Header="Log">
|
<PivotItem Header="Log">
|
||||||
<StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
<StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||||
<Button Click="Clear" Width="120">Clear</Button>
|
<Button Click="ClearLog" Width="120">Clear</Button>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</PivotItem>
|
</PivotItem>
|
||||||
</Pivot>
|
</Pivot>
|
||||||
|
|||||||
@@ -42,7 +42,8 @@ namespace MQTTnet.TestApp.UniversalWindows
|
|||||||
foreach (var traceMessage in _traceMessages)
|
foreach (var traceMessage in _traceMessages)
|
||||||
{
|
{
|
||||||
logText.AppendFormat(
|
logText.AppendFormat(
|
||||||
"[{0:yyyy-MM-dd HH:mm:ss.fff}] [{1}] [{2}] [{3}] [{4}]{5}", traceMessage.Timestamp,
|
"[{0:yyyy-MM-dd HH:mm:ss.fff}] [{1}] [{2}] [{3}] [{4}]{5}",
|
||||||
|
traceMessage.Timestamp,
|
||||||
traceMessage.Level,
|
traceMessage.Level,
|
||||||
traceMessage.Source,
|
traceMessage.Source,
|
||||||
traceMessage.ThreadId,
|
traceMessage.ThreadId,
|
||||||
@@ -197,8 +198,13 @@ namespace MQTTnet.TestApp.UniversalWindows
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Clear(object sender, RoutedEventArgs e)
|
private void ClearLog(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
|
while (_traceMessages.Count > 0)
|
||||||
|
{
|
||||||
|
_traceMessages.TryDequeue(out _);
|
||||||
|
}
|
||||||
|
|
||||||
Trace.Text = string.Empty;
|
Trace.Text = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user