@@ -13,11 +13,9 @@ | |||
<releaseNotes>* Updated to MQTTnet 2.8.0. | |||
</releaseNotes> | |||
<copyright>Copyright Christian Kratky 2016-2018</copyright> | |||
<tags>MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin</tags> | |||
<tags>MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin</tags> | |||
<dependencies> | |||
<group targetFramework="netstandard2.0"> | |||
<dependency id="MQTTnet" version="2.8.0-alpha5" /> | |||
</group> | |||
<dependency id="MQTTnet" version="2.8.0-alpha5" /> | |||
</dependencies> | |||
</metadata> | |||
@@ -15,24 +15,7 @@ | |||
<copyright>Copyright Christian Kratky 2016-2018</copyright> | |||
<tags>MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin</tags> | |||
<dependencies> | |||
<group targetFramework="netstandard2.0"> | |||
<dependency id="MQTTnet" version="2.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> | |||
<dependency id="MQTTnet" version="2.8.0-beta1" /> | |||
</dependencies> | |||
</metadata> | |||
@@ -48,6 +31,8 @@ | |||
<!-- .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\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> | |||
</package> |
@@ -15,24 +15,7 @@ | |||
<copyright>Copyright Christian Kratky 2016-2018</copyright> | |||
<tags>MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin</tags> | |||
<dependencies> | |||
<group targetFramework="netstandard2.0"> | |||
<dependency id="MQTTnet" version="2.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> | |||
<dependency id="MQTTnet" version="2.8.0-beta1" /> | |||
</dependencies> | |||
</metadata> | |||
@@ -48,6 +31,8 @@ | |||
<!-- .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\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> | |||
</package> |
@@ -48,7 +48,7 @@ | |||
<dependency id="System.Net.WebSockets.Client" version="4.3.2" /> | |||
</group> | |||
<group targetFramework="uap10.0"> | |||
<dependency id="Microsoft.NETCore.UniversalWindowsPlatform" version="5.4.1" /> | |||
<dependency id="Microsoft.NETCore.UniversalWindowsPlatform" version="6.1.4" /> | |||
</group> | |||
<group targetFramework="net452"> | |||
</group> | |||
@@ -69,6 +69,8 @@ | |||
<!-- .NET Framework --> | |||
<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> | |||
</package> |
@@ -36,8 +36,8 @@ if ($path) { | |||
Remove-Item .\NuGet -Force -Recurse -ErrorAction SilentlyContinue | |||
New-Item -ItemType Directory -Force -Path .\NuGet | |||
.\NuGet.exe pack MQTTnet.nuspec -Verbosity detailed -Symbols -OutputDir "NuGet" -Version $nugetVersion | |||
.\NuGet.exe pack MQTTnet.AspNetCore.nuspec -Verbosity detailed -Symbols -OutputDir "NuGet" -Version $nugetVersion | |||
.\NuGet.exe pack MQTTnet.Extensions.Rpc.nuspec -Verbosity detailed -Symbols -OutputDir "NuGet" -Version $nugetVersion | |||
.\NuGet.exe pack MQTTnet.Extensions.ManagedClient.nuspec -Verbosity detailed -Symbols -OutputDir "NuGet" -Version $nugetVersion | |||
.\nuget.exe pack MQTTnet.nuspec -Verbosity detailed -Symbols -OutputDir "NuGet" -Version $nugetVersion | |||
.\nuget.exe pack MQTTnet.AspNetCore.nuspec -Verbosity detailed -Symbols -OutputDir "NuGet" -Version $nugetVersion | |||
.\nuget.exe pack MQTTnet.Extensions.Rpc.nuspec -Verbosity detailed -Symbols -OutputDir "NuGet" -Version $nugetVersion | |||
.\nuget.exe pack MQTTnet.Extensions.ManagedClient.nuspec -Verbosity detailed -Symbols -OutputDir "NuGet" -Version $nugetVersion | |||
} |
@@ -0,0 +1,9 @@ | |||
param([string]$apiKey) | |||
$files = Get-ChildItem -Path ".\NuGet" -Filter "*.nupkg" | |||
foreach ($file in $files) | |||
{ | |||
Write-Host "Uploading: " $file | |||
.\nuget.exe push $file.Fullname $apiKey -NoSymbols -Source https://api.nuget.org/v3/index.json | |||
} |
@@ -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.Rpc.nuspec = Build\MQTTnet.Extensions.Rpc.nuspec | |||
Build\MQTTnet.nuspec = Build\MQTTnet.nuspec | |||
Build\upload.ps1 = Build\upload.ps1 | |||
EndProjectSection | |||
EndProject | |||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{B3F60ECB-45BA-4C66-8903-8BB89CA67998}" | |||
@@ -74,6 +75,7 @@ Global | |||
{A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Release|x86.Build.0 = Release|Any CPU | |||
{FF1F72D6-9524-4422-9497-3CC0002216ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||
{FF1F72D6-9524-4422-9497-3CC0002216ED}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||
{FF1F72D6-9524-4422-9497-3CC0002216ED}.Debug|Any CPU.Deploy.0 = Debug|Any CPU | |||
{FF1F72D6-9524-4422-9497-3CC0002216ED}.Debug|ARM.ActiveCfg = Debug|ARM | |||
{FF1F72D6-9524-4422-9497-3CC0002216ED}.Debug|ARM.Build.0 = Debug|ARM | |||
{FF1F72D6-9524-4422-9497-3CC0002216ED}.Debug|ARM.Deploy.0 = Debug|ARM | |||
@@ -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) | |||
* Extensible communication channels (i.e. In-Memory, TCP, TCP+TLS, WS) | |||
* 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 | |||
* Access to internal trace messages | |||
* 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 Core 1.1+ | |||
* .NET Core App 1.1+ | |||
* Universal Windows Platform (UWP) 10.0.10240+ (x86, x64, ARM, AnyCPU, Windows 10 IoT Core) | |||
* .NET Framework 4.5.2+ (x86, x64, AnyCPU) | |||
* Mono 5.2+ | |||
* Universal Windows Platform (UWP) 10.0.10240+ (x86, x64, ARM, AnyCPU, Windows 10 IoT Core) | |||
* Xamarin.Android 7.5+ | |||
* Xamarin.iOS 10.14+ | |||
## Supported MQTT versions | |||
* 5.0.0 (planned) | |||
* 3.1.1 | |||
* 3.1.0 | |||
@@ -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 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. | |||
@@ -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.Runtime.InteropServices; | |||
namespace MQTTnet.Benchmarks.Tcp | |||
namespace MQTTnet.AspNetCore.Client.Tcp | |||
{ | |||
public static class BufferExtensions | |||
{ |
@@ -1,6 +1,6 @@ | |||
using System.IO.Pipelines; | |||
namespace MQTTnet.Benchmarks.Tcp | |||
namespace MQTTnet.AspNetCore.Client.Tcp | |||
{ | |||
public class DuplexPipe : IDuplexPipe | |||
{ |
@@ -6,7 +6,7 @@ using System.Runtime.CompilerServices; | |||
using System.Threading; | |||
using System.Threading.Tasks; | |||
namespace MQTTnet.Benchmarks.Tcp | |||
namespace MQTTnet.AspNetCore.Client.Tcp | |||
{ | |||
public class SocketAwaitable : ICriticalNotifyCompletion | |||
{ | |||
@@ -23,9 +23,10 @@ namespace MQTTnet.Benchmarks.Tcp | |||
_ioScheduler = ioScheduler; | |||
} | |||
public SocketAwaitable GetAwaiter() => this; | |||
public bool IsCompleted => ReferenceEquals(_callback, _callbackCompleted); | |||
public SocketAwaitable GetAwaiter() => this; | |||
public int GetResult() | |||
{ | |||
Debug.Assert(ReferenceEquals(_callback, _callbackCompleted)); |
@@ -2,7 +2,7 @@ | |||
using System.IO.Pipelines; | |||
using System.Net.Sockets; | |||
namespace MQTTnet.Benchmarks.Tcp | |||
namespace MQTTnet.AspNetCore.Client.Tcp | |||
{ | |||
public class SocketReceiver | |||
{ | |||
@@ -24,7 +24,6 @@ namespace MQTTnet.Benchmarks.Tcp | |||
_eventArgs.SetBuffer(buffer); | |||
#else | |||
var segment = buffer.GetArray(); | |||
_eventArgs.SetBuffer(segment.Array, segment.Offset, segment.Count); | |||
#endif | |||
if (!_socket.ReceiveAsync(_eventArgs)) |
@@ -5,7 +5,7 @@ using System.Diagnostics; | |||
using System.IO.Pipelines; | |||
using System.Net.Sockets; | |||
namespace MQTTnet.Benchmarks.Tcp | |||
namespace MQTTnet.AspNetCore.Client.Tcp | |||
{ | |||
public class SocketSender | |||
{ | |||
@@ -61,7 +61,6 @@ namespace MQTTnet.Benchmarks.Tcp | |||
_eventArgs.SetBuffer(MemoryMarshal.AsMemory(memory)); | |||
#else | |||
var segment = memory.GetArray(); | |||
_eventArgs.SetBuffer(segment.Array, segment.Offset, segment.Count); | |||
#endif | |||
if (!_socket.SendAsync(_eventArgs)) |
@@ -1,30 +1,35 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.IO; | |||
using System.IO.Pipelines; | |||
using System.Net; | |||
using System.Net.Sockets; | |||
using System.Threading.Tasks; | |||
using Microsoft.AspNetCore.Connections; | |||
using Microsoft.AspNetCore.Http.Features; | |||
using MQTTnet.Exceptions; | |||
namespace MQTTnet.Benchmarks.Tcp | |||
namespace MQTTnet.AspNetCore.Client.Tcp | |||
{ | |||
public class TcpConnection | |||
public class TcpConnection : ConnectionContext | |||
{ | |||
private readonly Socket _socket; | |||
private volatile bool _aborted; | |||
private readonly EndPoint _endPoint; | |||
private SocketSender _sender; | |||
private SocketReceiver _receiver; | |||
private Socket _socket; | |||
private IDuplexPipe _application; | |||
private IDuplexPipe _transport; | |||
private readonly SocketSender _sender; | |||
private readonly SocketReceiver _receiver; | |||
public bool IsConnected { get; private set; } | |||
public override string ConnectionId { get; set; } | |||
public override IFeatureCollection Features { get; } | |||
public override IDictionary<object, object> Items { get; set; } | |||
public override IDuplexPipe Transport { get; set; } | |||
public TcpConnection(EndPoint endPoint) | |||
{ | |||
_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); | |||
_endPoint = endPoint; | |||
_sender = new SocketSender(_socket, PipeScheduler.ThreadPool); | |||
_receiver = new SocketReceiver(_socket, PipeScheduler.ThreadPool); | |||
} | |||
public TcpConnection(Socket socket) | |||
@@ -38,29 +43,34 @@ namespace MQTTnet.Benchmarks.Tcp | |||
public Task DisposeAsync() | |||
{ | |||
_transport?.Output.Complete(); | |||
_transport?.Input.Complete(); | |||
IsConnected = false; | |||
Transport?.Output.Complete(); | |||
Transport?.Input.Complete(); | |||
_socket?.Dispose(); | |||
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); | |||
} | |||
var pair = DuplexPipe.CreateConnectionPair(PipeOptions.Default, PipeOptions.Default); | |||
_transport = pair.Transport; | |||
Transport = pair.Transport; | |||
_application = pair.Application; | |||
_ = ExecuteAsync(); | |||
return pair.Transport; | |||
IsConnected = true; | |||
} | |||
private async Task ExecuteAsync() | |||
@@ -118,14 +128,14 @@ namespace MQTTnet.Benchmarks.Tcp | |||
if (!_aborted) | |||
{ | |||
// Calling Dispose after ReceiveAsync can cause an "InvalidArgument" error on *nix. | |||
//error = new MqttCommunicationException(); | |||
error = ConnectionAborted(); | |||
} | |||
} | |||
catch (ObjectDisposedException) | |||
{ | |||
if (!_aborted) | |||
{ | |||
//error = new MqttCommunicationException(); | |||
error = ConnectionAborted(); | |||
} | |||
} | |||
catch (IOException ex) | |||
@@ -140,7 +150,7 @@ namespace MQTTnet.Benchmarks.Tcp | |||
{ | |||
if (_aborted) | |||
{ | |||
//error = error ?? new MqttCommunicationException(); | |||
error = error ?? ConnectionAborted(); | |||
} | |||
_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() | |||
{ | |||
Exception error = null; | |||
@@ -190,11 +205,9 @@ namespace MQTTnet.Benchmarks.Tcp | |||
} | |||
catch (SocketException ex) when (ex.SocketErrorCode == SocketError.OperationAborted) | |||
{ | |||
error = null; | |||
} | |||
catch (ObjectDisposedException) | |||
{ | |||
error = null; | |||
} | |||
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 /> | |||
<Authors /> | |||
<PackageId /> | |||
<LangVersion>7.2</LangVersion> | |||
</PropertyGroup> | |||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'"> | |||
@@ -16,7 +17,7 @@ | |||
</PropertyGroup> | |||
<ItemGroup> | |||
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.0.1" /> | |||
<PackageReference Include="Microsoft.AspNetCore.Connections.Abstractions" Version="2.1.0" /> | |||
<PackageReference Include="Microsoft.AspNetCore.WebSockets" Version="2.0.1" /> | |||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="2.0.1" /> | |||
</ItemGroup> | |||
@@ -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; | |||
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)); | |||
} | |||
@@ -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) | |||
{ | |||
services.AddSingleton<MqttTcpServerAdapter>(); | |||
services.AddSingleton<IMqttServerAdapter>(s => s.GetService<MqttTcpServerAdapter>()); | |||
} | |||
return services; | |||
} | |||
public static IServiceCollection AddMqttConnectionHandler(this IServiceCollection services) | |||
{ | |||
services.AddSingleton<MqttConnectionHandler>(); | |||
services.AddSingleton<IMqttServerAdapter>(s => s.GetService<MqttConnectionHandler>()); | |||
return services; | |||
} | |||
} | |||
} |
@@ -7,7 +7,6 @@ using System.Threading.Tasks; | |||
using MQTTnet.Client; | |||
using MQTTnet.Diagnostics; | |||
using MQTTnet.Exceptions; | |||
using MQTTnet.Internal; | |||
using MQTTnet.Protocol; | |||
namespace MQTTnet.Extensions.ManagedClient | |||
@@ -15,8 +14,7 @@ namespace MQTTnet.Extensions.ManagedClient | |||
public class ManagedMqttClient : IManagedMqttClient | |||
{ | |||
private readonly BlockingCollection<ManagedMqttApplicationMessage> _messageQueue = new BlockingCollection<ManagedMqttApplicationMessage>(); | |||
private readonly Dictionary<string, MqttQualityOfServiceLevel> _subscriptions = new Dictionary<string, MqttQualityOfServiceLevel>(); | |||
private readonly AsyncLock _subscriptionsLock = new AsyncLock(); | |||
private readonly ConcurrentDictionary<string, MqttQualityOfServiceLevel> _subscriptions = new ConcurrentDictionary<string, MqttQualityOfServiceLevel>(); | |||
private readonly List<string> _unsubscriptions = new List<string>(); | |||
private readonly IMqttClient _mqttClient; | |||
@@ -118,39 +116,36 @@ namespace MQTTnet.Extensions.ManagedClient | |||
_messageQueue.Add(applicationMessage); | |||
} | |||
public async Task SubscribeAsync(IEnumerable<TopicFilter> topicFilters) | |||
public Task SubscribeAsync(IEnumerable<TopicFilter> 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; | |||
} | |||
_subscriptions[topicFilter.Topic] = topicFilter.QualityOfServiceLevel; | |||
_subscriptionsNotPushed = true; | |||
} | |||
return Task.FromResult(0); | |||
} | |||
public async Task UnsubscribeAsync(IEnumerable<string> topics) | |||
public Task UnsubscribeAsync(IEnumerable<string> topics) | |||
{ | |||
using (await _subscriptionsLock.LockAsync(CancellationToken.None).ConfigureAwait(false)) | |||
foreach (var topic in topics) | |||
{ | |||
foreach (var topic in topics) | |||
if (_subscriptions.TryRemove(topic, out _)) | |||
{ | |||
if (_subscriptions.Remove(topic)) | |||
{ | |||
_unsubscriptions.Add(topic); | |||
_subscriptionsNotPushed = true; | |||
} | |||
_unsubscriptions.Add(topic); | |||
_subscriptionsNotPushed = true; | |||
} | |||
} | |||
return Task.FromResult(0); | |||
} | |||
public void Dispose() | |||
{ | |||
_messageQueue?.Dispose(); | |||
_subscriptionsLock?.Dispose(); | |||
_connectionCancellationToken?.Dispose(); | |||
_publishingCancellationToken?.Dispose(); | |||
} | |||
@@ -289,7 +284,7 @@ namespace MQTTnet.Extensions.ManagedClient | |||
List<TopicFilter> subscriptions; | |||
List<string> unsubscriptions; | |||
using (await _subscriptionsLock.LockAsync(CancellationToken.None).ConfigureAwait(false)) | |||
lock (_subscriptions) | |||
{ | |||
subscriptions = _subscriptions.Select(i => new TopicFilter(i.Key, i.Value)).ToList(); | |||
@@ -82,11 +82,11 @@ namespace MQTTnet.Extensions.Rpc | |||
timeoutCts.Cancel(false); | |||
return result; | |||
} | |||
catch (TaskCanceledException taskCanceledException) | |||
catch (OperationCanceledException exception) | |||
{ | |||
if (timeoutCts.IsCancellationRequested && !cancellationToken.IsCancellationRequested) | |||
{ | |||
throw new MqttCommunicationTimedOutException(taskCanceledException); | |||
throw new MqttCommunicationTimedOutException(exception); | |||
} | |||
else | |||
{ | |||
@@ -20,7 +20,7 @@ namespace MQTTnet.Adapter | |||
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); | |||
} | |||
@@ -18,6 +18,8 @@ namespace MQTTnet.Adapter | |||
private const uint ErrorOperationAborted = 0x800703E3; | |||
private const int ReadBufferSize = 4096; // TODO: Move buffer size to config | |||
private readonly SemaphoreSlim _writerSemaphore = new SemaphoreSlim(1, 1); | |||
private readonly IMqttNetChildLogger _logger; | |||
private readonly IMqttChannel _channel; | |||
@@ -40,52 +42,91 @@ namespace MQTTnet.Adapter | |||
public event EventHandler ReadingPacketStarted; | |||
public event EventHandler ReadingPacketCompleted; | |||
public Task ConnectAsync(TimeSpan timeout, CancellationToken cancellationToken) | |||
public async Task ConnectAsync(TimeSpan timeout, CancellationToken cancellationToken) | |||
{ | |||
ThrowIfDisposed(); | |||
_logger.Verbose("Connecting [Timeout={0}]", timeout); | |||
return ExecuteAndWrapExceptionAsync(() => | |||
Internal.TaskExtensions.TimeoutAfter(ct => _channel.ConnectAsync(ct), timeout, cancellationToken)); | |||
try | |||
{ | |||
_logger.Verbose("Connecting [Timeout={0}]", timeout); | |||
await Internal.TaskExtensions | |||
.TimeoutAfterAsync(ct => _channel.ConnectAsync(ct), timeout, cancellationToken) | |||
.ConfigureAwait(false); | |||
} | |||
catch (Exception exception) | |||
{ | |||
if (IsWrappedException(exception)) | |||
{ | |||
throw; | |||
} | |||
WrapException(exception); | |||
} | |||
} | |||
public Task DisconnectAsync(TimeSpan timeout, CancellationToken cancellationToken) | |||
public async Task DisconnectAsync(TimeSpan timeout, CancellationToken cancellationToken) | |||
{ | |||
ThrowIfDisposed(); | |||
_logger.Verbose("Disconnecting [Timeout={0}]", timeout); | |||
return ExecuteAndWrapExceptionAsync(() => | |||
Internal.TaskExtensions.TimeoutAfter(ct => _channel.DisconnectAsync(), timeout, cancellationToken)); | |||
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 Task SendPacketAsync(TimeSpan timeout, MqttBasePacket packet, CancellationToken cancellationToken) | |||
public async Task SendPacketAsync(MqttBasePacket packet, CancellationToken cancellationToken) | |||
{ | |||
return ExecuteAndWrapExceptionAsync(() => | |||
await _writerSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); | |||
try | |||
{ | |||
_logger.Verbose("TX >>> {0} [Timeout={1}]", packet, timeout); | |||
_logger.Verbose("TX >>> {0}", packet); | |||
var packetData = PacketSerializer.Serialize(packet); | |||
return Internal.TaskExtensions.TimeoutAfter(ct => _channel.WriteAsync( | |||
packetData.Array, | |||
packetData.Offset, | |||
packetData.Count, | |||
ct), timeout, cancellationToken); | |||
}); | |||
await _channel.WriteAsync(packetData.Array, packetData.Offset, packetData.Count, cancellationToken).ConfigureAwait(false); | |||
PacketSerializer.FreeBuffer(); | |||
} | |||
catch (Exception exception) | |||
{ | |||
if (IsWrappedException(exception)) | |||
{ | |||
throw; | |||
} | |||
WrapException(exception); | |||
} | |||
finally | |||
{ | |||
_writerSemaphore.Release(); | |||
} | |||
} | |||
public async Task<MqttBasePacket> ReceivePacketAsync(TimeSpan timeout, CancellationToken cancellationToken) | |||
{ | |||
ThrowIfDisposed(); | |||
MqttBasePacket packet = null; | |||
await ExecuteAndWrapExceptionAsync(async () => | |||
try | |||
{ | |||
ReceivedMqttPacket receivedMqttPacket; | |||
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 | |||
{ | |||
@@ -94,28 +135,35 @@ namespace MQTTnet.Adapter | |||
if (receivedMqttPacket == null || cancellationToken.IsCancellationRequested) | |||
{ | |||
return; | |||
return null; | |||
} | |||
packet = PacketSerializer.Deserialize(receivedMqttPacket); | |||
var packet = PacketSerializer.Deserialize(receivedMqttPacket); | |||
if (packet == null) | |||
{ | |||
throw new MqttProtocolViolationException("Received malformed 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) | |||
{ | |||
var fixedHeader = await MqttPacketReader.ReadFixedHeaderAsync(channel, cancellationToken).ConfigureAwait(false); | |||
if (fixedHeader == null) | |||
{ | |||
return null; | |||
} | |||
try | |||
{ | |||
@@ -138,7 +186,9 @@ namespace MQTTnet.Adapter | |||
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) | |||
{ | |||
ExceptionHelper.ThrowGracefulSocketClose(); | |||
@@ -147,7 +197,7 @@ namespace MQTTnet.Adapter | |||
bodyOffset += readBytes; | |||
} while (bodyOffset < body.Length); | |||
return new ReceivedMqttPacket(fixedHeader.Flags, new MqttPacketBodyReader(body)); | |||
return new ReceivedMqttPacket(fixedHeader.Flags, new MqttPacketBodyReader(body, 0)); | |||
} | |||
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() | |||
{ | |||
_isDisposed = true; | |||
@@ -205,5 +219,34 @@ namespace MQTTnet.Adapter | |||
throw new ObjectDisposedException(nameof(MqttChannelAdapter)); | |||
} | |||
} | |||
private static bool IsWrappedException(Exception exception) | |||
{ | |||
return exception is TaskCanceledException || | |||
exception is OperationCanceledException || | |||
exception is MqttCommunicationTimedOutException || | |||
exception is MqttCommunicationException; | |||
} | |||
private static void WrapException(Exception exception) | |||
{ | |||
if (exception is IOException && exception.InnerException is SocketException socketException) | |||
{ | |||
if (socketException.SocketErrorCode == SocketError.ConnectionAborted) | |||
{ | |||
throw new OperationCanceledException(); | |||
} | |||
} | |||
if (exception is COMException comException) | |||
{ | |||
if ((uint)comException.HResult == ErrorOperationAborted) | |||
{ | |||
throw new OperationCanceledException(); | |||
} | |||
} | |||
throw new MqttCommunicationException(exception); | |||
} | |||
} | |||
} |
@@ -17,7 +17,7 @@ namespace MQTTnet.Client | |||
{ | |||
private readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider(); | |||
private readonly 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 IMqttClientAdapterFactory _adapterFactory; | |||
@@ -215,7 +215,7 @@ namespace MQTTnet.Client | |||
private async Task DisconnectInternalAsync(Task sender, Exception exception) | |||
{ | |||
await InitiateDisconnectAsync().ConfigureAwait(false); | |||
InitiateDisconnect(); | |||
var clientWasConnected = IsConnected; | |||
IsConnected = false; | |||
@@ -249,45 +249,38 @@ namespace MQTTnet.Client | |||
} | |||
} | |||
private async Task InitiateDisconnectAsync() | |||
private void InitiateDisconnect() | |||
{ | |||
await _disconnectLock.WaitAsync().ConfigureAwait(false); | |||
try | |||
lock (_disconnectLock) | |||
{ | |||
if (_cancellationTokenSource == null || _cancellationTokenSource.IsCancellationRequested) | |||
try | |||
{ | |||
return; | |||
} | |||
if (_cancellationTokenSource == null || _cancellationTokenSource.IsCancellationRequested) | |||
{ | |||
return; | |||
} | |||
_cancellationTokenSource.Cancel(false); | |||
} | |||
catch (Exception adapterException) | |||
{ | |||
_logger.Warning(adapterException, "Error while initiating disconnect."); | |||
} | |||
finally | |||
{ | |||
_disconnectLock.Release(); | |||
_cancellationTokenSource.Cancel(false); | |||
} | |||
catch (Exception adapterException) | |||
{ | |||
_logger.Warning(adapterException, "Error while initiating disconnect."); | |||
} | |||
} | |||
} | |||
private Task SendAsync(MqttBasePacket packet, CancellationToken cancellationToken) | |||
{ | |||
if (cancellationToken.IsCancellationRequested) | |||
{ | |||
throw new TaskCanceledException(); | |||
} | |||
cancellationToken.ThrowIfCancellationRequested(); | |||
_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 | |||
{ | |||
if (cancellationToken.IsCancellationRequested) | |||
{ | |||
throw new TaskCanceledException(); | |||
} | |||
cancellationToken.ThrowIfCancellationRequested(); | |||
_sendTracker.Restart(); | |||
@@ -300,8 +293,8 @@ namespace MQTTnet.Client | |||
var packetAwaiter = _packetDispatcher.AddPacketAwaiter<TResponsePacket>(identifier); | |||
try | |||
{ | |||
await _adapter.SendPacketAsync(_options.CommunicationTimeout, requestPacket, cancellationToken).ConfigureAwait(false); | |||
var respone = await Internal.TaskExtensions.TimeoutAfter(ct => packetAwaiter.Task, _options.CommunicationTimeout, cancellationToken).ConfigureAwait(false); | |||
await _adapter.SendPacketAsync(requestPacket, cancellationToken).ConfigureAwait(false); | |||
var respone = await Internal.TaskExtensions.TimeoutAfterAsync(ct => packetAwaiter.Task, _options.CommunicationTimeout, cancellationToken).ConfigureAwait(false); | |||
return (TResponsePacket)respone; | |||
} | |||
@@ -526,7 +519,7 @@ namespace MQTTnet.Client | |||
{ | |||
await task.ConfigureAwait(false); | |||
} | |||
catch (TaskCanceledException) | |||
catch (OperationCanceledException) | |||
{ | |||
} | |||
} | |||
@@ -9,7 +9,7 @@ namespace MQTTnet.Diagnostics | |||
public MqttNetChildLogger(IMqttNetLogger logger, string source) | |||
{ | |||
_logger = logger; | |||
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); | |||
_source = source; | |||
} | |||
@@ -10,6 +10,8 @@ | |||
return "net452"; | |||
#elif NET461 | |||
return "net461"; | |||
#elif NET472 | |||
return "net472"; | |||
#elif NETSTANDARD1_3 | |||
return "netstandard1.3"; | |||
#elif NETSTANDARD2_0 | |||
@@ -40,7 +40,18 @@ namespace MQTTnet.Implementations | |||
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) | |||
{ | |||
@@ -81,10 +92,13 @@ namespace MQTTnet.Implementations | |||
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); | |||
await _writeStream.FlushAsync(cancellationToken); | |||
// In the write method only the internal buffer will be filled. So here is no | |||
// 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() | |||
@@ -1,4 +1,4 @@ | |||
#if NET452 || NET461 || NETSTANDARD1_3 || NETSTANDARD2_0 | |||
#if !WINDOWS_UWP | |||
using System; | |||
using System.Net.Security; | |||
using System.Net.Sockets; | |||
@@ -1,4 +1,4 @@ | |||
#if NET452 || NET461 || NETSTANDARD1_3 || NETSTANDARD2_0 | |||
#if !WINDOWS_UWP | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Net.Sockets; | |||
@@ -1,4 +1,4 @@ | |||
#if NET452 || NET461 || NETSTANDARD1_3 || NETSTANDARD2_0 | |||
#if !WINDOWS_UWP | |||
using System; | |||
using System.Net; | |||
using System.Net.Security; | |||
@@ -78,7 +78,8 @@ namespace MQTTnet.Implementations | |||
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); | |||
ClientAccepted?.Invoke(this, new MqttServerAdapterClientAcceptedEventArgs(clientAdapter)); | |||
@@ -104,7 +105,7 @@ namespace MQTTnet.Implementations | |||
{ | |||
_socket?.Dispose(); | |||
#if NETSTANDARD1_3 || NETSTANDARD2_0 || NET461 | |||
#if NETSTANDARD1_3 || NETSTANDARD2_0 || NET461 || NET472 | |||
_tlsCertificate?.Dispose(); | |||
#endif | |||
} | |||
@@ -11,8 +11,10 @@ namespace MQTTnet.Internal | |||
private readonly LinkedList<TaskCompletionSource<bool>> _waiters = new LinkedList<TaskCompletionSource<bool>>(); | |||
private bool _isSignaled; | |||
public AsyncAutoResetEvent() : this(false) | |||
{ } | |||
public AsyncAutoResetEvent() | |||
: this(false) | |||
{ | |||
} | |||
public AsyncAutoResetEvent(bool signaled) | |||
{ | |||
@@ -58,27 +60,24 @@ namespace MQTTnet.Internal | |||
} | |||
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; | |||
} | |||
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. | |||
// This is an O(n) operation since waiters is a LinkedList<T>. | |||
lock (_waiters) | |||
_waiters.Remove(tcs); | |||
if (winner.Status == TaskStatus.Canceled) | |||
{ | |||
_waiters.Remove(tcs); | |||
if (winner.Status == TaskStatus.Canceled) | |||
{ | |||
throw new OperationCanceledException(cancellationToken); | |||
} | |||
else | |||
{ | |||
throw new TimeoutException(); | |||
} | |||
throw new OperationCanceledException(cancellationToken); | |||
} | |||
throw new TimeoutException(); | |||
} | |||
} | |||
@@ -17,7 +17,7 @@ namespace MQTTnet.Internal | |||
public Task<IDisposable> LockAsync(CancellationToken cancellationToken) | |||
{ | |||
Task wait = _semaphore.WaitAsync(cancellationToken); | |||
var wait = _semaphore.WaitAsync(cancellationToken); | |||
return wait.IsCompleted ? | |||
_releaser : | |||
wait.ContinueWith((_, state) => (IDisposable)state, | |||
@@ -7,7 +7,7 @@ namespace MQTTnet.Internal | |||
{ | |||
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)); | |||
@@ -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)); | |||
@@ -22,6 +22,14 @@ namespace MQTTnet | |||
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() | |||
{ | |||
var logger = new MqttNetLogger(); | |||
@@ -1,48 +0,0 @@ | |||
using System; | |||
namespace MQTTnet.Serializer | |||
{ | |||
public class ByteReader | |||
{ | |||
private readonly int _source; | |||
private int _index; | |||
public ByteReader(int source) | |||
{ | |||
_source = source; | |||
} | |||
public bool Read() | |||
{ | |||
if (_index >= 8) | |||
{ | |||
throw new InvalidOperationException("End of byte reached."); | |||
} | |||
var result = ((1 << _index) & _source) > 0; | |||
_index++; | |||
return result; | |||
} | |||
public int Read(int count) | |||
{ | |||
if (_index + count > 8) | |||
{ | |||
throw new InvalidOperationException("End of byte will be reached."); | |||
} | |||
var result = 0; | |||
for (var i = 0; i < count; i++) | |||
{ | |||
if (((1 << _index) & _source) > 0) | |||
{ | |||
result |= 1 << i; | |||
} | |||
_index++; | |||
} | |||
return result; | |||
} | |||
} | |||
} |
@@ -1,36 +0,0 @@ | |||
using System; | |||
namespace MQTTnet.Serializer | |||
{ | |||
public class ByteWriter | |||
{ | |||
private int _index; | |||
private int _byte; | |||
public byte Value => (byte)_byte; | |||
public void Write(int @byte, int count) | |||
{ | |||
for (var i = 0; i < count; i++) | |||
{ | |||
var value = ((1 << i) & @byte) > 0; | |||
Write(value); | |||
} | |||
} | |||
public void Write(bool bit) | |||
{ | |||
if (_index >= 8) | |||
{ | |||
throw new InvalidOperationException("End of the byte reached."); | |||
} | |||
if (bit) | |||
{ | |||
_byte |= 1 << _index; | |||
} | |||
_index++; | |||
} | |||
} | |||
} |
@@ -12,7 +12,10 @@ namespace MQTTnet.Serializer | |||
} | |||
var buffer = new byte[source.Count]; | |||
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; | |||
} | |||
@@ -11,5 +11,7 @@ namespace MQTTnet.Serializer | |||
ArraySegment<byte> Serialize(MqttBasePacket mqttPacket); | |||
MqttBasePacket Deserialize(ReceivedMqttPacket receivedMqttPacket); | |||
void FreeBuffer(); | |||
} | |||
} |
@@ -1,6 +1,6 @@ | |||
namespace MQTTnet.Serializer | |||
{ | |||
public class MqttFixedHeader | |||
public struct MqttFixedHeader | |||
{ | |||
public MqttFixedHeader(byte flags, int remainingLength) | |||
{ | |||
@@ -10,6 +10,6 @@ | |||
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 int _offset; | |||
public MqttPacketBodyReader(byte[] buffer) | |||
public MqttPacketBodyReader(byte[] buffer, int offset) | |||
{ | |||
_buffer = buffer; | |||
_offset = offset; | |||
} | |||
public int Length => _buffer.Length - _offset; | |||
@@ -1,4 +1,5 @@ | |||
using System.Threading; | |||
using System; | |||
using System.Threading; | |||
using System.Threading.Tasks; | |||
using MQTTnet.Channel; | |||
using MQTTnet.Exceptions; | |||
@@ -8,23 +9,27 @@ namespace MQTTnet.Serializer | |||
{ | |||
public static class MqttPacketReader | |||
{ | |||
[ThreadStatic] | |||
private static byte[] _fixedHeaderBuffer; | |||
[ThreadStatic] | |||
private static byte[] _singleByteBuffer; | |||
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. | |||
// 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; | |||
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 (cancellationToken.IsCancellationRequested) | |||
{ | |||
return null; | |||
} | |||
cancellationToken.ThrowIfCancellationRequested(); | |||
ExceptionHelper.ThrowGracefulSocketClose(); | |||
} | |||
@@ -37,11 +42,11 @@ namespace MQTTnet.Serializer | |||
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); | |||
} | |||
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. | |||
var multiplier = 128; | |||
@@ -50,7 +55,13 @@ namespace MQTTnet.Serializer | |||
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; | |||
if (multiplier > 128 * 128 * 128) | |||
@@ -64,16 +75,27 @@ namespace MQTTnet.Serializer | |||
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 readCount = await channel.ReadAsync(buffer, 0, 1, cancellationToken).ConfigureAwait(false); | |||
var buffer = InitializeSingleByteBuffer(); | |||
var readCount = channel.ReadAsync(buffer, 0, 1, cancellationToken).GetAwaiter().GetResult(); | |||
if (readCount <= 0) | |||
{ | |||
cancellationToken.ThrowIfCancellationRequested(); | |||
ExceptionHelper.ThrowGracefulSocketClose(); | |||
} | |||
return buffer[0]; | |||
} | |||
private static byte[] InitializeFixedHeaderBuffer() | |||
{ | |||
return _fixedHeaderBuffer ?? (_fixedHeaderBuffer = new byte[2]); | |||
} | |||
private static byte[] InitializeSingleByteBuffer() | |||
{ | |||
return _singleByteBuffer ?? (_singleByteBuffer = new byte[1]); | |||
} | |||
} | |||
} |
@@ -2,7 +2,6 @@ | |||
using MQTTnet.Packets; | |||
using MQTTnet.Protocol; | |||
using System; | |||
using System.IO; | |||
using System.Linq; | |||
using MQTTnet.Adapter; | |||
@@ -12,65 +11,33 @@ namespace MQTTnet.Serializer | |||
{ | |||
private const int FixedHeaderSize = 1; | |||
private readonly MqttPacketWriter _packetWriter = new MqttPacketWriter(); | |||
public MqttProtocolVersion ProtocolVersion { get; set; } = MqttProtocolVersion.V311; | |||
public ArraySegment<byte> Serialize(MqttBasePacket 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) | |||
stream.Seek(5, SeekOrigin.Begin); | |||
// Leave enough head space for max header size (fixed + 4 variable remaining length = 5 bytes) | |||
_packetWriter.Reset(); | |||
_packetWriter.Seek(5); | |||
var fixedHeader = SerializePacket(packet, stream); | |||
var remainingLength = (int)stream.Length - 5; | |||
var fixedHeader = SerializePacket(packet, _packetWriter); | |||
var remainingLength = _packetWriter.Length - 5; | |||
var remainingLengthBuffer = MqttPacketWriter.EncodeRemainingLength(remainingLength); | |||
var remainingLengthBuffer = MqttPacketWriter.EncodeRemainingLength(remainingLength); | |||
var headerSize = FixedHeaderSize + remainingLengthBuffer.Count; | |||
var headerOffset = 5 - headerSize; | |||
var headerSize = FixedHeaderSize + remainingLengthBuffer.Count; | |||
var headerOffset = 5 - headerSize; | |||
// Position cursor on correct offset on beginining of array (has leading 0x0) | |||
stream.Seek(headerOffset, SeekOrigin.Begin); | |||
stream.WriteByte(fixedHeader); | |||
stream.Write(remainingLengthBuffer.Array, remainingLengthBuffer.Offset, remainingLengthBuffer.Count); | |||
// Position cursor on correct offset on beginining of array (has leading 0x0) | |||
_packetWriter.Seek(headerOffset); | |||
_packetWriter.Write(fixedHeader); | |||
_packetWriter.Write(remainingLengthBuffer.Array, remainingLengthBuffer.Offset, remainingLengthBuffer.Count); | |||
#if NET461 || NET452 || NETSTANDARD2_0 | |||
var buffer = stream.GetBuffer(); | |||
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."); | |||
} | |||
var buffer = _packetWriter.GetBuffer(); | |||
return new ArraySegment<byte>(buffer, headerOffset, _packetWriter.Length - headerOffset); | |||
} | |||
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) | |||
{ | |||
ThrowIfBodyIsEmpty(body); | |||
@@ -192,20 +186,18 @@ namespace MQTTnet.Serializer | |||
private static MqttBasePacket DeserializePublish(ReceivedMqttPacket receivedMqttPacket) | |||
{ | |||
var body = receivedMqttPacket.Body; | |||
ThrowIfBodyIsEmpty(body); | |||
ThrowIfBodyIsEmpty(receivedMqttPacket.Body); | |||
var fixedHeader = new ByteReader(receivedMqttPacket.FixedHeader); | |||
var retain = fixedHeader.Read(); | |||
var qualityOfServiceLevel = (MqttQualityOfServiceLevel)fixedHeader.Read(2); | |||
var dup = fixedHeader.Read(); | |||
var retain = (receivedMqttPacket.FixedHeader & 0x1) > 0; | |||
var qualityOfServiceLevel = (MqttQualityOfServiceLevel)(receivedMqttPacket.FixedHeader >> 1 & 0x3); | |||
var dup = (receivedMqttPacket.FixedHeader & 0x3) > 0; | |||
var topic = body.ReadStringWithLengthPrefix(); | |||
var topic = receivedMqttPacket.Body.ReadStringWithLengthPrefix(); | |||
ushort? packetIdentifier = null; | |||
if (qualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce) | |||
{ | |||
packetIdentifier = body.ReadUInt16(); | |||
packetIdentifier = receivedMqttPacket.Body.ReadUInt16(); | |||
} | |||
var packet = new MqttPublishPacket | |||
@@ -213,7 +205,7 @@ namespace MQTTnet.Serializer | |||
PacketIdentifier = packetIdentifier, | |||
Retain = retain, | |||
Topic = topic, | |||
Payload = body.ReadRemainingData().ToArray(), | |||
Payload = receivedMqttPacket.Body.ReadRemainingData().ToArray(), | |||
QualityOfServiceLevel = qualityOfServiceLevel, | |||
Dup = dup | |||
}; | |||
@@ -253,8 +245,8 @@ namespace MQTTnet.Serializer | |||
throw new MqttProtocolViolationException($"Protocol name ({protocolName}) is not supported."); | |||
} | |||
var connectFlags = new ByteReader(body.ReadByte()); | |||
if (connectFlags.Read()) | |||
var connectFlags = body.ReadByte(); | |||
if ((connectFlags & 0x1) > 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 | |||
{ | |||
ProtocolVersion = protocolVersion, | |||
CleanSession = connectFlags.Read() | |||
CleanSession = (connectFlags & 0x2) > 0 | |||
}; | |||
var willFlag = connectFlags.Read(); | |||
var willQoS = connectFlags.Read(2); | |||
var willRetain = connectFlags.Read(); | |||
var passwordFlag = connectFlags.Read(); | |||
var usernameFlag = connectFlags.Read(); | |||
var willFlag = (connectFlags & 0x4) > 0; | |||
var willQoS = (connectFlags & 0x18) >> 3; | |||
var willRetain = (connectFlags & 0x20) > 0; | |||
var passwordFlag = (connectFlags & 0x40) > 0; | |||
var usernameFlag = (connectFlags & 0x80) > 0; | |||
packet.KeepAlivePeriod = body.ReadUInt16(); | |||
packet.ClientId = body.ReadStringWithLengthPrefix(); | |||
@@ -322,11 +314,11 @@ namespace MQTTnet.Serializer | |||
var packet = new MqttConnAckPacket(); | |||
var firstByteReader = new ByteReader(body.ReadByte()); | |||
var acknowledgeFlags = body.ReadByte(); | |||
if (ProtocolVersion == MqttProtocolVersion.V311) | |||
{ | |||
packet.IsSessionPresent = firstByteReader.Read(); | |||
packet.IsSessionPresent = (acknowledgeFlags & 0x1) > 0; | |||
} | |||
packet.ConnectReturnCode = (MqttConnectReturnCode)body.ReadByte(); | |||
@@ -344,46 +336,46 @@ namespace MQTTnet.Serializer | |||
} | |||
} | |||
// ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local | |||
private static void ValidatePublishPacket(MqttPublishPacket packet) | |||
{ | |||
if (packet == null) throw new ArgumentNullException(nameof(packet)); | |||
if (packet.QualityOfServiceLevel == 0 && packet.Dup) | |||
{ | |||
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); | |||
// Write variable header | |||
if (ProtocolVersion == MqttProtocolVersion.V311) | |||
{ | |||
stream.WriteWithLengthPrefix("MQTT"); | |||
stream.WriteByte(4); // 3.1.2.2 Protocol Level 4 | |||
packetWriter.WriteWithLengthPrefix("MQTT"); | |||
packetWriter.Write(4); // 3.1.2.2 Protocol Level 4 | |||
} | |||
else | |||
{ | |||
stream.WriteWithLengthPrefix("MQIsdp"); | |||
stream.WriteByte(3); // Protocol Level 3 | |||
packetWriter.WriteWithLengthPrefix("MQIsdp"); | |||
packetWriter.Write(3); // Protocol Level 3 | |||
} | |||
var connectFlags = new ByteWriter(); // 3.1.2.3 Connect Flags | |||
connectFlags.Write(false); // Reserved | |||
connectFlags.Write(packet.CleanSession); | |||
connectFlags.Write(packet.WillMessage != null); | |||
if (packet.WillMessage != null) | |||
byte connectFlags = 0x0; | |||
if (packet.CleanSession) | |||
{ | |||
connectFlags.Write((int)packet.WillMessage.QualityOfServiceLevel, 2); | |||
connectFlags.Write(packet.WillMessage.Retain); | |||
connectFlags |= 0x2; | |||
} | |||
else | |||
if (packet.WillMessage != null) | |||
{ | |||
connectFlags.Write(0, 2); | |||
connectFlags.Write(false); | |||
connectFlags |= 0x4; | |||
connectFlags |= (byte)((byte)packet.WillMessage.QualityOfServiceLevel << 3); | |||
if (packet.WillMessage.Retain) | |||
{ | |||
connectFlags |= 0x20; | |||
} | |||
} | |||
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]."); | |||
} | |||
connectFlags.Write(packet.Password != null); | |||
connectFlags.Write(packet.Username != null); | |||
if (packet.Password != null) | |||
{ | |||
connectFlags |= 0x40; | |||
} | |||
if (packet.Username != null) | |||
{ | |||
connectFlags |= 0x80; | |||
} | |||
stream.Write(connectFlags); | |||
stream.Write(packet.KeepAlivePeriod); | |||
stream.WriteWithLengthPrefix(packet.ClientId); | |||
packetWriter.Write(connectFlags); | |||
packetWriter.Write(packet.KeepAlivePeriod); | |||
packetWriter.WriteWithLengthPrefix(packet.ClientId); | |||
if (packet.WillMessage != null) | |||
{ | |||
stream.WriteWithLengthPrefix(packet.WillMessage.Topic); | |||
stream.WriteWithLengthPrefix(packet.WillMessage.Payload); | |||
packetWriter.WriteWithLengthPrefix(packet.WillMessage.Topic); | |||
packetWriter.WriteWithLengthPrefix(packet.WillMessage.Payload); | |||
} | |||
if (packet.Username != null) | |||
{ | |||
stream.WriteWithLengthPrefix(packet.Username); | |||
packetWriter.WriteWithLengthPrefix(packet.Username); | |||
} | |||
if (packet.Password != null) | |||
{ | |||
stream.WriteWithLengthPrefix(packet.Password); | |||
packetWriter.WriteWithLengthPrefix(packet.Password); | |||
} | |||
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.Connect); | |||
} | |||
private byte Serialize(MqttConnAckPacket packet, Stream stream) | |||
private byte Serialize(MqttConnAckPacket packet, MqttPacketWriter packetWriter) | |||
{ | |||
if (ProtocolVersion == MqttProtocolVersion.V310) | |||
{ | |||
stream.WriteByte(0); | |||
packetWriter.Write(0); | |||
} | |||
else if (ProtocolVersion == MqttProtocolVersion.V311) | |||
{ | |||
var connectAcknowledgeFlags = new ByteWriter(); | |||
connectAcknowledgeFlags.Write(packet.IsSessionPresent); | |||
byte connectAcknowledgeFlags = 0x0; | |||
if (packet.IsSessionPresent) | |||
{ | |||
connectAcknowledgeFlags |= 0x1; | |||
} | |||
stream.Write(connectAcknowledgeFlags); | |||
packetWriter.Write(connectAcknowledgeFlags); | |||
} | |||
else | |||
{ | |||
throw new MqttProtocolViolationException("Protocol version not supported."); | |||
} | |||
stream.WriteByte((byte)packet.ConnectReturnCode); | |||
packetWriter.Write((byte)packet.ConnectReturnCode); | |||
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) | |||
{ | |||
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); | |||
} | |||
private static byte Serialize(MqttPublishPacket packet, Stream stream) | |||
private static byte Serialize(MqttPublishPacket packet, MqttPacketWriter packetWriter) | |||
{ | |||
ValidatePublishPacket(packet); | |||
stream.WriteWithLengthPrefix(packet.Topic); | |||
packetWriter.WriteWithLengthPrefix(packet.Topic); | |||
if (packet.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce) | |||
{ | |||
@@ -465,7 +467,7 @@ namespace MQTTnet.Serializer | |||
throw new MqttProtocolViolationException("Publish packet has no packet identifier."); | |||
} | |||
stream.Write(packet.PacketIdentifier.Value); | |||
packetWriter.Write(packet.PacketIdentifier.Value); | |||
} | |||
else | |||
{ | |||
@@ -477,7 +479,7 @@ namespace MQTTnet.Serializer | |||
if (packet.Payload?.Length > 0) | |||
{ | |||
stream.Write(packet.Payload, 0, packet.Payload.Length); | |||
packetWriter.Write(packet.Payload, 0, packet.Payload.Length); | |||
} | |||
byte fixedHeader = 0; | |||
@@ -497,43 +499,43 @@ namespace MQTTnet.Serializer | |||
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) | |||
{ | |||
throw new MqttProtocolViolationException("PubAck packet has no packet identifier."); | |||
} | |||
stream.Write(packet.PacketIdentifier.Value); | |||
packetWriter.Write(packet.PacketIdentifier.Value); | |||
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) | |||
{ | |||
throw new MqttProtocolViolationException("PubRec packet has no packet identifier."); | |||
} | |||
stream.Write(packet.PacketIdentifier.Value); | |||
packetWriter.Write(packet.PacketIdentifier.Value); | |||
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) | |||
{ | |||
throw new MqttProtocolViolationException("PubComp packet has no packet identifier."); | |||
} | |||
stream.Write(packet.PacketIdentifier.Value); | |||
packetWriter.Write(packet.PacketIdentifier.Value); | |||
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]."); | |||
@@ -542,41 +544,41 @@ namespace MQTTnet.Serializer | |||
throw new MqttProtocolViolationException("Subscribe packet has no packet identifier."); | |||
} | |||
stream.Write(packet.PacketIdentifier.Value); | |||
packetWriter.Write(packet.PacketIdentifier.Value); | |||
if (packet.TopicFilters?.Count > 0) | |||
{ | |||
foreach (var topicFilter in packet.TopicFilters) | |||
{ | |||
stream.WriteWithLengthPrefix(topicFilter.Topic); | |||
stream.WriteByte((byte)topicFilter.QualityOfServiceLevel); | |||
packetWriter.WriteWithLengthPrefix(topicFilter.Topic); | |||
packetWriter.Write((byte)topicFilter.QualityOfServiceLevel); | |||
} | |||
} | |||
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) | |||
{ | |||
throw new MqttProtocolViolationException("SubAck packet has no packet identifier."); | |||
} | |||
stream.Write(packet.PacketIdentifier.Value); | |||
packetWriter.Write(packet.PacketIdentifier.Value); | |||
if (packet.SubscribeReturnCodes?.Any() == true) | |||
{ | |||
foreach (var packetSubscribeReturnCode in packet.SubscribeReturnCodes) | |||
{ | |||
stream.WriteByte((byte)packetSubscribeReturnCode); | |||
packetWriter.Write((byte)packetSubscribeReturnCode); | |||
} | |||
} | |||
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]."); | |||
@@ -585,27 +587,27 @@ namespace MQTTnet.Serializer | |||
throw new MqttProtocolViolationException("Unsubscribe packet has no packet identifier."); | |||
} | |||
stream.Write(packet.PacketIdentifier.Value); | |||
packetWriter.Write(packet.PacketIdentifier.Value); | |||
if (packet.TopicFilters?.Any() == true) | |||
{ | |||
foreach (var topicFilter in packet.TopicFilters) | |||
{ | |||
stream.WriteWithLengthPrefix(topicFilter); | |||
packetWriter.WriteWithLengthPrefix(topicFilter); | |||
} | |||
} | |||
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) | |||
{ | |||
throw new MqttProtocolViolationException("UnsubAck packet has no packet identifier."); | |||
} | |||
stream.Write(packet.PacketIdentifier.Value); | |||
packetWriter.Write(packet.PacketIdentifier.Value); | |||
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.UnsubAck); | |||
} | |||
@@ -614,6 +616,7 @@ namespace MQTTnet.Serializer | |||
return MqttPacketWriter.BuildFixedHeader(type); | |||
} | |||
// ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local | |||
private static void ThrowIfBodyIsEmpty(MqttPacketBodyReader body) | |||
{ | |||
if (body == null || body.Length == 0) | |||
@@ -1,44 +1,30 @@ | |||
using System; | |||
using System.IO; | |||
using System.Text; | |||
using MQTTnet.Protocol; | |||
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 byte BuildFixedHeader(MqttControlPacketType packetType, byte flags = 0) | |||
{ | |||
var fixedHeader = (int)packetType << 4; | |||
fixedHeader |= flags; | |||
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 int MaxBufferSize = 4096; | |||
public static void Write(this Stream stream, ByteWriter value) | |||
{ | |||
if (value == null) throw new ArgumentNullException(nameof(value)); | |||
private byte[] _buffer = new byte[128]; | |||
stream.WriteByte(value.Value); | |||
} | |||
private int _position; | |||
public static void WriteWithLengthPrefix(this Stream stream, string value) | |||
{ | |||
stream.WriteWithLengthPrefix(Encoding.UTF8.GetBytes(value ?? string.Empty)); | |||
} | |||
public int Length { get; private set; } | |||
public static void WriteWithLengthPrefix(this Stream stream, byte[] value) | |||
public static byte BuildFixedHeader(MqttControlPacketType packetType, byte flags = 0) | |||
{ | |||
var length = (ushort)value.Length; | |||
stream.Write(length); | |||
stream.Write(value, 0, length); | |||
var fixedHeader = (int)packetType << 4; | |||
fixedHeader |= flags; | |||
return (byte)fixedHeader; | |||
} | |||
public static ArraySegment<byte> EncodeRemainingLength(int length) | |||
@@ -69,5 +55,112 @@ namespace MQTTnet.Serializer | |||
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 _lastNonKeepAlivePacketReceivedTracker = new Stopwatch(); | |||
private readonly IMqttClientSession _clientSession; | |||
private readonly IMqttNetChildLogger _logger; | |||
private readonly string _clientId; | |||
private readonly Action _callback; | |||
private bool _isPaused; | |||
private Task _workerTask; | |||
public MqttClientKeepAliveMonitor(string clientId, Action callback, IMqttNetChildLogger logger) | |||
public MqttClientKeepAliveMonitor(IMqttClientSession clientSession, IMqttNetChildLogger logger) | |||
{ | |||
if (logger == null) throw new ArgumentNullException(nameof(logger)); | |||
_clientId = clientId; | |||
_callback = callback; | |||
_clientSession = clientSession ?? throw new ArgumentNullException(nameof(clientSession)); | |||
_logger = logger.CreateChildLogger(nameof(MqttClientKeepAliveMonitor)); | |||
} | |||
@@ -39,7 +37,7 @@ namespace MQTTnet.Server | |||
return; | |||
} | |||
_workerTask = Task.Run(() => RunAsync(keepAlivePeriod, cancellationToken), cancellationToken); | |||
Task.Run(() => RunAsync(keepAlivePeriod, cancellationToken), cancellationToken); | |||
} | |||
public void Pause() | |||
@@ -74,9 +72,9 @@ namespace MQTTnet.Server | |||
// Values described here: [MQTT-3.1.2-24]. | |||
if (!_isPaused && _lastPacketReceivedTracker.Elapsed.TotalSeconds > keepAlivePeriod * 1.5D) | |||
{ | |||
_logger.Warning(null, "Client '{0}': Did not receive any packet or keep alive signal.", _clientId); | |||
_callback(); | |||
_logger.Warning(null, "Client '{0}': Did not receive any packet or keep alive signal.", _clientSession.ClientId); | |||
_clientSession.Stop(MqttClientDisconnectType.NotClean); | |||
return; | |||
} | |||
@@ -88,11 +86,11 @@ namespace MQTTnet.Server | |||
} | |||
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 | |||
{ | |||
_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.Collections.Concurrent; | |||
using System.Collections.Generic; | |||
using System.Threading; | |||
using System.Threading.Tasks; | |||
using MQTTnet.Adapter; | |||
@@ -11,25 +11,34 @@ using MQTTnet.Protocol; | |||
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 IMqttServerOptions _options; | |||
private readonly MqttClientSession _clientSession; | |||
private readonly IMqttNetChildLogger _logger; | |||
private ConcurrentQueue<MqttBasePacket> _queue = new ConcurrentQueue<MqttBasePacket>(); | |||
public MqttClientPendingMessagesQueue(IMqttServerOptions options, MqttClientSession clientSession, IMqttNetChildLogger logger) | |||
public MqttClientPendingPacketsQueue(IMqttServerOptions options, MqttClientSession clientSession, IMqttNetChildLogger logger) | |||
{ | |||
if (logger == null) throw new ArgumentNullException(nameof(logger)); | |||
_options = options ?? throw new ArgumentNullException(nameof(options)); | |||
_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) | |||
{ | |||
@@ -42,25 +51,29 @@ namespace MQTTnet.Server | |||
Task.Run(() => SendQueuedPacketsAsync(adapter, cancellationToken), cancellationToken); | |||
} | |||
public void Enqueue(MqttBasePacket 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) | |||
{ | |||
_queue.TryDequeue(out _); | |||
if (_options.PendingMessagesOverflowStrategy == MqttPendingMessagesOverflowStrategy.DropOldestQueuedMessage) | |||
{ | |||
_queue.Dequeue(); | |||
} | |||
} | |||
_queue.Enqueue(packet); | |||
} | |||
_queue.Enqueue(packet); | |||
_queueAutoResetEvent.Set(); | |||
_logger.Verbose("Enqueued packet (ClientId: {0}).", _clientSession.ClientId); | |||
@@ -68,13 +81,14 @@ namespace MQTTnet.Server | |||
public void Clear() | |||
{ | |||
var newQueue = new ConcurrentQueue<MqttBasePacket>(); | |||
Interlocked.Exchange(ref _queue, newQueue); | |||
lock (_queue) | |||
{ | |||
_queue.Clear(); | |||
} | |||
} | |||
public void Dispose() | |||
{ | |||
} | |||
private async Task SendQueuedPacketsAsync(IMqttChannelAdapter adapter, CancellationToken cancellationToken) | |||
@@ -100,13 +114,17 @@ namespace MQTTnet.Server | |||
MqttBasePacket packet = null; | |||
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; | |||
} | |||
@@ -115,7 +133,7 @@ namespace MQTTnet.Server | |||
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); | |||
} |
@@ -12,13 +12,13 @@ using MQTTnet.Protocol; | |||
namespace MQTTnet.Server | |||
{ | |||
public class MqttClientSession : IDisposable | |||
public class MqttClientSession : IMqttClientSession | |||
{ | |||
private readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider(); | |||
private readonly MqttRetainedMessagesManager _retainedMessagesManager; | |||
private readonly MqttClientKeepAliveMonitor _keepAliveMonitor; | |||
private readonly MqttClientPendingMessagesQueue _pendingMessagesQueue; | |||
private readonly MqttClientPendingPacketsQueue _pendingPacketsQueue; | |||
private readonly MqttClientSubscriptionsManager _subscriptionsManager; | |||
private readonly MqttClientSessionsManager _sessionsManager; | |||
@@ -47,9 +47,9 @@ namespace MQTTnet.Server | |||
_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); | |||
_pendingMessagesQueue = new MqttClientPendingMessagesQueue(_options, this, _logger); | |||
_pendingPacketsQueue = new MqttClientPendingPacketsQueue(_options, this, _logger); | |||
} | |||
public string ClientId { get; } | |||
@@ -60,7 +60,7 @@ namespace MQTTnet.Server | |||
status.IsConnected = _adapter != null; | |||
status.Endpoint = _adapter?.Endpoint; | |||
status.ProtocolVersion = _adapter?.PacketSerializer?.ProtocolVersion; | |||
status.PendingApplicationMessagesCount = _pendingMessagesQueue.Count; | |||
status.PendingApplicationMessagesCount = _pendingPacketsQueue.Count; | |||
status.LastPacketReceived = _keepAliveMonitor.LastPacketReceived; | |||
status.LastNonKeepAlivePacketReceived = _keepAliveMonitor.LastNonKeepAlivePacketReceived; | |||
} | |||
@@ -80,7 +80,7 @@ namespace MQTTnet.Server | |||
_wasCleanDisconnect = false; | |||
_willMessage = connectPacket.WillMessage; | |||
_pendingMessagesQueue.Start(adapter, _cancellationTokenSource.Token); | |||
_pendingPacketsQueue.Start(adapter, _cancellationTokenSource.Token); | |||
_keepAliveMonitor.Start(connectPacket.KeepAlivePeriod, _cancellationTokenSource.Token); | |||
while (!_cancellationTokenSource.IsCancellationRequested) | |||
@@ -89,7 +89,7 @@ namespace MQTTnet.Server | |||
if (packet != null) | |||
{ | |||
_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) | |||
{ | |||
_logger.Verbose("Client '{0}': Connection closed gracefully.", ClientId); ; | |||
_logger.Verbose("Client '{0}': Connection closed gracefully.", ClientId); | |||
} | |||
else | |||
{ | |||
@@ -113,7 +113,7 @@ namespace MQTTnet.Server | |||
{ | |||
_logger.Error(exception, "Client '{0}': Unhandled exception while receiving client packets.", ClientId); | |||
} | |||
Stop(MqttClientDisconnectType.NotClean); | |||
} | |||
finally | |||
@@ -123,7 +123,7 @@ namespace MQTTnet.Server | |||
_adapter.ReadingPacketStarted -= OnAdapterReadingPacketStarted; | |||
_adapter.ReadingPacketCompleted -= OnAdapterReadingPacketCompleted; | |||
} | |||
_adapter = null; | |||
_cancellationTokenSource?.Dispose(); | |||
@@ -149,13 +149,10 @@ namespace MQTTnet.Server | |||
if (_willMessage != null && !_wasCleanDisconnect) | |||
{ | |||
_sessionsManager.StartDispatchApplicationMessage(this, _willMessage); | |||
_sessionsManager.EnqueueApplicationMessage(this, _willMessage.ToPublishPacket()); | |||
} | |||
_willMessage = null; | |||
////_pendingMessagesQueue.WaitForCompletion(); | |||
////_keepAliveMonitor.WaitForCompletion(); | |||
} | |||
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) | |||
{ | |||
return; | |||
} | |||
var publishPacket = applicationMessage.ToPublishPacket(); | |||
publishPacket.QualityOfServiceLevel = checkSubscriptionsResult.QualityOfServiceLevel; | |||
publishPacket = new MqttPublishPacket | |||
{ | |||
Topic = publishPacket.Topic, | |||
Payload = publishPacket.Payload, | |||
QualityOfServiceLevel = checkSubscriptionsResult.QualityOfServiceLevel, | |||
Retain = false, | |||
Dup = false | |||
}; | |||
if (publishPacket.QualityOfServiceLevel > 0) | |||
{ | |||
@@ -187,16 +190,20 @@ namespace MQTTnet.Server | |||
senderClientSession?.ClientId, | |||
ClientId, | |||
publishPacket.ToApplicationMessage()); | |||
_options.ClientMessageQueueInterceptor?.Invoke(context); | |||
if (!context.AcceptEnqueue || context.ApplicationMessage == null) | |||
{ | |||
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) | |||
@@ -226,31 +233,39 @@ namespace MQTTnet.Server | |||
public void ClearPendingApplicationMessages() | |||
{ | |||
_pendingMessagesQueue.Clear(); | |||
_pendingPacketsQueue.Clear(); | |||
} | |||
public void Dispose() | |||
{ | |||
_pendingMessagesQueue?.Dispose(); | |||
_pendingPacketsQueue?.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) | |||
{ | |||
return HandleIncomingPublishPacketAsync(adapter, publishPacket, cancellationToken); | |||
HandleIncomingPublishPacket(adapter, publishPacket, cancellationToken); | |||
return; | |||
} | |||
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) | |||
{ | |||
return HandleIncomingPubRelPacketAsync(adapter, pubRelPacket, cancellationToken); | |||
var responsePacket = new MqttPubCompPacket | |||
{ | |||
PacketIdentifier = pubRelPacket.PacketIdentifier | |||
}; | |||
adapter.SendPacketAsync(responsePacket, cancellationToken).GetAwaiter().GetResult(); | |||
return; | |||
} | |||
if (packet is MqttPubRecPacket pubRecPacket) | |||
@@ -260,40 +275,41 @@ namespace MQTTnet.Server | |||
PacketIdentifier = pubRecPacket.PacketIdentifier | |||
}; | |||
return adapter.SendPacketAsync(_options.DefaultCommunicationTimeout, responsePacket, cancellationToken); | |||
adapter.SendPacketAsync(responsePacket, cancellationToken).GetAwaiter().GetResult(); | |||
return; | |||
} | |||
if (packet is MqttPubAckPacket || packet is MqttPubCompPacket) | |||
{ | |||
// Discard message. | |||
return Task.FromResult(0); | |||
return; | |||
} | |||
if (packet is MqttSubscribePacket subscribePacket) | |||
{ | |||
return HandleIncomingSubscribePacketAsync(adapter, subscribePacket, cancellationToken); | |||
HandleIncomingSubscribePacket(adapter, subscribePacket, cancellationToken); | |||
return; | |||
} | |||
if (packet is MqttUnsubscribePacket unsubscribePacket) | |||
{ | |||
return HandleIncomingUnsubscribePacketAsync(adapter, unsubscribePacket, cancellationToken); | |||
HandleIncomingUnsubscribePacket(adapter, unsubscribePacket, cancellationToken); | |||
return; | |||
} | |||
if (packet is MqttDisconnectPacket) | |||
{ | |||
Stop(MqttClientDisconnectType.Clean); | |||
return Task.FromResult(0); | |||
return; | |||
} | |||
if (packet is MqttConnectPacket) | |||
{ | |||
Stop(MqttClientDisconnectType.NotClean); | |||
return Task.FromResult(0); | |||
return; | |||
} | |||
_logger.Warning(null, "Client '{0}': Received not supported packet ({1}). Closing connection.", ClientId, packet); | |||
Stop(MqttClientDisconnectType.NotClean); | |||
return Task.FromResult(0); | |||
} | |||
private void EnqueueSubscribedRetainedMessages(ICollection<TopicFilter> topicFilters) | |||
@@ -301,14 +317,14 @@ namespace MQTTnet.Server | |||
var retainedMessages = _retainedMessagesManager.GetSubscribedMessages(topicFilters); | |||
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); | |||
await adapter.SendPacketAsync(_options.DefaultCommunicationTimeout, subscribeResult.ResponsePacket, cancellationToken).ConfigureAwait(false); | |||
adapter.SendPacketAsync(subscribeResult.ResponsePacket, cancellationToken).GetAwaiter().GetResult(); | |||
if (subscribeResult.CloseConnection) | |||
{ | |||
@@ -319,30 +335,30 @@ namespace MQTTnet.Server | |||
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); | |||
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 (applicationMessage.QualityOfServiceLevel) | |||
switch (publishPacket.QualityOfServiceLevel) | |||
{ | |||
case MqttQualityOfServiceLevel.AtMostOnce: | |||
{ | |||
_sessionsManager.StartDispatchApplicationMessage(this, applicationMessage); | |||
return Task.FromResult(0); | |||
HandleIncomingPublishPacketWithQoS0(publishPacket); | |||
break; | |||
} | |||
case MqttQualityOfServiceLevel.AtLeastOnce: | |||
{ | |||
return HandleIncomingPublishPacketWithQoS1(adapter, applicationMessage, publishPacket, cancellationToken); | |||
HandleIncomingPublishPacketWithQoS1(adapter, publishPacket, cancellationToken); | |||
break; | |||
} | |||
case MqttQualityOfServiceLevel.ExactlyOnce: | |||
{ | |||
return HandleIncomingPublishPacketWithQoS2(adapter, applicationMessage, publishPacket, cancellationToken); | |||
HandleIncomingPublishPacketWithQoS2(adapter, publishPacket, cancellationToken); | |||
break; | |||
} | |||
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); | |||
var response = new MqttPubAckPacket { PacketIdentifier = publishPacket.PacketIdentifier }; | |||
return adapter.SendPacketAsync(_options.DefaultCommunicationTimeout, response, cancellationToken); | |||
_sessionsManager.EnqueueApplicationMessage(this, publishPacket); | |||
} | |||
private Task HandleIncomingPublishPacketWithQoS2(IMqttChannelAdapter adapter, MqttApplicationMessage applicationMessage, MqttPublishPacket publishPacket, CancellationToken cancellationToken) | |||
private void HandleIncomingPublishPacketWithQoS1( | |||
IMqttChannelAdapter adapter, | |||
MqttPublishPacket publishPacket, | |||
CancellationToken cancellationToken) | |||
{ | |||
// 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 MqttPubAckPacket | |||
{ | |||
PacketIdentifier = publishPacket.PacketIdentifier | |||
}; | |||
var response = new MqttPubRecPacket { PacketIdentifier = publishPacket.PacketIdentifier }; | |||
return adapter.SendPacketAsync(_options.DefaultCommunicationTimeout, response, cancellationToken); | |||
adapter.SendPacketAsync(response, cancellationToken).GetAwaiter().GetResult(); | |||
} | |||
private Task HandleIncomingPubRelPacketAsync(IMqttChannelAdapter adapter, MqttPubRelPacket pubRelPacket, CancellationToken cancellationToken) | |||
private void HandleIncomingPublishPacketWithQoS2( | |||
IMqttChannelAdapter adapter, | |||
MqttPublishPacket publishPacket, | |||
CancellationToken cancellationToken) | |||
{ | |||
var response = new MqttPubCompPacket { PacketIdentifier = pubRelPacket.PacketIdentifier }; | |||
return adapter.SendPacketAsync(_options.DefaultCommunicationTimeout, response, cancellationToken); | |||
// QoS 2 is implement as method "B" (4.3.3 QoS 2: Exactly once delivery) | |||
_sessionsManager.EnqueueApplicationMessage(this, publishPacket); | |||
var response = new MqttPubRecPacket | |||
{ | |||
PacketIdentifier = publishPacket.PacketIdentifier | |||
}; | |||
adapter.SendPacketAsync(response, cancellationToken).GetAwaiter().GetResult(); | |||
} | |||
private void OnAdapterReadingPacketCompleted(object sender, EventArgs e) | |||
@@ -1,6 +1,7 @@ | |||
using System; | |||
using System.Collections.Concurrent; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Threading; | |||
using System.Threading.Tasks; | |||
using MQTTnet.Adapter; | |||
@@ -14,19 +15,26 @@ namespace MQTTnet.Server | |||
{ | |||
public class MqttClientSessionsManager : IDisposable | |||
{ | |||
private readonly ConcurrentDictionary<string, MqttClientSession> _sessions = new ConcurrentDictionary<string, MqttClientSession>(); | |||
private readonly AsyncLock _sessionPreparationLock = new AsyncLock(); | |||
private readonly BlockingCollection<MqttEnqueuedApplicationMessage> _messageQueue = new BlockingCollection<MqttEnqueuedApplicationMessage>(); | |||
/// <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 IMqttServerOptions _options; | |||
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)); | |||
_logger = logger.CreateChildLogger(nameof(MqttClientSessionsManager)); | |||
_cancellationToken = cancellationToken; | |||
_options = options ?? throw new ArgumentNullException(nameof(options)); | |||
Server = server ?? throw new ArgumentNullException(nameof(server)); | |||
_retainedMessagesManager = retainedMessagesManager ?? throw new ArgumentNullException(nameof(retainedMessagesManager)); | |||
@@ -34,7 +42,154 @@ namespace MQTTnet.Server | |||
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 wasCleanDisconnect = false; | |||
@@ -60,7 +215,7 @@ namespace MQTTnet.Server | |||
var connectReturnCode = ValidateConnection(connectPacket); | |||
if (connectReturnCode != MqttConnectReturnCode.ConnectionAccepted) | |||
{ | |||
await clientAdapter.SendPacketAsync(_options.DefaultCommunicationTimeout, | |||
await clientAdapter.SendPacketAsync( | |||
new MqttConnAckPacket | |||
{ | |||
ConnectReturnCode = connectReturnCode | |||
@@ -70,15 +225,15 @@ namespace MQTTnet.Server | |||
return; | |||
} | |||
var result = await PrepareClientSessionAsync(connectPacket).ConfigureAwait(false); | |||
var result = PrepareClientSession(connectPacket); | |||
var clientSession = result.Session; | |||
await clientAdapter.SendPacketAsync(_options.DefaultCommunicationTimeout, | |||
await clientAdapter.SendPacketAsync( | |||
new MqttConnAckPacket | |||
{ | |||
ConnectReturnCode = connectReturnCode, | |||
IsSessionPresent = result.IsExistingSession | |||
}, | |||
}, | |||
cancellationToken).ConfigureAwait(false); | |||
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) | |||
{ | |||
if (_options.ConnectionValidator == null) | |||
@@ -197,16 +285,16 @@ namespace MQTTnet.Server | |||
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); | |||
if (isSessionPresent) | |||
{ | |||
if (connectPacket.CleanSession) | |||
{ | |||
_sessions.TryRemove(connectPacket.ClientId, out _); | |||
_sessions.Remove(connectPacket.ClientId); | |||
clientSession.Stop(MqttClientDisconnectType.Clean); | |||
clientSession.Dispose(); | |||
@@ -231,60 +319,19 @@ namespace MQTTnet.Server | |||
_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; | |||
if (interceptor == null) | |||
{ | |||
return interceptorContext; | |||
return null; | |||
} | |||
var interceptorContext = new MqttApplicationMessageInterceptorContext(sender?.ClientId, applicationMessage); | |||
interceptor(interceptorContext); | |||
return interceptorContext; | |||
} | |||
@@ -1,5 +1,4 @@ | |||
using System; | |||
using System.Collections.Concurrent; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using MQTTnet.Packets; | |||
@@ -9,7 +8,7 @@ namespace MQTTnet.Server | |||
{ | |||
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 MqttServer _server; | |||
private readonly string _clientId; | |||
@@ -54,7 +53,11 @@ namespace MQTTnet.Server | |||
if (interceptorContext.AcceptSubscription) | |||
{ | |||
_subscriptions[topicFilter.Topic] = topicFilter.QualityOfServiceLevel; | |||
lock (_subscriptions) | |||
{ | |||
_subscriptions[topicFilter.Topic] = topicFilter.QualityOfServiceLevel; | |||
} | |||
_server.OnClientSubscribedTopic(_clientId, topicFilter); | |||
} | |||
} | |||
@@ -66,10 +69,14 @@ namespace MQTTnet.Server | |||
{ | |||
if (unsubscribePacket == null) throw new ArgumentNullException(nameof(unsubscribePacket)); | |||
foreach (var topicFilter in unsubscribePacket.TopicFilters) | |||
lock (_subscriptions) | |||
{ | |||
_subscriptions.TryRemove(topicFilter, out _); | |||
_server.OnClientUnsubscribedTopic(_clientId, topicFilter); | |||
foreach (var topicFilter in unsubscribePacket.TopicFilters) | |||
{ | |||
_subscriptions.Remove(topicFilter); | |||
_server.OnClientUnsubscribedTopic(_clientId, topicFilter); | |||
} | |||
} | |||
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>(); | |||
foreach (var subscription in _subscriptions) | |||
lock (_subscriptions) | |||
{ | |||
if (!MqttTopicFilterComparer.IsMatch(applicationMessage.Topic, subscription.Key)) | |||
foreach (var subscription in _subscriptions) | |||
{ | |||
continue; | |||
} | |||
if (!MqttTopicFilterComparer.IsMatch(topic, subscription.Key)) | |||
{ | |||
continue; | |||
} | |||
qosLevels.Add(subscription.Value); | |||
qosLevels.Add(subscription.Value); | |||
} | |||
} | |||
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) | |||
@@ -122,12 +131,12 @@ namespace MQTTnet.Server | |||
return interceptorContext; | |||
} | |||
private static CheckSubscriptionsResult CreateSubscriptionResult(MqttApplicationMessage applicationMessage, HashSet<MqttQualityOfServiceLevel> subscribedQoSLevels) | |||
private static CheckSubscriptionsResult CreateSubscriptionResult(MqttQualityOfServiceLevel qosLevel, HashSet<MqttQualityOfServiceLevel> subscribedQoSLevels) | |||
{ | |||
MqttQualityOfServiceLevel effectiveQoS; | |||
if (subscribedQoSLevels.Contains(applicationMessage.QualityOfServiceLevel)) | |||
if (subscribedQoSLevels.Contains(qosLevel)) | |||
{ | |||
effectiveQoS = applicationMessage.QualityOfServiceLevel; | |||
effectiveQoS = qosLevel; | |||
} | |||
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.Collections.Concurrent; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Threading.Tasks; | |||
@@ -9,7 +8,8 @@ namespace MQTTnet.Server | |||
{ | |||
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 IMqttServerOptions _options; | |||
@@ -31,10 +31,13 @@ namespace MQTTnet.Server | |||
{ | |||
var retainedMessages = await _options.Storage.LoadRetainedMessagesAsync().ConfigureAwait(false); | |||
_messages.Clear(); | |||
foreach (var retainedMessage in retainedMessages) | |||
lock (_messages) | |||
{ | |||
_messages[retainedMessage.Topic] = retainedMessage; | |||
_messages.Clear(); | |||
foreach (var retainedMessage in retainedMessages) | |||
{ | |||
_messages[retainedMessage.Topic] = retainedMessage; | |||
} | |||
} | |||
} | |||
catch (Exception exception) | |||
@@ -61,17 +64,20 @@ namespace MQTTnet.Server | |||
{ | |||
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); | |||
break; | |||
retainedMessages.Add(retainedMessage); | |||
break; | |||
} | |||
} | |||
} | |||
@@ -82,28 +88,31 @@ namespace MQTTnet.Server | |||
{ | |||
var saveIsRequired = false; | |||
if (applicationMessage.Payload?.Length == 0) | |||
{ | |||
saveIsRequired = _messages.TryRemove(applicationMessage.Topic, out _); | |||
_logger.Info("Client '{0}' cleared retained message for topic '{1}'.", clientId, applicationMessage.Topic); | |||
} | |||
else | |||
lock (_messages) | |||
{ | |||
if (!_messages.TryGetValue(applicationMessage.Topic, out var existingMessage)) | |||
if (applicationMessage.Payload?.Length == 0) | |||
{ | |||
_messages[applicationMessage.Topic] = applicationMessage; | |||
saveIsRequired = true; | |||
saveIsRequired = _messages.Remove(applicationMessage.Topic); | |||
_logger.Info("Client '{0}' cleared retained message for topic '{1}'.", clientId, applicationMessage.Topic); | |||
} | |||
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; | |||
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) | |||
@@ -113,7 +122,13 @@ namespace MQTTnet.Server | |||
if (saveIsRequired && _options.Storage != null) | |||
{ | |||
await _options.Storage.SaveRetainedMessagesAsync(_messages.Values.ToList()).ConfigureAwait(false); | |||
List<MqttApplicationMessage> messages; | |||
lock (_messages) | |||
{ | |||
messages = _messages.Values.ToList(); | |||
} | |||
await _options.Storage.SaveRetainedMessagesAsync(messages).ConfigureAwait(false); | |||
} | |||
} | |||
} | |||
@@ -5,6 +5,7 @@ using System.Threading; | |||
using System.Threading.Tasks; | |||
using MQTTnet.Adapter; | |||
using MQTTnet.Diagnostics; | |||
using MQTTnet.Internal; | |||
namespace MQTTnet.Server | |||
{ | |||
@@ -65,7 +66,7 @@ namespace MQTTnet.Server | |||
if (_cancellationTokenSource == null) throw new InvalidOperationException("The server is not started."); | |||
_clientSessionsManager.StartDispatchApplicationMessage(null, applicationMessage); | |||
_clientSessionsManager.EnqueueApplicationMessage(null, applicationMessage.ToPublishPacket()); | |||
return Task.FromResult(0); | |||
} | |||
@@ -81,7 +82,8 @@ namespace MQTTnet.Server | |||
_retainedMessagesManager = new MqttRetainedMessagesManager(Options, _logger); | |||
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) | |||
{ | |||
@@ -103,25 +105,26 @@ namespace MQTTnet.Server | |||
} | |||
_cancellationTokenSource.Cancel(false); | |||
_cancellationTokenSource.Dispose(); | |||
foreach (var adapter in _adapters) | |||
{ | |||
adapter.ClientAccepted -= OnClientAccepted; | |||
await adapter.StopAsync().ConfigureAwait(false); | |||
} | |||
await _clientSessionsManager.StopAsync().ConfigureAwait(false); | |||
_clientSessionsManager.Stop(); | |||
_logger.Info("Stopped."); | |||
Stopped?.Invoke(this, EventArgs.Empty); | |||
} | |||
finally | |||
{ | |||
_clientSessionsManager?.Dispose(); | |||
_cancellationTokenSource?.Dispose(); | |||
_cancellationTokenSource = null; | |||
_retainedMessagesManager = null; | |||
_clientSessionsManager?.Dispose(); | |||
_clientSessionsManager = null; | |||
} | |||
} | |||
@@ -155,9 +158,7 @@ namespace MQTTnet.Server | |||
private void OnClientAccepted(object sender, MqttServerAdapterClientAcceptedEventArgs eventArgs) | |||
{ | |||
eventArgs.SessionTask = Task.Run( | |||
() => _clientSessionsManager.RunSessionAsync(eventArgs.Client, _cancellationTokenSource.Token), | |||
_cancellationTokenSource.Token); | |||
eventArgs.SessionTask = _clientSessionsManager.StartSession(eventArgs.Client); | |||
} | |||
} | |||
} |
@@ -1,6 +1,6 @@ | |||
namespace MQTTnet.Server | |||
{ | |||
public class GetOrCreateClientSessionResult | |||
public class PrepareClientSessionResult | |||
{ | |||
public bool IsExistingSession { get; set; } | |||
@@ -65,7 +65,7 @@ namespace MQTTnet.Benchmarks | |||
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; | |||
@@ -10,9 +10,11 @@ | |||
<ItemGroup> | |||
<PackageReference Include="BenchmarkDotNet" Version="0.10.14" /> | |||
<PackageReference Include="System.IO.Pipelines" Version="4.5.0" /> | |||
<PackageReference Include="Microsoft.AspNetCore" Version="2.1.0" /> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<ProjectReference Include="..\..\Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj" /> | |||
<ProjectReference Include="..\..\Source\MQTTnet\MQTTnet.csproj" /> | |||
</ItemGroup> | |||
@@ -9,6 +9,7 @@ namespace MQTTnet.Benchmarks | |||
{ | |||
[ClrJob] | |||
[RPlotExporter, RankColumn] | |||
[MemoryDiagnoser] | |||
public class MessageProcessingBenchmark | |||
{ | |||
private IMqttServer _mqttServer; | |||
@@ -0,0 +1,76 @@ | |||
using BenchmarkDotNet.Attributes; | |||
using MQTTnet.Client; | |||
using MQTTnet.AspNetCore; | |||
using Microsoft.AspNetCore; | |||
using Microsoft.AspNetCore.Hosting; | |||
using MQTTnet.Server; | |||
using MQTTnet.Diagnostics; | |||
using MQTTnet.AspNetCore.Client; | |||
namespace MQTTnet.Benchmarks | |||
{ | |||
[MemoryDiagnoser] | |||
public class MessageProcessingMqttConnectionContextBenchmark | |||
{ | |||
private IWebHost _host; | |||
private IMqttClient _mqttClient; | |||
private MqttApplicationMessage _message; | |||
[GlobalSetup] | |||
public void Setup() | |||
{ | |||
_host = WebHost.CreateDefaultBuilder() | |||
.UseKestrel(o => o.ListenAnyIP(1883, l => l.UseMqtt())) | |||
.ConfigureServices(services => { | |||
var mqttServerOptions = new MqttServerOptionsBuilder() | |||
.WithoutDefaultEndpoint() | |||
.Build(); | |||
services | |||
.AddHostedMqttServer(mqttServerOptions) | |||
.AddMqttConnectionHandler(); | |||
}) | |||
.Configure(app => { | |||
app.UseMqttServer(s => { | |||
}); | |||
}) | |||
.Build(); | |||
var factory = new MqttFactory(); | |||
_mqttClient = factory.CreateMqttClient(new MqttNetLogger(), new MqttClientConnectionContextFactory()); | |||
_host.StartAsync().GetAwaiter().GetResult(); | |||
var clientOptions = new MqttClientOptionsBuilder() | |||
.WithTcpServer("localhost").Build(); | |||
_mqttClient.ConnectAsync(clientOptions).GetAwaiter().GetResult(); | |||
_message = new MqttApplicationMessageBuilder() | |||
.WithTopic("A") | |||
.Build(); | |||
} | |||
[GlobalCleanup] | |||
public void Cleanup() | |||
{ | |||
_mqttClient.DisconnectAsync().GetAwaiter().GetResult(); | |||
_mqttClient.Dispose(); | |||
_host.StopAsync().GetAwaiter().GetResult(); | |||
_host.Dispose(); | |||
} | |||
[Benchmark] | |||
public void Send_10000_Messages() | |||
{ | |||
for (var i = 0; i < 10000; i++) | |||
{ | |||
_mqttClient.PublishAsync(_message).GetAwaiter().GetResult(); | |||
} | |||
} | |||
} | |||
} |
@@ -16,6 +16,7 @@ namespace MQTTnet.Benchmarks | |||
Console.WriteLine("5 = ChannelAdapterBenchmark"); | |||
Console.WriteLine("6 = MqttTcpChannelBenchmark"); | |||
Console.WriteLine("7 = TcpPipesBenchmark"); | |||
Console.WriteLine("8 = MessageProcessingMqttConnectionContextBenchmark"); | |||
var pressedKey = Console.ReadKey(true); | |||
switch (pressedKey.KeyChar) | |||
@@ -41,6 +42,9 @@ namespace MQTTnet.Benchmarks | |||
case '7': | |||
BenchmarkRunner.Run<TcpPipesBenchmark>(); | |||
break; | |||
case '8': | |||
BenchmarkRunner.Run<MessageProcessingMqttConnectionContextBenchmark>(new AllowNonOptimized()); | |||
break; | |||
} | |||
Console.ReadLine(); | |||
@@ -6,8 +6,9 @@ using BenchmarkDotNet.Attributes.Jobs; | |||
using BenchmarkDotNet.Attributes.Exporters; | |||
using System; | |||
using System.Threading; | |||
using System.IO; | |||
using System.Threading.Tasks; | |||
using MQTTnet.Adapter; | |||
using MQTTnet.Channel; | |||
namespace MQTTnet.Benchmarks | |||
{ | |||
@@ -38,37 +39,73 @@ namespace MQTTnet.Benchmarks | |||
for (var i = 0; i < 10000; i++) | |||
{ | |||
_serializer.Serialize(_packet); | |||
_serializer.FreeBuffer(); | |||
} | |||
} | |||
[Benchmark] | |||
public void Deserialize_10000_Messages() | |||
{ | |||
var channel = new BenchmarkMqttChannel(_serializedPacket); | |||
for (var i = 0; i < 10000; i++) | |||
{ | |||
using (var headerStream = new MemoryStream(Join(_serializedPacket))) | |||
{ | |||
var channel = new TestMqttChannel(headerStream); | |||
var header = MqttPacketReader.ReadFixedHeaderAsync(channel, CancellationToken.None).GetAwaiter().GetResult(); | |||
using (var bodyStream = new MemoryStream(Join(_serializedPacket), (int)headerStream.Position, header.RemainingLength)) | |||
{ | |||
_serializer.Deserialize(new ReceivedMqttPacket(header.Flags, new MqttPacketBodyReader(bodyStream.ToArray()))); | |||
} | |||
} | |||
channel.Reset(); | |||
var header = MqttPacketReader.ReadFixedHeaderAsync(channel, CancellationToken.None).GetAwaiter().GetResult(); | |||
var receivedPacket = new ReceivedMqttPacket( | |||
header.Flags, | |||
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(); | |||
foreach (var chunk in chunks) | |||
private readonly ArraySegment<byte> _buffer; | |||
private int _position; | |||
public BenchmarkMqttChannel(ArraySegment<byte> buffer) | |||
{ | |||
_buffer = buffer; | |||
_position = _buffer.Offset; | |||
} | |||
public string Endpoint { get; } | |||
public void Reset() | |||
{ | |||
_position = _buffer.Offset; | |||
} | |||
public Task ConnectAsync(CancellationToken cancellationToken) | |||
{ | |||
buffer.Write(chunk.Array, chunk.Offset, chunk.Count); | |||
throw new NotImplementedException(); | |||
} | |||
return buffer.ToArray(); | |||
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.Tasks; | |||
using BenchmarkDotNet.Attributes; | |||
using MQTTnet.Benchmarks.Tcp; | |||
using MQTTnet.AspNetCore.Client.Tcp; | |||
namespace MQTTnet.Benchmarks | |||
{ | |||
@@ -25,10 +25,12 @@ namespace MQTTnet.Benchmarks | |||
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()); | |||
_server = serverConnection.StartAsync().GetAwaiter().GetResult(); | |||
serverConnection.StartAsync().GetAwaiter().GetResult(); | |||
_server = serverConnection.Transport; | |||
} | |||
@@ -1,30 +0,0 @@ | |||
using Microsoft.VisualStudio.TestTools.UnitTesting; | |||
using MQTTnet.Serializer; | |||
namespace MQTTnet.Core.Tests | |||
{ | |||
[TestClass] | |||
public class ByteReaderTests | |||
{ | |||
[TestMethod] | |||
public void ByteReader_ReadToEnd() | |||
{ | |||
var reader = new ByteReader(85); | |||
Assert.IsTrue(reader.Read()); | |||
Assert.IsFalse(reader.Read()); | |||
Assert.IsTrue(reader.Read()); | |||
Assert.IsFalse(reader.Read()); | |||
Assert.IsTrue(reader.Read()); | |||
Assert.IsFalse(reader.Read()); | |||
Assert.IsTrue(reader.Read()); | |||
Assert.IsFalse(reader.Read()); | |||
} | |||
[TestMethod] | |||
public void ByteReader_ReadPartial() | |||
{ | |||
var reader = new ByteReader(15); | |||
Assert.AreEqual(3, reader.Read(2)); | |||
} | |||
} | |||
} |
@@ -1,51 +0,0 @@ | |||
using Microsoft.VisualStudio.TestTools.UnitTesting; | |||
using MQTTnet.Serializer; | |||
namespace MQTTnet.Core.Tests | |||
{ | |||
[TestClass] | |||
public class ByteWriterTests | |||
{ | |||
[TestMethod] | |||
public void ByteWriter_WriteMultipleAll() | |||
{ | |||
var b = new ByteWriter(); | |||
Assert.AreEqual(0, b.Value); | |||
b.Write(3, 2); | |||
Assert.AreEqual(3, b.Value); | |||
} | |||
[TestMethod] | |||
public void ByteWriter_WriteMultiplePartial() | |||
{ | |||
var b = new ByteWriter(); | |||
Assert.AreEqual(0, b.Value); | |||
b.Write(255, 2); | |||
Assert.AreEqual(3, b.Value); | |||
} | |||
[TestMethod] | |||
public void ByteWriter_WriteTo0xFF() | |||
{ | |||
var b = new ByteWriter(); | |||
Assert.AreEqual(0, b.Value); | |||
b.Write(true); | |||
Assert.AreEqual(1, b.Value); | |||
b.Write(true); | |||
Assert.AreEqual(3, b.Value); | |||
b.Write(true); | |||
Assert.AreEqual(7, b.Value); | |||
b.Write(true); | |||
Assert.AreEqual(15, b.Value); | |||
b.Write(true); | |||
Assert.AreEqual(31, b.Value); | |||
b.Write(true); | |||
Assert.AreEqual(63, b.Value); | |||
b.Write(true); | |||
Assert.AreEqual(127, b.Value); | |||
b.Write(true); | |||
Assert.AreEqual(255, b.Value); | |||
} | |||
} | |||
} |
@@ -14,20 +14,20 @@ namespace MQTTnet.Core.Tests | |||
[TestMethod] | |||
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))] | |||
[TestMethod] | |||
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] | |||
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); | |||
} | |||
@@ -36,7 +36,7 @@ namespace MQTTnet.Core.Tests | |||
{ | |||
try | |||
{ | |||
await MQTTnet.Internal.TaskExtensions.TimeoutAfter(ct => Task.Run(() => | |||
await MQTTnet.Internal.TaskExtensions.TimeoutAfterAsync(ct => Task.Run(() => | |||
{ | |||
var iis = new int[0]; | |||
iis[1] = 0; | |||
@@ -55,7 +55,7 @@ namespace MQTTnet.Core.Tests | |||
{ | |||
try | |||
{ | |||
await MQTTnet.Internal.TaskExtensions.TimeoutAfter(ct => Task.Run(() => | |||
await MQTTnet.Internal.TaskExtensions.TimeoutAfterAsync(ct => Task.Run(() => | |||
{ | |||
var iis = new int[0]; | |||
iis[1] = 0; | |||
@@ -76,7 +76,7 @@ namespace MQTTnet.Core.Tests | |||
var tasks = Enumerable.Range(0, 100000) | |||
.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); | |||
@@ -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 MQTTnet.Adapter; | |||
using MQTTnet.Diagnostics; | |||
using MQTTnet.Packets; | |||
using MQTTnet.Server; | |||
@@ -12,39 +16,31 @@ namespace MQTTnet.Core.Tests | |||
[TestMethod] | |||
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 | |||
{ | |||
timeoutCalledCount++; | |||
}, new MqttNetLogger().CreateChildLogger("")); | |||
Assert.AreEqual(0, timeoutCalledCount); | |||
Assert.AreEqual(0, clientSession.StopCalledCount); | |||
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. | |||
Assert.AreEqual(1, timeoutCalledCount); | |||
Assert.AreEqual(1, clientSession.StopCalledCount); | |||
} | |||
[TestMethod] | |||
public void KeepAlive_NoTimeout() | |||
{ | |||
var timeoutCalledCount = 0; | |||
var monitor = new MqttClientKeepAliveMonitor(string.Empty, delegate | |||
{ | |||
timeoutCalledCount++; | |||
}, new MqttNetLogger().CreateChildLogger("")); | |||
var clientSession = new TestClientSession(); | |||
var monitor = new MqttClientKeepAliveMonitor(clientSession, new MqttNetLogger().CreateChildLogger()); | |||
Assert.AreEqual(0, timeoutCalledCount); | |||
Assert.AreEqual(0, clientSession.StopCalledCount); | |||
monitor.Start(1, CancellationToken.None); | |||
Assert.AreEqual(0, timeoutCalledCount); | |||
Assert.AreEqual(0, clientSession.StopCalledCount); | |||
// Simulate traffic. | |||
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()); | |||
Thread.Sleep(1000); | |||
Assert.AreEqual(0, timeoutCalledCount); | |||
Assert.AreEqual(0, clientSession.StopCalledCount); | |||
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 | |||
{ | |||
[TestMethod] | |||
[ExpectedException(typeof(MqttCommunicationException))] | |||
[ExpectedException(typeof(MqttCommunicationClosedGracefullyException))] | |||
public void MqttPacketReader_EmptyStream() | |||
{ | |||
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)) | |||
{ | |||
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); | |||
Assert.AreEqual(expectedBase64Value, Convert.ToBase64String(Join(buffer2))); | |||
@@ -20,13 +20,7 @@ namespace MQTTnet.Core.Tests | |||
sm.Subscribe(sp); | |||
var pp = new MqttApplicationMessage | |||
{ | |||
Topic = "A/B/C", | |||
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce | |||
}; | |||
var result = sm.CheckSubscriptions(pp); | |||
var result = sm.CheckSubscriptions("A/B/C", MqttQualityOfServiceLevel.AtMostOnce); | |||
Assert.IsTrue(result.IsSubscribed); | |||
Assert.AreEqual(result.QualityOfServiceLevel, MqttQualityOfServiceLevel.AtMostOnce); | |||
} | |||
@@ -41,13 +35,7 @@ namespace MQTTnet.Core.Tests | |||
sm.Subscribe(sp); | |||
var pp = new MqttApplicationMessage | |||
{ | |||
Topic = "A/B/C", | |||
QualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce | |||
}; | |||
var result = sm.CheckSubscriptions(pp); | |||
var result = sm.CheckSubscriptions("A/B/C", MqttQualityOfServiceLevel.ExactlyOnce); | |||
Assert.IsTrue(result.IsSubscribed); | |||
Assert.AreEqual(result.QualityOfServiceLevel, MqttQualityOfServiceLevel.AtMostOnce); | |||
} | |||
@@ -63,13 +51,7 @@ namespace MQTTnet.Core.Tests | |||
sm.Subscribe(sp); | |||
var pp = new MqttApplicationMessage | |||
{ | |||
Topic = "A/B/C", | |||
QualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce | |||
}; | |||
var result = sm.CheckSubscriptions(pp); | |||
var result = sm.CheckSubscriptions("A/B/C", MqttQualityOfServiceLevel.ExactlyOnce); | |||
Assert.IsTrue(result.IsSubscribed); | |||
Assert.AreEqual(result.QualityOfServiceLevel, MqttQualityOfServiceLevel.AtLeastOnce); | |||
} | |||
@@ -84,13 +66,7 @@ namespace MQTTnet.Core.Tests | |||
sm.Subscribe(sp); | |||
var pp = new MqttApplicationMessage | |||
{ | |||
Topic = "A/B/X", | |||
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce | |||
}; | |||
Assert.IsFalse(sm.CheckSubscriptions(pp).IsSubscribed); | |||
Assert.IsFalse(sm.CheckSubscriptions("A/B/X", MqttQualityOfServiceLevel.AtMostOnce).IsSubscribed); | |||
} | |||
[TestMethod] | |||
@@ -103,19 +79,13 @@ namespace MQTTnet.Core.Tests | |||
sm.Subscribe(sp); | |||
var pp = new MqttApplicationMessage | |||
{ | |||
Topic = "A/B/C", | |||
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce | |||
}; | |||
Assert.IsTrue(sm.CheckSubscriptions(pp).IsSubscribed); | |||
Assert.IsTrue(sm.CheckSubscriptions("A/B/C", MqttQualityOfServiceLevel.AtMostOnce).IsSubscribed); | |||
var up = new MqttUnsubscribePacket(); | |||
up.TopicFilters.Add("A/B/C"); | |||
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.Collections.Concurrent; | |||
using System.Collections.Generic; | |||
using System.Threading; | |||
using System.Threading.Tasks; | |||
using MQTTnet.Adapter; | |||
@@ -36,7 +35,7 @@ namespace MQTTnet.Core.Tests | |||
return Task.FromResult(0); | |||
} | |||
public Task SendPacketAsync(TimeSpan timeout, MqttBasePacket packet, CancellationToken cancellationToken) | |||
public Task SendPacketAsync(MqttBasePacket packet, CancellationToken cancellationToken) | |||
{ | |||
ThrowIfPartnerIsNull(); | |||
@@ -1,7 +1,7 @@ | |||
<Project Sdk="Microsoft.NET.Sdk.Web"> | |||
<PropertyGroup> | |||
<TargetFramework>netcoreapp2.0</TargetFramework> | |||
<TargetFramework>netcoreapp2.1</TargetFramework> | |||
<TypeScriptToolsVersion>Latest</TypeScriptToolsVersion> | |||
</PropertyGroup> | |||
@@ -10,7 +10,8 @@ | |||
</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> | |||
@@ -1,5 +1,6 @@ | |||
using Microsoft.AspNetCore; | |||
using Microsoft.AspNetCore.Hosting; | |||
using MQTTnet.AspNetCore; | |||
namespace MQTTnet.TestApp.AspNetCore2 | |||
{ | |||
@@ -12,6 +13,10 @@ namespace MQTTnet.TestApp.AspNetCore2 | |||
private static IWebHost BuildWebHost(string[] args) => | |||
WebHost.CreateDefaultBuilder(args) | |||
.UseKestrel(o => { | |||
o.ListenAnyIP(1883, l => l.UseMqtt()); | |||
o.ListenAnyIP(5000); // default http pipeline | |||
}) | |||
.UseStartup<Startup>() | |||
.Build(); | |||
} | |||
@@ -17,8 +17,12 @@ namespace MQTTnet.TestApp.AspNetCore2 | |||
public void ConfigureServices(IServiceCollection services) | |||
{ | |||
var mqttServerOptions = new MqttServerOptionsBuilder().Build(); | |||
services.AddHostedMqttServer(mqttServerOptions); | |||
var mqttServerOptions = new MqttServerOptionsBuilder() | |||
.WithoutDefaultEndpoint() | |||
.Build(); | |||
services | |||
.AddHostedMqttServer(mqttServerOptions) | |||
.AddMqttConnectionHandler(); | |||
} | |||
// 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 async Task RunAsync() | |||
public static void RunClientOnly() | |||
{ | |||
Console.WriteLine("Press 'c' for concurrent sends. Otherwise in one batch."); | |||
var concurrent = Console.ReadKey(true).KeyChar == 'c'; | |||
try | |||
{ | |||
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++) | |||
{ | |||
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 server = Task.Run(RunServerAsync); | |||
await Task.Delay(1000); | |||
var client = Task.Run(() => RunClientAsync(2000, TimeSpan.FromMilliseconds(10), concurrent)); | |||
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(); | |||
await Task.WhenAll(server, client).ConfigureAwait(false); | |||
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) | |||
@@ -53,29 +124,8 @@ namespace MQTTnet.TestApp.NetCore | |||
} | |||
var message = CreateMessage(); | |||
var messages = new[] { message }; | |||
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; | |||
for (var i = 0; i < testMessageCount; i++) | |||
{ | |||
@@ -142,38 +192,5 @@ namespace MQTTnet.TestApp.NetCore | |||
Interlocked.Increment(ref count); | |||
return Task.Run(() => client.PublishAsync(applicationMessage)); | |||
} | |||
private static async Task RunServerAsync() | |||
{ | |||
try | |||
{ | |||
var mqttServer = new MqttFactory().CreateMqttServer(); | |||
////var msgs = 0; | |||
////var stopwatch = Stopwatch.StartNew(); | |||
////mqttServer.ApplicationMessageReceived += (sender, args) => | |||
////{ | |||
//// msgs++; | |||
//// if (stopwatch.ElapsedMilliseconds > 1000) | |||
//// { | |||
//// Console.WriteLine($"received {msgs}"); | |||
//// msgs = 0; | |||
//// stopwatch.Restart(); | |||
//// } | |||
////}; | |||
await mqttServer.StartAsync(new MqttServerOptions()); | |||
Console.WriteLine("Press any key to exit."); | |||
Console.ReadLine(); | |||
await mqttServer.StopAsync().ConfigureAwait(false); | |||
} | |||
catch (Exception e) | |||
{ | |||
Console.WriteLine(e); | |||
} | |||
Console.ReadLine(); | |||
} | |||
} | |||
} |
@@ -22,6 +22,8 @@ namespace MQTTnet.TestApp.NetCore | |||
Console.WriteLine("5 = Start public broker test"); | |||
Console.WriteLine("6 = Start server & client"); | |||
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); | |||
if (pressedKey.KeyChar == '1') | |||
@@ -34,7 +36,8 @@ namespace MQTTnet.TestApp.NetCore | |||
} | |||
else if (pressedKey.KeyChar == '3') | |||
{ | |||
Task.Run(PerformanceTest.RunAsync); | |||
PerformanceTest.RunClientAndServer(); | |||
return; | |||
} | |||
else if (pressedKey.KeyChar == '4') | |||
{ | |||
@@ -52,6 +55,16 @@ namespace MQTTnet.TestApp.NetCore | |||
{ | |||
Task.Run(ClientFlowTest.RunAsync); | |||
} | |||
else if (pressedKey.KeyChar == '8') | |||
{ | |||
PerformanceTest.RunClientOnly(); | |||
return; | |||
} | |||
else if (pressedKey.KeyChar == '9') | |||
{ | |||
ServerTest.RunEmptyServer(); | |||
return; | |||
} | |||
Thread.Sleep(Timeout.Infinite); | |||
} | |||
@@ -8,12 +8,19 @@ namespace MQTTnet.TestApp.NetCore | |||
{ | |||
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() | |||
{ | |||
try | |||
{ | |||
MqttNetConsoleLogger.ForwardToConsole(); | |||
var options = new MqttServerOptions | |||
{ | |||
ConnectionValidator = p => | |||
@@ -4,6 +4,8 @@ | |||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" | |||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" | |||
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:DesignWidth="800" | |||
mc:Ignorable="d"> | |||
@@ -31,7 +33,7 @@ | |||
<CheckBox x:Name="CleanSession" IsChecked="True"></CheckBox> | |||
<TextBlock>Keep alive interval:</TextBlock> | |||
<TextBox x:Name="KeepAliveInterval" Text="5"></TextBox> | |||
<StackPanel Orientation="Horizontal"> | |||
<RadioButton x:Name="UseTcp" IsChecked="True" GroupName="connection">TCP</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="ServerClearRetainedMessages">Clear previously retained messages on startup</CheckBox> | |||
<CheckBox x:Name="ServerAllowPersistentSessions">Allow persistent sessions</CheckBox> | |||
<StackPanel Orientation="Horizontal"> | |||
<Button Width="120" Margin="0,0,10,0" Click="StartServer">Start</Button> | |||
@@ -149,11 +152,6 @@ | |||
</StackPanel> | |||
</StackPanel> | |||
</PivotItem> | |||
<PivotItem Header="Log"> | |||
<StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> | |||
<Button Click="ClearLog" Width="120">Clear</Button> | |||
</StackPanel> | |||
</PivotItem> | |||
<PivotItem Header="Sessions"> | |||
<StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> | |||
@@ -162,12 +160,23 @@ | |||
<Button Width="120" Margin="0,0,10,0" Click="ClearSessions">Clear</Button> | |||
</StackPanel> | |||
<ListView> | |||
<ListView ItemsSource="{Binding}" x:Name="ListViewSessions"> | |||
<ListView.ItemTemplate> | |||
<DataTemplate> | |||
<Grid> | |||
<TextBlock></TextBlock> | |||
</Grid> | |||
<DataTemplate x:DataType="server:IMqttClientSessionStatus"> | |||
<StackPanel Orientation="Horizontal"> | |||
<StackPanel.Resources> | |||
<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> | |||
</ListView.ItemTemplate> | |||
</ListView> | |||
@@ -1,5 +1,6 @@ | |||
using System; | |||
using System.Collections.Concurrent; | |||
using System.Collections.ObjectModel; | |||
using System.Text; | |||
using System.Threading.Tasks; | |||
using Windows.Security.Cryptography.Certificates; | |||
@@ -21,6 +22,7 @@ namespace MQTTnet.TestApp.UniversalWindows | |||
public sealed partial class MainPage | |||
{ | |||
private readonly ConcurrentQueue<MqttNetLogMessage> _traceMessages = new ConcurrentQueue<MqttNetLogMessage>(); | |||
private readonly ObservableCollection<IMqttClientSessionStatus> _sessions = new ObservableCollection<IMqttClientSessionStatus>(); | |||
private IMqttClient _mqttClient; | |||
private IMqttServer _mqttServer; | |||
@@ -306,6 +308,7 @@ namespace MQTTnet.TestApp.UniversalWindows | |||
var options = new MqttServerOptions(); | |||
options.DefaultEndpointOptions.Port = int.Parse(ServerPort.Text); | |||
options.Storage = storage; | |||
options.EnablePersistentSessions = ServerAllowPersistentSessions.IsChecked == true; | |||
await _mqttServer.StartAsync(options); | |||
} | |||
@@ -374,10 +377,25 @@ namespace MQTTnet.TestApp.UniversalWindows | |||
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() | |||