Merge pull request #1 from chkr1011/develop

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